OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/ocpn_utils.h"
58#include "model/own_ship.h"
59#include "model/plugin_comm.h"
60#include "model/route.h"
61#include "model/routeman.h"
62#include "model/select.h"
63#include "model/select_item.h"
64#include "model/track.h"
65#include "model/ocpn_utils.h"
66
67#include "ais.h"
70#include "canvas_config.h"
71#include "canvas_menu.h"
72#include "canvas_options.h"
73#include "chartdb.h"
74#include "chartimg.h"
75#include "chcanv.h"
76#include "ch_info_win.h"
77#include "cm93.h" // for chart outline draw
78#include "compass.h"
79#include "concanv.h"
80#include "detail_slider.h"
81#include "displays.h"
82#include "hotkeys_dlg.h"
83#include "font_mgr.h"
84#include "gl_texture_descr.h"
85#include "go_to_position_dlg.h"
86#include "gshhs.h"
87#include "ienc_toolbar.h"
88#include "kml.h"
89#include "line_clip.h"
90#include "mark_info.h"
91#include "mbtiles.h"
92#include "mui_bar.h"
93#include "navutil.h"
94#include "ocpn_aui_manager.h"
95#include "ocpndc.h"
96#include "ocpn_pixel.h"
97#include "ocpn_region.h"
98#include "options.h"
99#include "piano.h"
100#include "pluginmanager.h"
101#include "quilt.h"
102#include "route_gui.h"
103#include "routemanagerdialog.h"
104#include "route_point_gui.h"
105#include "route_prop_dlg_impl.h"
106#include "s52plib.h"
107#include "s52utils.h"
108#include "s57_query_dlg.h"
109#include "s57chart.h" // for ArrayOfS57Obj
110#include "senc_manager.h"
111#include "shapefile_basemap.h"
112#include "styles.h"
113#include "tcmgr.h"
114#include "tc_win.h"
115#include "thumbwin.h"
116#include "tide_time.h"
117#include "timers.h"
118#include "toolbar.h"
119#include "top_frame.h"
120#include "track_gui.h"
121#include "track_prop_dlg.h"
122#include "undo.h"
123#include "user_colors.h"
124
125#include "s57_ocpn_utils.h"
126
127#ifdef __ANDROID__
128#include "androidUTIL.h"
129#endif
130
131#ifdef ocpnUSE_GL
132#include "gl_chart_canvas.h"
135#endif
136
137#ifdef __VISUALC__
138#include <wx/msw/msvcrt.h>
139#endif
140
141#ifndef __WXMSW__
142#include <signal.h>
143#include <setjmp.h>
144#endif
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173arrayofCanvasPtr g_canvasArray;
175static bool g_bSmoothRecenter = true;
176static bool bDrawCurrentValues;
186static int mouse_x;
196static int mouse_y;
197static bool mouse_leftisdown;
198static bool g_brouteCreating;
199static int r_gamma_mult;
200static int g_gamma_mult;
201static int b_gamma_mult;
202static int gamma_state;
203static bool g_brightness_init;
204static int last_brightness;
205static wxGLContext *g_pGLcontext; // shared common context
206
207// "Curtain" mode parameters
208static wxDialog *g_pcurtain;
209
210static wxString g_lastS52PLIBPluginMessage;
211
212#define MIN_BRIGHT 10
213#define MAX_BRIGHT 100
214
215//------------------------------------------------------------------------------
216// ChartCanvas Implementation
217//------------------------------------------------------------------------------
218BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
219EVT_PAINT(ChartCanvas::OnPaint)
220EVT_ACTIVATE(ChartCanvas::OnActivate)
221EVT_SIZE(ChartCanvas::OnSize)
222#ifndef HAVE_WX_GESTURE_EVENTS
223EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
224#endif
225EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
226EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
227EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
228EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
229EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
230EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
231EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
232EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
233EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
234EVT_KEY_UP(ChartCanvas::OnKeyUp)
235EVT_CHAR(ChartCanvas::OnKeyChar)
236EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
237EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
238EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
239EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
240EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
241EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
242EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
243EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
244EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
245EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
246
247END_EVENT_TABLE()
248
249// Define a constructor for my canvas
250ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
251 : AbstractChartCanvas(frame, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
252 m_nmea_log(nmea_log) {
253 parent_frame = frame; // save a pointer to parent
254 m_canvasIndex = canvasIndex;
255
256 pscratch_bm = NULL;
257
258 SetBackgroundColour(wxColour(0, 0, 0));
259 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
260 // color scheme change
261
262 m_groupIndex = 0;
263 m_bDrawingRoute = false;
264 m_bRouteEditing = false;
265 m_bMarkEditing = false;
266 m_bRoutePoinDragging = false;
267 m_bIsInRadius = false;
268 m_bMayToggleMenuBar = true;
269
270 m_bFollow = false;
271 m_bShowNavobjects = true;
272 m_bTCupdate = false;
273 m_bAppendingRoute = false; // was true in MSW, why??
274 pThumbDIBShow = NULL;
275 m_bShowCurrent = false;
276 m_bShowTide = false;
277 bShowingCurrent = false;
278 pCwin = NULL;
279 warp_flag = false;
280 m_bzooming = false;
281 m_b_paint_enable = true;
282 m_routeState = 0;
283
284 pss_overlay_bmp = NULL;
285 pss_overlay_mask = NULL;
286 m_bChartDragging = false;
287 m_bMeasure_Active = false;
288 m_bMeasure_DistCircle = false;
289 m_pMeasureRoute = NULL;
290 m_pTrackRolloverWin = NULL;
291 m_pRouteRolloverWin = NULL;
292 m_pAISRolloverWin = NULL;
293 m_bedge_pan = false;
294 m_disable_edge_pan = false;
295 m_dragoffsetSet = false;
296 m_bautofind = false;
297 m_bFirstAuto = true;
298 m_groupIndex = 0;
299 m_singleChart = NULL;
300 m_upMode = NORTH_UP_MODE;
301 m_bShowAIS = true;
302 m_bShowAISScaled = false;
303 m_timed_move_vp_active = false;
304 m_inPinch = false;
305 m_disable_adjust_on_zoom = false;
306
307 m_vLat = 0.;
308 m_vLon = 0.;
309
310 m_pCIWin = NULL;
311
312 m_pSelectedRoute = NULL;
313 m_pSelectedTrack = NULL;
314 m_pRoutePointEditTarget = NULL;
315 m_pFoundPoint = NULL;
316 m_pMouseRoute = NULL;
317 m_prev_pMousePoint = NULL;
318 m_pEditRouteArray = NULL;
319 m_pFoundRoutePoint = NULL;
320 m_FinishRouteOnKillFocus = true;
321
322 m_pRolloverRouteSeg = NULL;
323 m_pRolloverTrackSeg = NULL;
324 m_bsectors_shown = false;
325
326 m_bbrightdir = false;
327 r_gamma_mult = 1;
328 g_gamma_mult = 1;
329 b_gamma_mult = 1;
330
331 m_pos_image_user_day = NULL;
332 m_pos_image_user_dusk = NULL;
333 m_pos_image_user_night = NULL;
334 m_pos_image_user_grey_day = NULL;
335 m_pos_image_user_grey_dusk = NULL;
336 m_pos_image_user_grey_night = NULL;
337
338 m_zoom_factor = 1;
339 m_rotation_speed = 0;
340 m_mustmove = 0;
341
342 m_OSoffsetx = 0.;
343 m_OSoffsety = 0.;
344
345 m_pos_image_user_yellow_day = NULL;
346 m_pos_image_user_yellow_dusk = NULL;
347 m_pos_image_user_yellow_night = NULL;
348
349 SetOwnShipState(SHIP_INVALID);
350
351 undo = new Undo(this);
352
353 VPoint.Invalidate();
354
355 m_glcc = NULL;
356
357 m_focus_indicator_pix = 1;
358
359 m_pCurrentStack = NULL;
360 m_bpersistent_quilt = false;
361 m_piano_ctx_menu = NULL;
362 m_Compass = NULL;
363 m_NotificationsList = NULL;
364 m_notification_button = NULL;
365
366 g_ChartNotRenderScaleFactor = 2.0;
367 m_bShowScaleInStatusBar = true;
368
369 m_muiBar = NULL;
370 m_bShowScaleInStatusBar = false;
371 m_show_focus_bar = true;
372
373 m_bShowOutlines = false;
374 m_bDisplayGrid = false;
375 m_bShowDepthUnits = true;
376 m_encDisplayCategory = (int)STANDARD;
377
378 m_encShowLights = true;
379 m_encShowAnchor = true;
380 m_encShowDataQual = false;
381 m_bShowGPS = true;
382 m_pQuilt = new Quilt(this);
383 SetQuiltMode(true);
384 SetAlertString("");
385 m_sector_glat = 0;
386 m_sector_glon = 0;
387 g_PrintingInProgress = false;
388
389#ifdef HAVE_WX_GESTURE_EVENTS
390 m_oldVPSScale = -1.0;
391 m_popupWanted = false;
392 m_leftdown = false;
393#endif /* HAVE_WX_GESTURE_EVENTS */
394 m_inLongPress = false;
395 m_sw_down_time = 0;
396 m_sw_up_time = 0;
397 m_sw_left_down.Start();
398 m_sw_left_up.Start();
399
400 SetupGlCanvas();
401
402 singleClickEventIsValid = false;
403
404 // Build the cursors
405
406 pCursorLeft = NULL;
407 pCursorRight = NULL;
408 pCursorUp = NULL;
409 pCursorDown = NULL;
410 pCursorArrow = NULL;
411 pCursorPencil = NULL;
412 pCursorCross = NULL;
413
414 RebuildCursors();
415
416 SetCursor(*pCursorArrow);
417
418 pPanTimer = new wxTimer(this, m_MouseDragging);
419 pPanTimer->Stop();
420
421 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
422 pMovementTimer->Stop();
423
424 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
425 pMovementStopTimer->Stop();
426
427 pRotDefTimer = new wxTimer(this, ROT_TIMER);
428 pRotDefTimer->Stop();
429
430 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
431 m_DoubleClickTimer->Stop();
432
433 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
434 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
435 m_chart_drag_inertia_active = false;
436
437 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
438 m_animationActive = false;
439 m_menuTimer.SetOwner(this, MENU_TIMER);
440 m_tap_timer.SetOwner(this, TAP_TIMER);
441
442 m_panx = m_pany = 0;
443 m_panspeed = 0;
444 m_panx_target_final = m_pany_target_final = 0;
445 m_panx_target_now = m_pany_target_now = 0;
446 m_DragTrigger = -1;
447
448 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
449 pCurTrackTimer->Stop();
450 m_curtrack_timer_msec = 10;
451
452 m_wheelzoom_stop_oneshot = 0;
453 m_last_wheel_dir = 0;
454
455 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
456
457 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
458
459 m_rollover_popup_timer_msec = 20;
460
461 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
462
463 m_b_rot_hidef = true;
464
465 proute_bm = NULL;
466 m_prot_bm = NULL;
467
468 m_upMode = NORTH_UP_MODE;
469 m_bLookAhead = false;
470
471 // Set some benign initial values
472
473 m_cs = GLOBAL_COLOR_SCHEME_DAY;
474 VPoint.clat = 0;
475 VPoint.clon = 0;
476 VPoint.view_scale_ppm = 1;
477 VPoint.Invalidate();
478 m_nMeasureState = 0;
479
480 m_canvas_scale_factor = 1.;
481
482 m_canvas_width = 1000;
483
484 m_overzoomTextWidth = 0;
485 m_overzoomTextHeight = 0;
486
487 // Create the default world chart
488 pWorldBackgroundChart = new GSHHSChart;
489 gShapeBasemap.Reset();
490
491 // Create the default depth unit emboss maps
492 m_pEM_Feet = NULL;
493 m_pEM_Meters = NULL;
494 m_pEM_Fathoms = NULL;
495
496 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
497
498 m_pEM_OverZoom = NULL;
499 SetOverzoomFont();
500 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
501
502 // Build icons for tide/current points
503 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
504 m_bmTideDay =
505 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
506
507 // Dusk
508 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
509
510 // Night
511 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
512
513 // Build Dusk/Night ownship icons
514 double factor_dusk = 0.5;
515 double factor_night = 0.25;
516
517 // Red
518 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
519
520 int rimg_width = m_os_image_red_day.GetWidth();
521 int rimg_height = m_os_image_red_day.GetHeight();
522
523 m_os_image_red_dusk = m_os_image_red_day.Copy();
524 m_os_image_red_night = m_os_image_red_day.Copy();
525
526 for (int iy = 0; iy < rimg_height; iy++) {
527 for (int ix = 0; ix < rimg_width; ix++) {
528 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
529 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
530 m_os_image_red_day.GetGreen(ix, iy),
531 m_os_image_red_day.GetBlue(ix, iy));
532 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
533 hsv.value = hsv.value * factor_dusk;
534 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
535 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
536
537 hsv = wxImage::RGBtoHSV(rgb);
538 hsv.value = hsv.value * factor_night;
539 nrgb = wxImage::HSVtoRGB(hsv);
540 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
541 }
542 }
543 }
544
545 // Grey
546 m_os_image_grey_day =
547 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
548
549 int gimg_width = m_os_image_grey_day.GetWidth();
550 int gimg_height = m_os_image_grey_day.GetHeight();
551
552 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
553 m_os_image_grey_night = m_os_image_grey_day.Copy();
554
555 for (int iy = 0; iy < gimg_height; iy++) {
556 for (int ix = 0; ix < gimg_width; ix++) {
557 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
558 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
559 m_os_image_grey_day.GetGreen(ix, iy),
560 m_os_image_grey_day.GetBlue(ix, iy));
561 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
562 hsv.value = hsv.value * factor_dusk;
563 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
564 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
565
566 hsv = wxImage::RGBtoHSV(rgb);
567 hsv.value = hsv.value * factor_night;
568 nrgb = wxImage::HSVtoRGB(hsv);
569 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
570 }
571 }
572 }
573
574 // Yellow
575 m_os_image_yellow_day = m_os_image_red_day.Copy();
576
577 gimg_width = m_os_image_yellow_day.GetWidth();
578 gimg_height = m_os_image_yellow_day.GetHeight();
579
580 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
581 m_os_image_yellow_night = m_os_image_red_day.Copy();
582
583 for (int iy = 0; iy < gimg_height; iy++) {
584 for (int ix = 0; ix < gimg_width; ix++) {
585 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
586 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
587 m_os_image_yellow_day.GetGreen(ix, iy),
588 m_os_image_yellow_day.GetBlue(ix, iy));
589 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
590 hsv.hue += 60. / 360.; // shift to yellow
591 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
592 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
593
594 hsv = wxImage::RGBtoHSV(rgb);
595 hsv.value = hsv.value * factor_dusk;
596 hsv.hue += 60. / 360.; // shift to yellow
597 nrgb = wxImage::HSVtoRGB(hsv);
598 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
599
600 hsv = wxImage::RGBtoHSV(rgb);
601 hsv.hue += 60. / 360.; // shift to yellow
602 hsv.value = hsv.value * factor_night;
603 nrgb = wxImage::HSVtoRGB(hsv);
604 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
605 }
606 }
607 }
608
609 // Set initial pointers to ownship images
610 m_pos_image_red = &m_os_image_red_day;
611 m_pos_image_yellow = &m_os_image_yellow_day;
612 m_pos_image_grey = &m_os_image_grey_day;
613
614 SetUserOwnship();
615
616 m_pBrightPopup = NULL;
617
618#ifdef ocpnUSE_GL
619 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
620#endif
621
622 SetupGridFont();
623
624 m_Piano = new Piano(this);
625
626 m_bShowCompassWin = false;
627 m_Compass = new ocpnCompass(this);
628 m_Compass->SetScaleFactor(g_compass_scalefactor);
629 m_Compass->Show(false); // Will be shown later during init chain.
630
631 if (IsPrimaryCanvas() && !g_disableNotifications) {
632 m_notification_button = new NotificationButton(this);
633 m_notification_button->SetScaleFactor(g_compass_scalefactor);
634 m_notification_button->Show(true);
635 }
636
637 m_pianoFrozen = false;
638
639 SetMinSize(wxSize(200, 200));
640
641 m_displayScale = 1.0;
642#if defined(__WXOSX__) || defined(__WXGTK3__)
643 // Support scaled HDPI displays.
644 m_displayScale = GetContentScaleFactor();
645#endif
646 VPoint.SetPixelScale(m_displayScale);
647
648#ifdef HAVE_WX_GESTURE_EVENTS
649 // if (!m_glcc)
650 {
651 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
652 wxLogError("Failed to enable touch events");
653 }
654
655 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
656
657 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
658 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
659
660 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
661 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
662
663 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
664 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
665
666 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
667 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
668 }
669#endif
670
671 // Listen for notification events
672 auto &noteman = NotificationManager::GetInstance();
673
674 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
675 evt_notificationlist_change_listener.Listen(
676 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
677 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
678 if (m_NotificationsList && m_NotificationsList->IsShown()) {
679 m_NotificationsList->ReloadNotificationList();
680 }
681 Refresh();
682 });
683}
684
685ChartCanvas::~ChartCanvas() {
686 delete pThumbDIBShow;
687
688 // Delete Cursors
689 delete pCursorLeft;
690 delete pCursorRight;
691 delete pCursorUp;
692 delete pCursorDown;
693 delete pCursorArrow;
694 delete pCursorPencil;
695 delete pCursorCross;
696
697 delete pPanTimer;
698 delete pMovementTimer;
699 delete pMovementStopTimer;
700 delete pCurTrackTimer;
701 delete pRotDefTimer;
702 delete m_DoubleClickTimer;
703
704 delete m_pTrackRolloverWin;
705 delete m_pRouteRolloverWin;
706 delete m_pAISRolloverWin;
707 delete m_pBrightPopup;
708
709 delete m_pCIWin;
710
711 delete pscratch_bm;
712
713 m_dc_route.SelectObject(wxNullBitmap);
714 delete proute_bm;
715
716 delete pWorldBackgroundChart;
717 delete pss_overlay_bmp;
718
719 delete m_pEM_Feet;
720 delete m_pEM_Meters;
721 delete m_pEM_Fathoms;
722
723 delete m_pEM_OverZoom;
724 // delete m_pEM_CM93Offset;
725
726 delete m_prot_bm;
727
728 delete m_pos_image_user_day;
729 delete m_pos_image_user_dusk;
730 delete m_pos_image_user_night;
731 delete m_pos_image_user_grey_day;
732 delete m_pos_image_user_grey_dusk;
733 delete m_pos_image_user_grey_night;
734 delete m_pos_image_user_yellow_day;
735 delete m_pos_image_user_yellow_dusk;
736 delete m_pos_image_user_yellow_night;
737
738 delete undo;
739#ifdef ocpnUSE_GL
740 if (!g_bdisable_opengl) {
741 delete m_glcc;
742
743#if wxCHECK_VERSION(2, 9, 0)
744 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
745#endif
746 }
747#endif
748
749 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
750 // wx tries to deliver events to this canvas during destroy.
751 MUIBar *muiBar = m_muiBar;
752 m_muiBar = 0;
753 delete muiBar;
754 delete m_pQuilt;
755 delete m_pCurrentStack;
756 delete m_Compass;
757 delete m_Piano;
758 delete m_notification_button;
759}
760
761void ChartCanvas::SetupGridFont() {
762 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
763 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
764 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
765 m_pgridFont = FontMgr::Get().FindOrCreateFont(
766 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
767 FALSE, wxString("Arial"));
768}
769
770void ChartCanvas::RebuildCursors() {
771 delete pCursorLeft;
772 delete pCursorRight;
773 delete pCursorUp;
774 delete pCursorDown;
775 delete pCursorArrow;
776 delete pCursorPencil;
777 delete pCursorCross;
778
779 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
780 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
781
782 double pencilScale =
783 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
784
785 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
786 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
787 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
788 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
789 wxImage ICursorPencil =
790 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
791 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
792
793#if !defined(__WXMSW__) && !defined(__WXQT__)
794 ICursorLeft.ConvertAlphaToMask(128);
795 ICursorRight.ConvertAlphaToMask(128);
796 ICursorUp.ConvertAlphaToMask(128);
797 ICursorDown.ConvertAlphaToMask(128);
798 ICursorPencil.ConvertAlphaToMask(10);
799 ICursorCross.ConvertAlphaToMask(10);
800#endif
801
802 if (ICursorLeft.Ok()) {
803 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
805 pCursorLeft = new wxCursor(ICursorLeft);
806 } else
807 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
808
809 if (ICursorRight.Ok()) {
810 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
812 pCursorRight = new wxCursor(ICursorRight);
813 } else
814 pCursorRight = new wxCursor(wxCURSOR_ARROW);
815
816 if (ICursorUp.Ok()) {
817 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
819 pCursorUp = new wxCursor(ICursorUp);
820 } else
821 pCursorUp = new wxCursor(wxCURSOR_ARROW);
822
823 if (ICursorDown.Ok()) {
824 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
826 pCursorDown = new wxCursor(ICursorDown);
827 } else
828 pCursorDown = new wxCursor(wxCURSOR_ARROW);
829
830 if (ICursorPencil.Ok()) {
831 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
833 pCursorPencil = new wxCursor(ICursorPencil);
834 } else
835 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
836
837 if (ICursorCross.Ok()) {
838 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
840 pCursorCross = new wxCursor(ICursorCross);
841 } else
842 pCursorCross = new wxCursor(wxCURSOR_ARROW);
843
844 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
845 pPlugIn_Cursor = NULL;
846}
847
848void ChartCanvas::CanvasApplyLocale() {
849 CreateDepthUnitEmbossMaps(m_cs);
850 CreateOZEmbossMapData(m_cs);
851}
852
853void ChartCanvas::SetupGlCanvas() {
854#ifndef __ANDROID__
855#ifdef ocpnUSE_GL
856 if (!g_bdisable_opengl) {
857 if (g_bopengl) {
858 wxLogMessage("Creating glChartCanvas");
859 m_glcc = new glChartCanvas(this);
860
861 // We use one context for all GL windows, so that textures etc will be
862 // automatically shared
863 if (IsPrimaryCanvas()) {
864 // qDebug() << "Creating Primary Context";
865
866 // wxGLContextAttrs ctxAttr;
867 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
868 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
869 // NULL, &ctxAttr);
870 wxGLContext *pctx = new wxGLContext(m_glcc);
871 m_glcc->SetContext(pctx);
872 g_pGLcontext = pctx; // Save a copy of the common context
873 } else {
874#ifdef __WXOSX__
875 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
876#else
877 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
878 // saved common context
879#endif
880 }
881 }
882 }
883#endif
884#endif
885
886#ifdef __ANDROID__ // ocpnUSE_GL
887 if (!g_bdisable_opengl) {
888 if (g_bopengl) {
889 // qDebug() << "SetupGlCanvas";
890 wxLogMessage("Creating glChartCanvas");
891
892 // We use one context for all GL windows, so that textures etc will be
893 // automatically shared
894 if (IsPrimaryCanvas()) {
895 qDebug() << "Creating Primary glChartCanvas";
896
897 // wxGLContextAttrs ctxAttr;
898 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
899 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
900 // NULL, &ctxAttr);
901 m_glcc = new glChartCanvas(this);
902
903 wxGLContext *pctx = new wxGLContext(m_glcc);
904 m_glcc->SetContext(pctx);
905 g_pGLcontext = pctx; // Save a copy of the common context
906 m_glcc->m_pParentCanvas = this;
907 // m_glcc->Reparent(this);
908 } else {
909 qDebug() << "Creating Secondary glChartCanvas";
910 // QGLContext *pctx =
911 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
912 // << "pctx: " << pctx;
913
914 m_glcc =
915 new glChartCanvas(wxTheApp->GetTopWindow(),
916 top_frame::Get()->GetWxGlCanvas()); // Shared
917 // m_glcc = new glChartCanvas(this, pctx); //Shared
918 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
919 wxGLContext *pwxctx = new wxGLContext(m_glcc);
920 m_glcc->SetContext(pwxctx);
921 m_glcc->m_pParentCanvas = this;
922 // m_glcc->Reparent(this);
923 }
924 }
925 }
926#endif
927}
928
929void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
930 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
931
932 // On Android, we get a KillFocus on just about every keystroke.
933 // Why?
934#ifdef __ANDROID__
935 return;
936#endif
937
938 // Special logic:
939 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
940 // canvas focus. Why??? Who knows... So, we provide for this case by
941 // starting a timer if required to actually Finish() a route on a legitimate
942 // focus change, but not if the focus is quickly regained ( <20 msec.) on
943 // this canvas.
944#ifdef __WXOSX__
945 if (m_routeState && m_FinishRouteOnKillFocus)
946 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
947#else
948 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
949#endif
950}
951
952void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
953 m_routeFinishTimer.Stop();
954
955 // Try to keep the global top-line menubar selections up to date with the
956 // current "focus" canvas
957 top_frame::Get()->UpdateGlobalMenuItems(this);
958
959 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
960}
961
962void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
963 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
964}
965
966#ifdef HAVE_WX_GESTURE_EVENTS
967void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
968#ifdef __ANDROID__
969 /* we defer the popup menu call upon the leftup event
970 else the menu disappears immediately,
971 (see
972 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
973 */
974 m_popupWanted = true;
975#else
976 m_inLongPress = !g_bhide_context_menus;
977
978 // Send a synthetic mouse left-up event to sync the mouse pan logic.
979 m_menuPos = event.GetPosition();
980 wxMouseEvent ev(wxEVT_LEFT_UP);
981 ev.m_x = m_menuPos.x;
982 ev.m_y = m_menuPos.y;
983 wxPostEvent(this, ev);
984
985 // In touch mode, send a "RIGHT CLICK" event, for plugins
986 if (g_btouch) {
987 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
988 ev_right_click.m_x = m_menuPos.x;
989 ev_right_click.m_y = m_menuPos.y;
990 MouseEvent(ev_right_click);
991 }
992#endif
993}
994
995void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
996 // not implemented yet
997}
998
999void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1000
1001void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1002
1003void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1004#ifdef __WXGTK__
1005 long dt = m_sw_left_up.Time() - m_sw_up_time;
1006 m_sw_up_time = m_sw_left_up.Time();
1007
1008 // printf(" dt %ld\n",dt);
1009 if (dt < 5) {
1010 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1011 // ignore it.
1012 return;
1013 }
1014#endif
1015 // printf("Left_UP\n");
1016
1017 wxPoint pos = event.GetPosition();
1018
1019 m_leftdown = false;
1020
1021 if (!m_popupWanted) {
1022 wxMouseEvent ev(wxEVT_LEFT_UP);
1023 ev.m_x = pos.x;
1024 ev.m_y = pos.y;
1025 MouseEvent(ev);
1026 return;
1027 }
1028
1029 m_popupWanted = false;
1030
1031 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1032 ev.m_x = pos.x;
1033 ev.m_y = pos.y;
1034
1035 MouseEvent(ev);
1036}
1037
1038void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1039 m_leftdown = true;
1040
1041 // Detect and manage multiple left-downs coming from GTK mouse emulation
1042#ifdef __WXGTK__
1043 long dt = m_sw_left_down.Time() - m_sw_down_time;
1044 m_sw_down_time = m_sw_left_down.Time();
1045
1046 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1047
1048 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1049 // The timing between the two events is dependent upon the wxWidgets
1050 // message queue status, and the processing time required for intervening
1051 // events.
1052 // We detect and remove the duplicate events by measuring the elapsed time
1053 // between arrival of events.
1054 // Choose a duplicate detection time long enough to catch worst case time lag
1055 // between duplicating events, but considerably shorter than the nominal
1056 // "intentional double-click" time interval defined generally as 350 msec.
1057 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1058 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1059 // ignore it.
1060 return;
1061 }
1062#endif
1063
1064 // printf("Left_DOWN\n");
1065
1066 // detect and manage double-tap
1067#ifdef __WXGTK__
1068 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1069 2; // Use system setting for distance
1070 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1071 m_lastTapPos.y - max_double_click_distance,
1072 max_double_click_distance * 2, max_double_click_distance * 2);
1073
1074 // A new tap has started, check if it's close enough and in time
1075 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1076 // printf(" TapBump 1\n");
1077 m_tap_count += 1;
1078 } else {
1079 // printf(" TapSet 1\n");
1080 m_tap_count = 1;
1081 m_lastTapPos = event.GetPosition();
1082 m_tap_timer.StartOnce(
1083 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1084 }
1085
1086 if (m_tap_count == 2) {
1087 // printf(" Doubletap detected\n");
1088 m_tap_count = 0; // Reset after a double-tap
1089
1090 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1091 ev.m_x = event.m_x;
1092 ev.m_y = event.m_y;
1093 // wxPostEvent(this, ev);
1094 MouseEvent(ev);
1095 return;
1096 }
1097
1098#endif
1099
1100 MouseEvent(event);
1101}
1102
1103void ChartCanvas::OnMotion(wxMouseEvent &event) {
1104 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1105 dragging, upon simple click, and without the OnLeftDown event before Thus,
1106 this consists in skiping it, and setting the leftdown bit according to a
1107 status that we trust */
1108 event.m_leftDown = m_leftdown;
1109 MouseEvent(event);
1110}
1111
1112void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1113 /* there are spurious end zoom events upon right-click */
1114 if (event.IsGestureEnd()) return;
1115
1116 double factor = event.GetZoomFactor();
1117
1118 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1119 m_oldVPSScale = GetVPScale();
1120 }
1121
1122 double current_vps = GetVPScale();
1123 double wanted_factor = m_oldVPSScale / current_vps * factor;
1124
1125 ZoomCanvas(wanted_factor, true, false);
1126
1127 // Allow combined zoom/pan operation
1128 if (event.IsGestureStart()) {
1129 m_zoomStartPoint = event.GetPosition();
1130 } else {
1131 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1132 PanCanvas(-delta.x, -delta.y);
1133 m_zoomStartPoint = event.GetPosition();
1134 }
1135}
1136
1137void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1138
1139void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1140 DoRotateCanvas(0.0);
1141}
1142#endif /* HAVE_WX_GESTURE_EVENTS */
1143
1144void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1145 // printf("tap timer %d\n", m_tap_count);
1146 m_tap_count = 0;
1147}
1148
1149void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1150 m_FinishRouteOnKillFocus = false;
1151 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1152 m_FinishRouteOnKillFocus = true;
1153}
1154
1155void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1156 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1157 m_vLat = pcc->iLat;
1158 m_vLon = pcc->iLon;
1159
1160 m_restore_dbindex = pcc->DBindex;
1161 m_bFollow = pcc->bFollow;
1162 if (pcc->GroupID < 0) pcc->GroupID = 0;
1163
1164 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1165 m_groupIndex = 0;
1166 else
1167 m_groupIndex = pcc->GroupID;
1168
1169 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1170
1171 ShowTides(pcc->bShowTides);
1172 ShowCurrents(pcc->bShowCurrents);
1173
1174 SetShowDepthUnits(pcc->bShowDepthUnits);
1175 SetShowGrid(pcc->bShowGrid);
1176 SetShowOutlines(pcc->bShowOutlines);
1177
1178 SetShowAIS(pcc->bShowAIS);
1179 SetAttenAIS(pcc->bAttenAIS);
1180
1181 SetbEnableBasemapTile((pcc->bEnableBasemapTile));
1182
1183 // ENC options
1184 SetShowENCText(pcc->bShowENCText);
1185 m_encDisplayCategory = pcc->nENCDisplayCategory;
1186 m_encShowDepth = pcc->bShowENCDepths;
1187 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1188 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1189 m_encShowLights = pcc->bShowENCLights;
1190 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1191 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1192 m_encShowDataQual = pcc->bShowENCDataQuality;
1193
1194 bool courseUp = pcc->bCourseUp;
1195 bool headUp = pcc->bHeadUp;
1196 m_upMode = NORTH_UP_MODE;
1197 if (courseUp)
1198 m_upMode = COURSE_UP_MODE;
1199 else if (headUp)
1200 m_upMode = HEAD_UP_MODE;
1201
1202 m_bLookAhead = pcc->bLookahead;
1203
1204 m_singleChart = NULL;
1205}
1206
1207void ChartCanvas::ApplyGlobalSettings() {
1208 // GPS compas window
1209 if (m_Compass) {
1210 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1211 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1212 }
1213 if (m_notification_button) m_notification_button->UpdateStatus();
1214}
1215
1216void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1217 bool groupOK = CheckGroup(m_groupIndex);
1218
1219 if (!groupOK) {
1220 SetGroupIndex(m_groupIndex, true);
1221 }
1222}
1223
1224void ChartCanvas::SetShowGPS(bool bshow) {
1225 if (m_bShowGPS != bshow) {
1226 delete m_Compass;
1227 m_Compass = new ocpnCompass(this, bshow);
1228 m_Compass->SetScaleFactor(g_compass_scalefactor);
1229 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1230 }
1231 m_bShowGPS = bshow;
1232}
1233
1234void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1235 m_bShowCompassWin = bshow;
1236 if (m_Compass) {
1237 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1238 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1239 }
1240}
1241
1242int ChartCanvas::GetPianoHeight() {
1243 int height = 0;
1244 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1245
1246 return height;
1247}
1248
1249void ChartCanvas::ConfigureChartBar() {
1250 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1251
1252 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1253 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1254
1255 if (GetQuiltMode()) {
1256 m_Piano->SetRoundedRectangles(true);
1257 }
1258 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1259 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1260 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1261}
1262
1263void ChartCanvas::ShowTides(bool bShow) {
1264 top_frame::Get()->LoadHarmonics();
1265
1266 if (ptcmgr->IsReady()) {
1267 SetbShowTide(bShow);
1268
1269 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1270 } else {
1271 wxLogMessage("Chart1::Event...TCMgr Not Available");
1272 SetbShowTide(false);
1273 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1274 }
1275
1276 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1277 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1278
1279 // TODO
1280 // if( GetbShowTide() ) {
1281 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1282 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1283 // update
1284 // } else
1285 // FrameTCTimer.Stop();
1286}
1287
1288void ChartCanvas::ShowCurrents(bool bShow) {
1289 top_frame::Get()->LoadHarmonics();
1290
1291 if (ptcmgr->IsReady()) {
1292 SetbShowCurrent(bShow);
1293 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1294 } else {
1295 wxLogMessage("Chart1::Event...TCMgr Not Available");
1296 SetbShowCurrent(false);
1297 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1298 }
1299
1300 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1301 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1302
1303 // TODO
1304 // if( GetbShowCurrent() ) {
1305 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1306 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1307 // update
1308 // } else
1309 // FrameTCTimer.Stop();
1310}
1311
1312// TODO
1313static ChartDummy *pDummyChart;
1314
1317
1318void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1319
1320void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1321 SetAlertString("");
1322
1323 int new_index = index;
1324 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1325
1326 bool bgroup_override = false;
1327 int old_group_index = new_index;
1328
1329 if (!CheckGroup(new_index)) {
1330 new_index = 0;
1331 bgroup_override = true;
1332 }
1333
1334 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1335 new_index = index;
1336
1337 // Get the currently displayed chart native scale, and the current ViewPort
1338 int current_chart_native_scale = GetCanvasChartNativeScale();
1339 ViewPort vp = GetVP();
1340
1341 m_groupIndex = new_index;
1342
1343 // Are there ENCs in this group
1344 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1345
1346 // Update the MUIBar for ENC availability
1347 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1348
1349 // Allow the chart database to pre-calculate the MBTile inclusion test
1350 // boolean...
1351 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1352
1353 // Invalidate the "sticky" chart on group change, since it might not be in
1354 // the new group
1355 g_sticky_chart = -1;
1356
1357 // We need a chartstack and quilt to figure out which chart to open in the
1358 // new group
1359 UpdateCanvasOnGroupChange();
1360
1361 int dbi_now = -1;
1362 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1363
1364 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1365
1366 // If a new reference chart is indicated, set a good scale for it.
1367 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1368 double best_scale = GetBestStartScale(dbi_hint, vp);
1369 SetVPScale(best_scale);
1370 }
1371
1372 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1373
1374 // Refresh the canvas, selecting the "best" chart,
1375 // applying the prior ViewPort exactly
1376 canvasChartsRefresh(dbi_hint);
1377
1378 UpdateCanvasControlBar();
1379
1380 if (!autoSwitch && bgroup_override) {
1381 // show a short timed message box
1382 wxString msg(_("Group \""));
1383
1384 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1385 msg += pGroup->m_group_name;
1386
1387 msg += _("\" is empty.");
1388
1389 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1390
1391 return;
1392 }
1393
1394 // Message box is deferred so that canvas refresh occurs properly before
1395 // dialog
1396 if (bgroup_override) {
1397 wxString msg(_("Group \""));
1398
1399 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1400 msg += pGroup->m_group_name;
1401
1402 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1403
1404 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1405 }
1406}
1407
1408bool ChartCanvas::CheckGroup(int igroup) {
1409 if (!ChartData) return true; // Not known yet...
1410
1411 if (igroup == 0) return true; // "all charts" is always OK
1412
1413 if (igroup < 0) // negative group is an error
1414 return false;
1415
1416 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1417
1418 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1419 // and auto-shift to group 0
1420 return false;
1421
1422 for (const auto &elem : pGroup->m_element_array) {
1423 for (unsigned int ic = 0;
1424 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1425 auto &cte = ChartData->GetChartTableEntry(ic);
1426 wxString chart_full_path(cte.GetpFullPath(), wxConvUTF8);
1427
1428 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1429 }
1430 }
1431
1432 // If necessary, check for GSHHS
1433 for (const auto &elem : pGroup->m_element_array) {
1434 const wxString &element_root = elem.m_element_name;
1435 wxString test_string = "GSHH";
1436 if (element_root.Upper().Contains(test_string)) return true;
1437 }
1438
1439 return false;
1440}
1441
1442void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1443 if (!ChartData) return;
1444
1445 AbstractPlatform::ShowBusySpinner();
1446
1447 double old_scale = GetVPScale();
1448 InvalidateQuilt();
1449 SetQuiltRefChart(-1);
1450
1451 m_singleChart = NULL;
1452
1453 // delete m_pCurrentStack;
1454 // m_pCurrentStack = NULL;
1455
1456 // Build a new ChartStack
1457 if (!m_pCurrentStack) {
1458 m_pCurrentStack = new ChartStack;
1459 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1460 }
1461
1462 if (-1 != dbi_hint) {
1463 if (GetQuiltMode()) {
1464 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1465 SetQuiltRefChart(dbi_hint);
1466 } else {
1467 // Open the saved chart
1468 ChartBase *pTentative_Chart;
1469 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1470
1471 if (pTentative_Chart) {
1472 /* m_singleChart is always NULL here, (set above) should this go before
1473 * that? */
1474 if (m_singleChart) m_singleChart->Deactivate();
1475
1476 m_singleChart = pTentative_Chart;
1477 m_singleChart->Activate();
1478
1479 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1480 GetpCurrentStack(), m_singleChart->GetFullPath());
1481 }
1482 }
1483
1484 // refresh_Piano();
1485 } else {
1486 // Select reference chart from the stack, as though clicked by user
1487 // Make it the smallest scale chart on the stack
1488 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1489 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1490 SetQuiltRefChart(selected_index);
1491 }
1492
1493 // Validate the correct single chart, or set the quilt mode as appropriate
1494 SetupCanvasQuiltMode();
1495 if (!GetQuiltMode() && m_singleChart == 0) {
1496 // use a dummy like in DoChartUpdate
1497 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1498 m_singleChart = pDummyChart;
1499 SetVPScale(old_scale);
1500 }
1501
1502 ReloadVP();
1503
1504 UpdateCanvasControlBar();
1505 UpdateGPSCompassStatusBox(true);
1506
1507 SetCursor(wxCURSOR_ARROW);
1508
1509 AbstractPlatform::HideBusySpinner();
1510}
1511
1512bool ChartCanvas::DoCanvasUpdate() {
1513 double tLat, tLon; // Chart Stack location
1514 double vpLat, vpLon; // ViewPort location
1515 bool blong_jump = false;
1516 meters_to_shift = 0;
1517 dir_to_shift = 0;
1518
1519 bool bNewChart = false;
1520 bool bNewView = false;
1521 bool bCanvasChartAutoOpen = true; // debugging
1522
1523 bool bNewPiano = false;
1524 bool bOpenSpecified;
1525 ChartStack LastStack;
1526 ChartBase *pLast_Ch;
1527
1528 ChartStack WorkStack;
1529
1530 if (!GetVP().IsValid()) return false;
1531 if (bDBUpdateInProgress) return false;
1532 if (!ChartData) return false;
1533
1534 if (ChartData->IsBusy()) return false;
1535 // Do not disturb any existing animations
1536 if (m_chart_drag_inertia_active) return false;
1537 if (m_animationActive) return false;
1538
1539 // Startup case:
1540 // Quilting is enabled, but the last chart seen was not quiltable
1541 // In this case, drop to single chart mode, set persistence flag,
1542 // And open the specified chart
1543 // TODO implement this
1544 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1545 // if( GetQuiltMode() ) {
1546 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1547 // gFrame->ToggleQuiltMode();
1548 // m_bpersistent_quilt = true;
1549 // m_singleChart = NULL;
1550 // }
1551 // }
1552 // }
1553
1554 // If in auto-follow mode, use the current glat,glon to build chart
1555 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1556 // other means
1557
1558 if (m_bFollow) {
1559 tLat = gLat;
1560 tLon = gLon;
1561
1562 // Set the ViewPort center based on the OWNSHIP offset
1563 double dx = m_OSoffsetx;
1564 double dy = m_OSoffsety;
1565 double d_east = dx / GetVP().view_scale_ppm;
1566 double d_north = dy / GetVP().view_scale_ppm;
1567
1568 if (GetUpMode() == NORTH_UP_MODE) {
1569 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1570 } else {
1571 double offset_angle = atan2(d_north, d_east);
1572 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1573 double chart_angle = GetVPRotation();
1574 double target_angle = chart_angle + offset_angle;
1575 double d_east_mod = offset_distance * cos(target_angle);
1576 double d_north_mod = offset_distance * sin(target_angle);
1577 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1578 }
1579
1580 // on lookahead mode, adjust the vp center point
1581 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1582 double cog_to_use = gCog;
1583 if (g_btenhertz &&
1584 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1585 cog_to_use = gCog_gt;
1586 blong_jump = true;
1587 }
1588 if (!g_btenhertz) cog_to_use = g_COGAvg;
1589
1590 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1591
1592 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1593 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1594
1595 double pixel_delta_tent =
1596 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1597
1598 double pixel_delta = 0;
1599
1600 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1601 // avoid jumping of the vp center point during slow maneuvering, or at
1602 // anchor....
1603 if (!std::isnan(gSog)) {
1604 if (gSog < 2.0)
1605 pixel_delta = 0.;
1606 else
1607 pixel_delta = pixel_delta_tent;
1608 }
1609
1610 meters_to_shift = 0;
1611 dir_to_shift = 0;
1612 if (!std::isnan(gCog)) {
1613 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1614 dir_to_shift = cog_to_use;
1615 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1616 &vpLon);
1617 } else {
1618 vpLat = gLat;
1619 vpLon = gLon;
1620 }
1621 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1622 m_OSoffsetx = 0; // center ownship on loss of GPS
1623 m_OSoffsety = 0;
1624 vpLat = gLat;
1625 vpLon = gLon;
1626 }
1627
1628 } else {
1629 tLat = m_vLat;
1630 tLon = m_vLon;
1631 vpLat = m_vLat;
1632 vpLon = m_vLon;
1633 }
1634
1635 if (GetQuiltMode()) {
1636 int current_db_index = -1;
1637 if (m_pCurrentStack)
1638 current_db_index =
1639 m_pCurrentStack
1640 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1641 // chart dbIndex
1642 else
1643 m_pCurrentStack = new ChartStack;
1644
1645 // This logic added to enable opening a chart when there is no
1646 // previous chart indication, either from inital startup, or from adding
1647 // new chart directory
1648 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1649 m_pCurrentStack) {
1650 if (m_pCurrentStack->nEntry) {
1651 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1652 1); // smallest scale
1653 SelectQuiltRefdbChart(new_dbIndex, true);
1654 m_bautofind = false;
1655 }
1656 }
1657
1658 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1659 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1660
1661 if (m_bFirstAuto) {
1662 // Allow the chart database to pre-calculate the MBTile inclusion test
1663 // boolean...
1664 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1665
1666 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1667 // physical pixels. On standard DPI displays where logical = physical
1668 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1669 // logical pixels, this ratio would be 0.5.
1670 double proposed_scale_onscreen =
1671 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1672
1673 int initial_db_index = m_restore_dbindex;
1674 if (initial_db_index < 0) {
1675 if (m_pCurrentStack->nEntry) {
1676 initial_db_index =
1677 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1678 } else
1679 m_bautofind = true; // initial_db_index = 0;
1680 }
1681
1682 if (m_pCurrentStack->nEntry) {
1683 int initial_type = ChartData->GetDBChartType(initial_db_index);
1684
1685 // Check to see if the target new chart is quiltable as a reference
1686 // chart
1687
1688 if (!IsChartQuiltableRef(initial_db_index)) {
1689 // If it is not quiltable, then walk the stack up looking for a
1690 // satisfactory chart i.e. one that is quiltable and of the same type
1691 // XXX if there's none?
1692 int stack_index = 0;
1693
1694 if (stack_index >= 0) {
1695 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1696 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1697 if (IsChartQuiltableRef(test_db_index) &&
1698 (initial_type ==
1699 ChartData->GetDBChartType(initial_db_index))) {
1700 initial_db_index = test_db_index;
1701 break;
1702 }
1703 stack_index++;
1704 }
1705 }
1706 }
1707
1708 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1709 if (pc) {
1710 SetQuiltRefChart(initial_db_index);
1711 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1712 }
1713 }
1714 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1715 // just GetVPScale(), so I'm not sure why it's necessary to define the
1716 // proposed_scale_onscreen variable.
1717 bNewView |= SetViewPoint(vpLat, vpLon,
1718 GetCanvasScaleFactor() / proposed_scale_onscreen,
1719 0, GetVPRotation());
1720 }
1721 // Measure rough jump distance if in bfollow mode
1722 // No good reason to do smooth pan for
1723 // jump distance more than one screen width at scale.
1724 bool super_jump = false;
1725 if (m_bFollow) {
1726 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1727 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1728 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1729 }
1730 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1731 goto update_finish;
1732 }
1733
1734 // Single Chart Mode from here....
1735 pLast_Ch = m_singleChart;
1736 ChartTypeEnum new_open_type;
1737 ChartFamilyEnum new_open_family;
1738 if (pLast_Ch) {
1739 new_open_type = pLast_Ch->GetChartType();
1740 new_open_family = pLast_Ch->GetChartFamily();
1741 } else {
1742 new_open_type = CHART_TYPE_KAP;
1743 new_open_family = CHART_FAMILY_RASTER;
1744 }
1745
1746 bOpenSpecified = m_bFirstAuto;
1747
1748 // Make sure the target stack is valid
1749 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1750
1751 // Build a chart stack based on tLat, tLon
1752 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1753 m_groupIndex)) { // Bogus Lat, Lon?
1754 if (NULL == pDummyChart) {
1755 pDummyChart = new ChartDummy;
1756 bNewChart = true;
1757 }
1758
1759 if (m_singleChart)
1760 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1761
1762 m_singleChart = pDummyChart;
1763
1764 // If the current viewpoint is invalid, set the default scale to
1765 // something reasonable.
1766 double set_scale = GetVPScale();
1767 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1768
1769 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1770
1771 // If the chart stack has just changed, there is new status
1772 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1773 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1774 bNewPiano = true;
1775 bNewChart = true;
1776 }
1777 }
1778
1779 // Copy the new (by definition empty) stack into the target stack
1780 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1781
1782 goto update_finish;
1783 }
1784
1785 // Check to see if Chart Stack has changed
1786 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1787 // New chart stack, so...
1788 bNewPiano = true;
1789
1790 // Save a copy of the current stack
1791 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1792
1793 // Copy the new stack into the target stack
1794 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1795
1796 // Is Current Chart in new stack?
1797
1798 int tEntry = -1;
1799 if (NULL != m_singleChart) // this handles startup case
1800 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1801 m_singleChart->GetFullPath());
1802
1803 if (tEntry != -1) { // m_singleChart is in the new stack
1804 m_pCurrentStack->CurrentStackEntry = tEntry;
1805 bNewChart = false;
1806 }
1807
1808 else // m_singleChart is NOT in new stack, or m_singlechart not yet set
1809 { // So, need to open a new chart
1810 // Find the largest scale raster chart that opens OK
1811
1812 ChartBase *pProposed = NULL;
1813
1814 if (bCanvasChartAutoOpen) {
1815 bool search_direction =
1816 false; // default is to search from lowest to highest
1817 int start_index = 0;
1818
1819 // A special case: If panning at high scale, open largest scale
1820 // chart first
1821 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1822 (LastStack.nEntry == 0)) {
1823 search_direction = true;
1824 start_index = m_pCurrentStack->nEntry - 1;
1825 }
1826
1827 // Another special case, open specified db index on program start
1828 if (bOpenSpecified) {
1829 if (m_restore_dbindex >= 0) {
1830 pProposed =
1831 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
1832 std::vector<int> one_array;
1833 one_array.push_back(m_restore_dbindex);
1834 m_Piano->SetActiveKeyArray(one_array);
1835 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
1836 m_restore_dbindex = -1; // Mark as used...
1837 }
1838
1839 if (!pProposed) {
1840 search_direction = false;
1841 start_index = m_restore_dbindex;
1842 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1843 start_index = 0;
1844
1845 new_open_type = CHART_TYPE_DONTCARE;
1846 }
1847 }
1848
1849 if (!pProposed) {
1850 pProposed = ChartData->OpenStackChartConditional(
1851 m_pCurrentStack, start_index, search_direction, new_open_type,
1852 new_open_family);
1853
1854 // Try to open other types/families of chart in some priority
1855 if (NULL == pProposed)
1856 pProposed = ChartData->OpenStackChartConditional(
1857 m_pCurrentStack, start_index, search_direction,
1858 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1859
1860 if (NULL == pProposed)
1861 pProposed = ChartData->OpenStackChartConditional(
1862 m_pCurrentStack, start_index, search_direction,
1863 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1864
1865 bNewChart = true;
1866 }
1867 } // bCanvasChartAutoOpen
1868
1869 else
1870 pProposed = NULL;
1871
1872 // If no go, then
1873 // Open a Dummy Chart
1874 if (NULL == pProposed) {
1875 if (NULL == pDummyChart) {
1876 pDummyChart = new ChartDummy;
1877 bNewChart = true;
1878 }
1879
1880 if (pLast_Ch)
1881 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1882
1883 pProposed = pDummyChart;
1884 }
1885
1886 // Arriving here, pProposed points to an opened chart, or NULL.
1887 if (m_singleChart) m_singleChart->Deactivate();
1888 m_singleChart = pProposed;
1889
1890 if (m_singleChart) {
1891 m_singleChart->Activate();
1892 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1893 m_pCurrentStack, m_singleChart->GetFullPath());
1894 }
1895 } // need new chart
1896
1897 // Arriving here, m_singleChart is opened and OK, or NULL
1898 if (NULL != m_singleChart) {
1899 // Setup the view using the current scale
1900 double set_scale = GetVPScale();
1901
1902 double proposed_scale_onscreen;
1903
1904 if (m_bFollow) { // autoset the scale only if in autofollow
1905 double new_scale_ppm =
1906 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1907 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1908 } else
1909 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1910
1911 // This logic will bring a new chart onscreen at roughly twice the true
1912 // paper scale equivalent. Note that first chart opened on application
1913 // startup (bOpenSpecified = true) will open at the config saved scale
1914 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1915 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1916 double equivalent_vp_scale =
1917 GetCanvasScaleFactor() / proposed_scale_onscreen;
1918 double new_scale_ppm =
1919 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1920 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1921 }
1922
1923 if (m_bFollow) { // bounds-check the scale only if in autofollow
1924 proposed_scale_onscreen =
1925 wxMin(proposed_scale_onscreen,
1926 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1927 GetCanvasWidth()));
1928 proposed_scale_onscreen =
1929 wxMax(proposed_scale_onscreen,
1930 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1932 }
1933
1934 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1935
1936 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1937 m_singleChart->GetChartSkew() * PI / 180.,
1938 GetVPRotation());
1939 }
1940 } // new stack
1941
1942 else // No change in Chart Stack
1943 {
1944 double s = GetVPScale();
1945 if ((m_bFollow) && m_singleChart)
1946 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1947 m_singleChart->GetChartSkew() * PI / 180.,
1948 GetVPRotation());
1949 }
1950
1951update_finish:
1952
1953 // TODO
1954 // if( bNewPiano ) UpdateControlBar();
1955
1956 m_bFirstAuto = false; // Auto open on program start
1957
1958 // If we need a Refresh(), do it here...
1959 // But don't duplicate a Refresh() done by SetViewPoint()
1960 if (bNewChart && !bNewView) Refresh(false);
1961
1962#ifdef ocpnUSE_GL
1963 // If a new chart, need to invalidate gl viewport for refresh
1964 // so the fbo gets flushed
1965 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1966#endif
1967
1968 return bNewChart | bNewView;
1969}
1970
1971void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1972 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1973
1974 SetQuiltRefChart(db_index);
1975 if (ChartData) {
1976 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1977 if (pc) {
1978 if (b_autoscale) {
1979 double best_scale_ppm = GetBestVPScale(pc);
1980 SetVPScale(best_scale_ppm);
1981 }
1982 } else
1983 SetQuiltRefChart(-1);
1984 } else
1985 SetQuiltRefChart(-1);
1986}
1987
1988void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1989 std::vector<int> piano_chart_index_array =
1990 GetQuiltExtendedStackdbIndexArray();
1991 int current_db_index = piano_chart_index_array[selected_index];
1992
1993 SelectQuiltRefdbChart(current_db_index);
1994}
1995
1996double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1997 if (pchart) {
1998 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1999
2000 if ((g_bPreserveScaleOnX) ||
2001 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2002 double new_scale_ppm = GetVPScale();
2003 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2004 } else {
2005 // This logic will bring the new chart onscreen at roughly twice the true
2006 // paper scale equivalent.
2007 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2008 double equivalent_vp_scale =
2009 GetCanvasScaleFactor() / proposed_scale_onscreen;
2010 double new_scale_ppm =
2011 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2012 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2013 }
2014
2015 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2016 // set. Otherwise, we get severe performance problems on all platforms
2017
2018 double max_underzoom_multiplier = 2.0;
2019 if (GetVP().b_quilt) {
2020 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2021 pchart->GetChartType(),
2022 pchart->GetChartFamily());
2023 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2024 }
2025
2026 proposed_scale_onscreen = wxMin(
2027 proposed_scale_onscreen,
2028 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2029 max_underzoom_multiplier);
2030
2031 // And, do not allow excessive overzoom either
2032 proposed_scale_onscreen =
2033 wxMax(proposed_scale_onscreen,
2034 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2035
2036 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2037 } else
2038 return 1.0;
2039}
2040
2041void ChartCanvas::SetupCanvasQuiltMode() {
2042 if (GetQuiltMode()) // going to quilt mode
2043 {
2044 ChartData->LockCache();
2045
2046 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2047
2048 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2049
2050 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2051 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2052 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2053 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2054
2055 m_Piano->SetRoundedRectangles(true);
2056
2057 // Select the proper Ref chart
2058 int target_new_dbindex = -1;
2059 if (m_pCurrentStack) {
2060 target_new_dbindex =
2061 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2062
2063 if (-1 != target_new_dbindex) {
2064 if (!IsChartQuiltableRef(target_new_dbindex)) {
2065 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2066 int type = ChartData->GetDBChartType(target_new_dbindex);
2067
2068 // walk the stack up looking for a satisfactory chart
2069 int stack_index = m_pCurrentStack->CurrentStackEntry;
2070
2071 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2072 (stack_index >= 0)) {
2073 int proj_tent = ChartData->GetDBChartProj(
2074 m_pCurrentStack->GetDBIndex(stack_index));
2075 int type_tent = ChartData->GetDBChartType(
2076 m_pCurrentStack->GetDBIndex(stack_index));
2077
2078 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2079 if ((proj == proj_tent) && (type_tent == type)) {
2080 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2081 break;
2082 }
2083 }
2084 stack_index++;
2085 }
2086 }
2087 }
2088 }
2089
2090 if (IsChartQuiltableRef(target_new_dbindex))
2091 SelectQuiltRefdbChart(target_new_dbindex,
2092 false); // Try not to allow a scale change
2093 else { // fall back to last selected no-quilt chart as new reference
2094 int stack_index = m_pCurrentStack->CurrentStackEntry;
2095 SelectQuiltRefdbChart(m_pCurrentStack->GetDBIndex(stack_index), false);
2096 }
2097
2098 m_singleChart = NULL; // Bye....
2099
2100 // Re-qualify the quilt reference chart selection
2101 AdjustQuiltRefChart();
2102
2103 // Restore projection type saved on last quilt mode toggle
2104 // TODO
2105 // if(g_sticky_projection != -1)
2106 // GetVP().SetProjectionType(g_sticky_projection);
2107 // else
2108 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2109 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2110
2111 } else // going to SC Mode
2112 {
2113 std::vector<int> empty_array;
2114 m_Piano->SetActiveKeyArray(empty_array);
2115 m_Piano->SetNoshowIndexArray(empty_array);
2116 m_Piano->SetEclipsedIndexArray(empty_array);
2117
2118 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2119 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2120 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2121 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2122 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2123
2124 m_Piano->SetRoundedRectangles(false);
2125 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2126 }
2127
2128 // When shifting from quilt to single chart mode, select the "best" single
2129 // chart to show
2130 if (!GetQuiltMode()) {
2131 if (ChartData && ChartData->IsValid()) {
2132 UnlockQuilt();
2133
2134 double tLat, tLon;
2135 if (m_bFollow == true) {
2136 tLat = gLat;
2137 tLon = gLon;
2138 } else {
2139 tLat = m_vLat;
2140 tLon = m_vLon;
2141 }
2142
2143 if (!m_singleChart) {
2144 // First choice is to adopt the outgoing quilt reference chart
2145 if (GetQuiltReferenceChartIndex() >= 0) {
2146 m_singleChart = ChartData->OpenChartFromDB(
2147 GetQuiltReferenceChartIndex(), FULL_INIT);
2148 }
2149 // Second choice is to use any "no-quilt restore index", if available
2150 else if (m_restore_dbindex >= 0) {
2151 m_singleChart =
2152 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
2153 }
2154 // Final choice it to pick a suitable chart based on current stack.
2155 else {
2156 // Build a temporary chart stack based on tLat, tLon
2157 ChartStack TempStack;
2158 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2159 m_groupIndex);
2160
2161 // Iterate over the quilt charts actually shown, looking for the
2162 // largest scale chart that will be in the new chartstack.... This
2163 // will (almost?) always be the reference chart....
2164
2165 ChartBase *Candidate_Chart = NULL;
2166 int cur_max_scale = (int)1e8;
2167
2168 ChartBase *pChart = GetFirstQuiltChart();
2169 while (pChart) {
2170 // Is this pChart in new stack?
2171 int tEntry =
2172 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2173 if (tEntry != -1) {
2174 if (pChart->GetNativeScale() < cur_max_scale) {
2175 Candidate_Chart = pChart;
2176 cur_max_scale = pChart->GetNativeScale();
2177 }
2178 }
2179 pChart = GetNextQuiltChart();
2180 }
2181
2182 m_singleChart = Candidate_Chart;
2183
2184 // If the quilt is empty, there is no "best" chart.
2185 // So, open the smallest scale chart in the current stack
2186 if (NULL == m_singleChart) {
2187 m_singleChart = ChartData->OpenStackChartConditional(
2188 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2189 CHART_FAMILY_DONTCARE);
2190 }
2191 }
2192 }
2193 // Invalidate all the charts in the quilt,
2194 // as any cached data may be region based and not have fullscreen coverage
2195 InvalidateAllQuiltPatchs();
2196
2197 if (m_singleChart) {
2198 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2199 std::vector<int> one_array;
2200 one_array.push_back(dbi);
2201 m_Piano->SetActiveKeyArray(one_array);
2202 }
2203
2204 if (m_singleChart) {
2205 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2206 }
2207 }
2208 }
2209 // Invalidate the current stack so that it will be rebuilt on next tick
2210 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2211 SetVPScale(GetVPScale() * 1.0001);
2212}
2213
2214bool ChartCanvas::IsTempMenuBarEnabled() {
2215#ifdef __WXMSW__
2216 int major;
2217 wxGetOsVersion(&major);
2218 return (major >
2219 5); // For Windows, function is only available on Vista and above
2220#else
2221 return true;
2222#endif
2223}
2224
2225double ChartCanvas::GetCanvasRangeMeters() {
2226 int width, height;
2227 GetSize(&width, &height);
2228 int minDimension = wxMin(width, height);
2229
2230 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2231 range *= cos(GetVP().clat * PI / 180.);
2232 return range;
2233}
2234
2235void ChartCanvas::SetCanvasRangeMeters(double range) {
2236 int width, height;
2237 GetSize(&width, &height);
2238 int minDimension = wxMin(width, height);
2239
2240 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2241 SetVPScale(scale_ppm / 2);
2242}
2243
2244bool ChartCanvas::SetUserOwnship() {
2245 // Look for user defined ownship image
2246 // This may be found in the shared data location along with other user
2247 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2248 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2249 double factor_dusk = 0.5;
2250 double factor_night = 0.25;
2251
2252 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2253 m_pos_image_user_day = new wxImage;
2254 *m_pos_image_user_day = pbmp->ConvertToImage();
2255 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2256
2257 int gimg_width = m_pos_image_user_day->GetWidth();
2258 int gimg_height = m_pos_image_user_day->GetHeight();
2259
2260 // Make dusk and night images
2261 m_pos_image_user_dusk = new wxImage;
2262 m_pos_image_user_night = new wxImage;
2263
2264 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2265 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2266
2267 for (int iy = 0; iy < gimg_height; iy++) {
2268 for (int ix = 0; ix < gimg_width; ix++) {
2269 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2270 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2271 m_pos_image_user_day->GetGreen(ix, iy),
2272 m_pos_image_user_day->GetBlue(ix, iy));
2273 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2274 hsv.value = hsv.value * factor_dusk;
2275 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2276 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2277 nrgb.blue);
2278
2279 hsv = wxImage::RGBtoHSV(rgb);
2280 hsv.value = hsv.value * factor_night;
2281 nrgb = wxImage::HSVtoRGB(hsv);
2282 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2283 nrgb.blue);
2284 }
2285 }
2286 }
2287
2288 // Make some alternate greyed out day/dusk/night images
2289 m_pos_image_user_grey_day = new wxImage;
2290 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2291
2292 m_pos_image_user_grey_dusk = new wxImage;
2293 m_pos_image_user_grey_night = new wxImage;
2294
2295 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2296 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2297
2298 for (int iy = 0; iy < gimg_height; iy++) {
2299 for (int ix = 0; ix < gimg_width; ix++) {
2300 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2301 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2302 m_pos_image_user_grey_day->GetGreen(ix, iy),
2303 m_pos_image_user_grey_day->GetBlue(ix, iy));
2304 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2305 hsv.value = hsv.value * factor_dusk;
2306 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2307 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2308 nrgb.blue);
2309
2310 hsv = wxImage::RGBtoHSV(rgb);
2311 hsv.value = hsv.value * factor_night;
2312 nrgb = wxImage::HSVtoRGB(hsv);
2313 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2314 nrgb.blue);
2315 }
2316 }
2317 }
2318
2319 // Make a yellow image for rendering under low accuracy chart conditions
2320 m_pos_image_user_yellow_day = new wxImage;
2321 m_pos_image_user_yellow_dusk = new wxImage;
2322 m_pos_image_user_yellow_night = new wxImage;
2323
2324 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2325 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2326 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2327
2328 for (int iy = 0; iy < gimg_height; iy++) {
2329 for (int ix = 0; ix < gimg_width; ix++) {
2330 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2331 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2332 m_pos_image_user_grey_day->GetGreen(ix, iy),
2333 m_pos_image_user_grey_day->GetBlue(ix, iy));
2334
2335 // Simply remove all "blue" from the greyscaled image...
2336 // so, what is not black becomes yellow.
2337 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2338 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2339 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2340
2341 hsv = wxImage::RGBtoHSV(rgb);
2342 hsv.value = hsv.value * factor_dusk;
2343 nrgb = wxImage::HSVtoRGB(hsv);
2344 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2345
2346 hsv = wxImage::RGBtoHSV(rgb);
2347 hsv.value = hsv.value * factor_night;
2348 nrgb = wxImage::HSVtoRGB(hsv);
2349 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2350 0);
2351 }
2352 }
2353 }
2354
2355 return true;
2356 } else
2357 return false;
2358}
2359
2361 m_display_size_mm = size;
2362
2363 // int sx, sy;
2364 // wxDisplaySize( &sx, &sy );
2365
2366 // Calculate logical pixels per mm for later reference.
2367 wxSize sd = g_Platform->getDisplaySize();
2368 double horizontal = sd.x;
2369 // Set DPI (Win) scale factor
2370 g_scaler = g_Platform->GetDisplayDIPMult(this);
2371
2372 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2373 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2374
2375 if (ps52plib) {
2376 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2377 ps52plib->SetPPMM(m_pix_per_mm);
2378 }
2379
2380 wxString msg;
2381 msg.Printf(
2382 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2383 "%d:%d ",
2384 m_display_size_mm, sd.x, sd.y);
2385 wxLogDebug(msg);
2386
2387 int ssx, ssy;
2388 ssx = g_monitor_info[g_current_monitor].width;
2389 ssy = g_monitor_info[g_current_monitor].height;
2390 msg.Printf("monitor size: %d %d", ssx, ssy);
2391 wxLogDebug(msg);
2392
2393 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2394}
2395#if 0
2396void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2397{
2398 wxString msg(event.m_string.c_str(), wxConvUTF8);
2399 // if cpus are removed between runs
2400 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2401 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2402 }
2403
2404 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2405 {
2406 compress_msg_array.RemoveAt(event.thread);
2407 compress_msg_array.Insert( msg, event.thread);
2408 }
2409 else
2410 compress_msg_array.Add(msg);
2411
2412
2413 wxString combined_msg;
2414 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2415 combined_msg += compress_msg_array[i];
2416 combined_msg += "\n";
2417 }
2418
2419 bool skip = false;
2420 pprog->Update(pprog_count, combined_msg, &skip );
2421 pprog->SetSize(pprog_size);
2422 if(skip)
2423 b_skipout = skip;
2424}
2425#endif
2426void ChartCanvas::InvalidateGL() {
2427 if (!m_glcc) return;
2428#ifdef ocpnUSE_GL
2429 if (g_bopengl) m_glcc->Invalidate();
2430#endif
2431 if (m_Compass) m_Compass->UpdateStatus(true);
2432}
2433
2434int ChartCanvas::GetCanvasChartNativeScale() {
2435 int ret = 1;
2436 if (!VPoint.b_quilt) {
2437 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2438 } else
2439 ret = (int)m_pQuilt->GetRefNativeScale();
2440
2441 return ret;
2442}
2443
2444ChartBase *ChartCanvas::GetChartAtCursor() {
2445 ChartBase *target_chart;
2446 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2447 target_chart = m_singleChart;
2448 else if (VPoint.b_quilt)
2449 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2450 else
2451 target_chart = NULL;
2452 return target_chart;
2453}
2454
2455ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2456 ChartBase *target_chart;
2457 if (VPoint.b_quilt)
2458 target_chart =
2459 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2460 else
2461 target_chart = NULL;
2462 return target_chart;
2463}
2464
2465int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2466 int new_dbIndex = -1;
2467 if (!VPoint.b_quilt) {
2468 if (m_pCurrentStack) {
2469 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2470 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2471 if (sc >= scale) {
2472 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2473 break;
2474 }
2475 }
2476 }
2477 } else {
2478 // Using the current quilt, select a useable reference chart
2479 // Said chart will be in the extended (possibly full-screen) stack,
2480 // And will have a scale equal to or just greater than the stipulated
2481 // value, and will belong to the same chart family (RNC/ENC)
2482 // If no family match is found, then family requirement is ignored.
2483 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2484 if (im > 0) {
2485 // Find closest using scale and family
2486 for (unsigned int is = 0; is < im; is++) {
2487 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2488 m_pQuilt->GetExtendedStackIndexArray()[is]);
2489 if ((m.Scale_ge(scale)) &&
2490 (m_pQuilt->GetRefFamily() == m.GetChartFamily())) {
2491 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2492 break;
2493 }
2494 }
2495 // if not found, likely due to Family requirement
2496 // That is, there is no matching family chart in the StackIndexArray.
2497 // Try again, without consideration of family.
2498 if (new_dbIndex < 0) {
2499 for (unsigned int is = 0; is < im; is++) {
2500 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2501 m_pQuilt->GetExtendedStackIndexArray()[is]);
2502 if (m.Scale_ge(scale)) {
2503 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2504 break;
2505 }
2506 }
2507 }
2508 }
2509 }
2510
2511 return new_dbIndex;
2512}
2513
2514void ChartCanvas::EnablePaint(bool b_enable) {
2515 m_b_paint_enable = b_enable;
2516#ifdef ocpnUSE_GL
2517 if (m_glcc) m_glcc->EnablePaint(b_enable);
2518#endif
2519}
2520
2521bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2522
2523void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2524
2525std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2526 return m_pQuilt->GetQuiltIndexArray();
2527 ;
2528}
2529
2530void ChartCanvas::SetQuiltMode(bool b_quilt) {
2531 VPoint.b_quilt = b_quilt;
2532 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2533}
2534
2535bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2536
2537int ChartCanvas::GetQuiltReferenceChartIndex() {
2538 return m_pQuilt->GetRefChartdbIndex();
2539}
2540
2541void ChartCanvas::InvalidateAllQuiltPatchs() {
2542 m_pQuilt->InvalidateAllQuiltPatchs();
2543}
2544
2545ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2546 return m_pQuilt->GetLargestScaleChart();
2547}
2548
2549ChartBase *ChartCanvas::GetFirstQuiltChart() {
2550 return m_pQuilt->GetFirstChart();
2551}
2552
2553ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2554
2555int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2556
2557void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2558 m_pQuilt->SetHiliteIndex(dbIndex);
2559}
2560
2561void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2562 m_pQuilt->SetHiliteIndexArray(hilite_array);
2563}
2564
2565void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2566 m_pQuilt->ClearHiliteIndexArray();
2567}
2568
2569std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2570 bool flag2) {
2571 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2572}
2573
2574int ChartCanvas::GetQuiltRefChartdbIndex() {
2575 return m_pQuilt->GetRefChartdbIndex();
2576}
2577
2578std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2579 return m_pQuilt->GetExtendedStackIndexArray();
2580}
2581
2582std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2583 return m_pQuilt->GetFullscreenIndexArray();
2584}
2585
2586std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2587 return m_pQuilt->GetEclipsedStackIndexArray();
2588}
2589
2590void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2591
2592double ChartCanvas::GetQuiltMaxErrorFactor() {
2593 return m_pQuilt->GetMaxErrorFactor();
2594}
2595
2596bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2597 return m_pQuilt->IsChartQuiltableRef(db_index);
2598}
2599
2600bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2601 double chartMaxScale =
2602 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2603 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2604}
2605
2606void ChartCanvas::StartMeasureRoute() {
2607 if (!m_routeState) { // no measure tool if currently creating route
2608 if (m_bMeasure_Active) {
2609 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2610 m_pMeasureRoute = NULL;
2611 }
2612
2613 m_bMeasure_Active = true;
2614 m_nMeasureState = 1;
2615 m_bDrawingRoute = false;
2616
2617 SetCursor(*pCursorPencil);
2618 Refresh();
2619 }
2620}
2621
2622void ChartCanvas::CancelMeasureRoute() {
2623 m_bMeasure_Active = false;
2624 m_nMeasureState = 0;
2625 m_bDrawingRoute = false;
2626
2627 if (m_pMeasureRoute && g_pRouteMan->IsRouteInList(m_pMeasureRoute)) {
2628 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2629 } else if (m_pMeasureRoute) {
2630 wxLogMessage("CancelMeasureRoute: stale route %p", m_pMeasureRoute);
2631 }
2632
2633 m_pMeasureRoute = NULL;
2634
2635 SetCursor(*pCursorArrow);
2636}
2637
2638ViewPort &ChartCanvas::GetVP() { return VPoint; }
2639
2640void ChartCanvas::SetVP(ViewPort &vp) {
2641 VPoint = vp;
2642 VPoint.SetPixelScale(m_displayScale);
2643}
2644
2645// void ChartCanvas::SetFocus()
2646// {
2647// printf("set %d\n", m_canvasIndex);
2648// //wxWindow:SetFocus();
2649// }
2650
2651void ChartCanvas::TriggerDeferredFocus() {
2652 // #if defined(__WXGTK__) || defined(__WXOSX__)
2653
2654 m_deferredFocusTimer.Start(20, true);
2655
2656#if defined(__WXGTK__) || defined(__WXOSX__)
2657 top_frame::Get()->Raise();
2658#endif
2659
2660 // top_frame::Get()->Raise();
2661 // #else
2662 // SetFocus();
2663 // Refresh(true);
2664 // #endif
2665}
2666
2667void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2668 SetFocus();
2669 Refresh(true);
2670}
2671
2672void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2673 if (SendKeyEventToPlugins(event))
2674 return; // PlugIn did something, and does not want the canvas to do
2675 // anything else
2676
2677 int key_char = event.GetKeyCode();
2678 switch (key_char) {
2679 case '?':
2680 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2681 break;
2682 case '+':
2683 ZoomCanvas(g_plus_minus_zoom_factor, false);
2684 break;
2685 case '-':
2686 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2687 break;
2688 default:
2689 break;
2690 }
2691 if (g_benable_rotate) {
2692 switch (key_char) {
2693 case ']':
2694 RotateCanvas(1);
2695 Refresh();
2696 break;
2697
2698 case '[':
2699 RotateCanvas(-1);
2700 Refresh();
2701 break;
2702
2703 case '\\':
2704 DoRotateCanvas(0);
2705 break;
2706 }
2707 }
2708
2709 event.Skip();
2710}
2711
2712void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2713 if (SendKeyEventToPlugins(event))
2714 return; // PlugIn did something, and does not want the canvas to do
2715 // anything else
2716
2717 bool b_handled = false;
2718
2719 m_modkeys = event.GetModifiers();
2720
2721 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2722
2723#ifdef OCPN_ALT_MENUBAR
2724#ifndef __WXOSX__
2725 // If the permanent menubar is disabled, we show it temporarily when Alt is
2726 // pressed or when Alt + a letter is presssed (for the top-menu-level
2727 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2728 // some special cases.
2729 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2730 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2731 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2732 if (!g_bTempShowMenuBar) {
2733 g_bTempShowMenuBar = true;
2734 top_frame::Get()->ApplyGlobalSettings(false);
2735 }
2736 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2737 event.Skip();
2738 return;
2739 }
2740 // If another key is pressed while Alt is down, do NOT toggle the menus when
2741 // Alt is released
2742 if (event.GetKeyCode() != WXK_ALT) {
2743 m_bMayToggleMenuBar = false;
2744 }
2745 }
2746#endif
2747#endif
2748
2749 // HOTKEYS
2750 switch (event.GetKeyCode()) {
2751 case WXK_TAB:
2752 // parent_frame->SwitchKBFocus( this );
2753 break;
2754
2755 case WXK_MENU:
2756 int x, y;
2757 event.GetPosition(&x, &y);
2758 m_FinishRouteOnKillFocus = false;
2759 CallPopupMenu(x, y);
2760 m_FinishRouteOnKillFocus = true;
2761 break;
2762
2763 case WXK_ALT:
2764 m_modkeys |= wxMOD_ALT;
2765 break;
2766
2767 case WXK_CONTROL:
2768 m_modkeys |= wxMOD_CONTROL;
2769 break;
2770
2771#ifdef __WXOSX__
2772 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2773 case WXK_RAW_CONTROL:
2774 m_modkeys |= wxMOD_RAW_CONTROL;
2775 break;
2776#endif
2777
2778 case WXK_LEFT:
2779 if (m_modkeys == wxMOD_CONTROL)
2780 top_frame::Get()->DoStackDown(this);
2781 else if (g_bsmoothpanzoom) {
2782 StartTimedMovement();
2783 m_panx = -1;
2784 } else {
2785 PanCanvas(-panspeed, 0);
2786 }
2787 b_handled = true;
2788 break;
2789
2790 case WXK_UP:
2791 if (g_bsmoothpanzoom) {
2792 StartTimedMovement();
2793 m_pany = -1;
2794 } else
2795 PanCanvas(0, -panspeed);
2796 b_handled = true;
2797 break;
2798
2799 case WXK_RIGHT:
2800 if (m_modkeys == wxMOD_CONTROL)
2801 top_frame::Get()->DoStackUp(this);
2802 else if (g_bsmoothpanzoom) {
2803 StartTimedMovement();
2804 m_panx = 1;
2805 } else
2806 PanCanvas(panspeed, 0);
2807 b_handled = true;
2808
2809 break;
2810
2811 case WXK_DOWN:
2812 if (g_bsmoothpanzoom) {
2813 StartTimedMovement();
2814 m_pany = 1;
2815 } else
2816 PanCanvas(0, panspeed);
2817 b_handled = true;
2818 break;
2819
2820 case WXK_F2: {
2821 if (event.ShiftDown()) {
2822 double scale = GetVP().view_scale_ppm;
2823 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2824
2825 std::shared_ptr<HostApi> host_api;
2826 host_api = GetHostApi();
2827 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2828
2829 if (api_121)
2830 api_121->SelectChartFamily(m_canvasIndex,
2831 (ChartFamilyEnumPI)target_family);
2832 } else
2833 TogglebFollow();
2834 break;
2835 }
2836 case WXK_F3: {
2837 if (event.ShiftDown()) {
2838 double scale = GetVP().view_scale_ppm;
2839 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2840
2841 std::shared_ptr<HostApi> host_api;
2842 host_api = GetHostApi();
2843 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2844
2845 if (api_121)
2846 api_121->SelectChartFamily(m_canvasIndex,
2847 (ChartFamilyEnumPI)target_family);
2848 } else {
2849 SetShowENCText(!GetShowENCText());
2850 Refresh(true);
2851 InvalidateGL();
2852 }
2853 break;
2854 }
2855 case WXK_F4:
2856 if (!m_bMeasure_Active) {
2857 if (event.ShiftDown())
2858 m_bMeasure_DistCircle = true;
2859 else
2860 m_bMeasure_DistCircle = false;
2861
2862 StartMeasureRoute();
2863 } else {
2864 CancelMeasureRoute();
2865
2866 SetCursor(*pCursorArrow);
2867
2868 // SurfaceToolbar();
2869 InvalidateGL();
2870 Refresh(false);
2871 }
2872
2873 break;
2874
2875 case WXK_F5:
2876 top_frame::Get()->ToggleColorScheme();
2877 top_frame::Get()->Raise();
2878 TriggerDeferredFocus();
2879 break;
2880
2881 case WXK_F6: {
2882 int mod = m_modkeys & wxMOD_SHIFT;
2883 if (mod != m_brightmod) {
2884 m_brightmod = mod;
2885 m_bbrightdir = !m_bbrightdir;
2886 }
2887
2888 if (!m_bbrightdir) {
2889 g_nbrightness -= 10;
2890 if (g_nbrightness <= MIN_BRIGHT) {
2891 g_nbrightness = MIN_BRIGHT;
2892 m_bbrightdir = true;
2893 }
2894 } else {
2895 g_nbrightness += 10;
2896 if (g_nbrightness >= MAX_BRIGHT) {
2897 g_nbrightness = MAX_BRIGHT;
2898 m_bbrightdir = false;
2899 }
2900 }
2901
2902 SetScreenBrightness(g_nbrightness);
2903 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2904
2905 SetFocus(); // just in case the external program steals it....
2906 top_frame::Get()->Raise(); // And reactivate the application main
2907
2908 break;
2909 }
2910
2911 case WXK_F7:
2912 top_frame::Get()->DoStackDown(this);
2913 break;
2914
2915 case WXK_F8:
2916 top_frame::Get()->DoStackUp(this);
2917 break;
2918
2919#ifndef __WXOSX__
2920 case WXK_F9: {
2921 ToggleCanvasQuiltMode();
2922 break;
2923 }
2924#endif
2925
2926 case WXK_F11:
2927 top_frame::Get()->ToggleFullScreen();
2928 b_handled = true;
2929 break;
2930
2931 case WXK_F12: {
2932 if (m_modkeys == wxMOD_ALT) {
2933 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2934 } else {
2935 ToggleChartOutlines();
2936 }
2937 break;
2938 }
2939
2940 case WXK_PAUSE: // Drop MOB
2941 top_frame::Get()->ActivateMOB();
2942 break;
2943
2944 // NUMERIC PAD
2945 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2946 case WXK_PAGEUP: {
2947 ZoomCanvas(g_plus_minus_zoom_factor, false);
2948 break;
2949 }
2950 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2951 case WXK_PAGEDOWN: {
2952 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2953 break;
2954 }
2955 case WXK_DELETE:
2956 case WXK_BACK:
2957 if (m_bMeasure_Active) {
2958 if (m_nMeasureState > 2) {
2959 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2960 m_pMeasureRoute->m_lastMousePointIndex =
2961 m_pMeasureRoute->GetnPoints();
2962 m_nMeasureState--;
2963 top_frame::Get()->RefreshAllCanvas();
2964 } else {
2965 CancelMeasureRoute();
2966 StartMeasureRoute();
2967 }
2968 }
2969 break;
2970 default:
2971 break;
2972 }
2973
2974 if (event.GetKeyCode() < 128) // ascii
2975 {
2976 int key_char = event.GetKeyCode();
2977
2978 // Handle both QWERTY and AZERTY keyboard separately for a few control
2979 // codes
2980 if (!g_b_assume_azerty) {
2981#ifdef __WXMAC__
2982 if (g_benable_rotate) {
2983 switch (key_char) {
2984 // On other platforms these are handled in OnKeyChar, which
2985 // (apparently) works better in some locales. On OS X it is better
2986 // to handle them here, since pressing Alt (which should change the
2987 // rotation speed) changes the key char and so prevents the keys
2988 // from working.
2989 case ']':
2990 RotateCanvas(1);
2991 b_handled = true;
2992 break;
2993
2994 case '[':
2995 RotateCanvas(-1);
2996 b_handled = true;
2997 break;
2998
2999 case '\\':
3000 DoRotateCanvas(0);
3001 b_handled = true;
3002 break;
3003 }
3004 }
3005#endif
3006 } else { // AZERTY
3007 switch (key_char) {
3008 case 43:
3009 ZoomCanvas(g_plus_minus_zoom_factor, false);
3010 break;
3011
3012 case 54: // '-' alpha/num pad
3013 // case 56: // '_' alpha/num pad
3014 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3015 break;
3016 }
3017 }
3018
3019#ifdef __WXOSX__
3020 // Ctrl+Cmd+F toggles fullscreen on macOS
3021 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3022 m_modkeys & wxMOD_RAW_CONTROL) {
3023 top_frame::Get()->ToggleFullScreen();
3024 return;
3025 }
3026#endif
3027
3028 if (event.ControlDown()) key_char -= 64;
3029
3030 if (key_char >= '0' && key_char <= '9')
3031 SetGroupIndex(key_char - '0');
3032 else
3033
3034 switch (key_char) {
3035 case 'A':
3036 SetShowENCAnchor(!GetShowENCAnchor());
3037 ReloadVP();
3038
3039 break;
3040
3041 case 'C':
3042 top_frame::Get()->ToggleColorScheme();
3043 break;
3044
3045 case 'D': {
3046 int x, y;
3047 event.GetPosition(&x, &y);
3048 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3049 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3050 // First find out what kind of chart is being used
3051 if (!pPopupDetailSlider) {
3052 if (VPoint.b_quilt) {
3053 if (m_pQuilt) {
3054 if (m_pQuilt->GetChartAtPix(
3055 VPoint,
3056 wxPoint(
3057 x, y))) // = null if no chart loaded for this point
3058 {
3059 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3060 ->GetChartType();
3061 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3062 ->GetChartFamily();
3063 }
3064 }
3065 } else {
3066 if (m_singleChart) {
3067 ChartType = m_singleChart->GetChartType();
3068 ChartFam = m_singleChart->GetChartFamily();
3069 }
3070 }
3071 // If a charttype is found show the popupslider
3072 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3073 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3075 this, -1, ChartType, ChartFam,
3076 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3077 wxDefaultSize, wxSIMPLE_BORDER, "");
3079 }
3080 } else //( !pPopupDetailSlider ) close popupslider
3081 {
3083 pPopupDetailSlider = NULL;
3084 }
3085 break;
3086 }
3087
3088 case 'E':
3089 m_nmea_log->Show();
3090 m_nmea_log->Raise();
3091 break;
3092
3093 case 'L':
3094 SetShowENCLights(!GetShowENCLights());
3095 ReloadVP();
3096
3097 break;
3098
3099 case 'M':
3100 if (event.ShiftDown())
3101 m_bMeasure_DistCircle = true;
3102 else
3103 m_bMeasure_DistCircle = false;
3104
3105 StartMeasureRoute();
3106 break;
3107
3108 case 'N':
3109 if (g_bInlandEcdis && ps52plib) {
3110 SetENCDisplayCategory((_DisCat)STANDARD);
3111 }
3112 break;
3113
3114 case 'O':
3115 ToggleChartOutlines();
3116 break;
3117
3118 case 'Q':
3119 ToggleCanvasQuiltMode();
3120 break;
3121
3122 case 'P':
3123 top_frame::Get()->ToggleTestPause();
3124 break;
3125 case 'R':
3126 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3127 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3128 g_iNavAidRadarRingsNumberVisible = 1;
3129 else if (!g_bNavAidRadarRingsShown &&
3130 g_iNavAidRadarRingsNumberVisible == 1)
3131 g_iNavAidRadarRingsNumberVisible = 0;
3132 break;
3133 case 'S':
3134 SetShowENCDepth(!m_encShowDepth);
3135 ReloadVP();
3136 break;
3137
3138 case 'T':
3139 SetShowENCText(!GetShowENCText());
3140 ReloadVP();
3141 break;
3142
3143 case 'U':
3144 SetShowENCDataQual(!GetShowENCDataQual());
3145 ReloadVP();
3146 break;
3147
3148 case 'V':
3149 m_bShowNavobjects = !m_bShowNavobjects;
3150 Refresh(true);
3151 break;
3152
3153 case 'W': // W Toggle CPA alarm
3154 ToggleCPAWarn();
3155
3156 break;
3157
3158 case 1: // Ctrl A
3159 TogglebFollow();
3160
3161 break;
3162
3163 case 2: // Ctrl B
3164 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3165 break;
3166
3167 case 13: // Ctrl M // Drop Marker at cursor
3168 {
3169 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3170 break;
3171 }
3172
3173 case 14: // Ctrl N - Activate next waypoint in a route
3174 {
3175 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3176 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3177 if ((indexActive + 1) <= r->GetnPoints()) {
3179 InvalidateGL();
3180 Refresh(false);
3181 }
3182 }
3183 break;
3184 }
3185
3186 case 15: // Ctrl O - Drop Marker at boat's position
3187 {
3188 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3189 break;
3190 }
3191
3192 case 32: // Special needs use space bar
3193 {
3194 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3195 break;
3196 }
3197
3198 case -32: // Ctrl Space // Drop MOB
3199 {
3200 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3201
3202 break;
3203 }
3204
3205 case -20: // Ctrl ,
3206 {
3207 top_frame::Get()->DoSettings();
3208 break;
3209 }
3210 case 17: // Ctrl Q
3211 parent_frame->Close();
3212 return;
3213
3214 case 18: // Ctrl R
3215 StartRoute();
3216 return;
3217
3218 case 20: // Ctrl T
3219 if (NULL == pGoToPositionDialog) // There is one global instance of
3220 // the Go To Position Dialog
3222 pGoToPositionDialog->SetCanvas(this);
3223 pGoToPositionDialog->Show();
3224 break;
3225
3226 case 25: // Ctrl Y
3227 if (undo->AnythingToRedo()) {
3228 undo->RedoNextAction();
3229 InvalidateGL();
3230 Refresh(false);
3231 }
3232 break;
3233
3234 case 26:
3235 if (event.ShiftDown()) { // Shift-Ctrl-Z
3236 if (undo->AnythingToRedo()) {
3237 undo->RedoNextAction();
3238 InvalidateGL();
3239 Refresh(false);
3240 }
3241 } else { // Ctrl Z
3242 if (undo->AnythingToUndo()) {
3243 undo->UndoLastAction();
3244 InvalidateGL();
3245 Refresh(false);
3246 }
3247 }
3248 break;
3249
3250 case 27:
3251 // Generic break
3252 if (m_bMeasure_Active) {
3253 CancelMeasureRoute();
3254
3255 SetCursor(*pCursorArrow);
3256
3257 // SurfaceToolbar();
3258 top_frame::Get()->RefreshAllCanvas();
3259 }
3260
3261 if (m_routeState) // creating route?
3262 {
3263 FinishRoute();
3264 // SurfaceToolbar();
3265 InvalidateGL();
3266 Refresh(false);
3267 }
3268
3269 break;
3270
3271 case 7: // Ctrl G
3272 switch (gamma_state) {
3273 case (0):
3274 r_gamma_mult = 0;
3275 g_gamma_mult = 1;
3276 b_gamma_mult = 0;
3277 gamma_state = 1;
3278 break;
3279 case (1):
3280 r_gamma_mult = 1;
3281 g_gamma_mult = 0;
3282 b_gamma_mult = 0;
3283 gamma_state = 2;
3284 break;
3285 case (2):
3286 r_gamma_mult = 1;
3287 g_gamma_mult = 1;
3288 b_gamma_mult = 1;
3289 gamma_state = 0;
3290 break;
3291 }
3292 SetScreenBrightness(g_nbrightness);
3293
3294 break;
3295
3296 case 9: // Ctrl I
3297 if (event.ControlDown()) {
3298 m_bShowCompassWin = !m_bShowCompassWin;
3299 SetShowGPSCompassWindow(m_bShowCompassWin);
3300 Refresh(false);
3301 }
3302 break;
3303
3304 default:
3305 break;
3306
3307 } // switch
3308 }
3309
3310 // Allow OnKeyChar to catch the key events too.
3311 if (!b_handled) {
3312 event.Skip();
3313 }
3314}
3315
3316void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3317 if (SendKeyEventToPlugins(event))
3318 return; // PlugIn did something, and does not want the canvas to do
3319 // anything else
3320
3321 switch (event.GetKeyCode()) {
3322 case WXK_TAB:
3323 top_frame::Get()->SwitchKBFocus(this);
3324 break;
3325
3326 case WXK_LEFT:
3327 case WXK_RIGHT:
3328 m_panx = 0;
3329 if (!m_pany) m_panspeed = 0;
3330 break;
3331
3332 case WXK_UP:
3333 case WXK_DOWN:
3334 m_pany = 0;
3335 if (!m_panx) m_panspeed = 0;
3336 break;
3337
3338 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3339 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3340 case WXK_PAGEUP:
3341 case WXK_PAGEDOWN:
3342 if (m_mustmove) DoMovement(m_mustmove);
3343
3344 m_zoom_factor = 1;
3345 break;
3346
3347 case WXK_ALT:
3348 m_modkeys &= ~wxMOD_ALT;
3349#ifdef OCPN_ALT_MENUBAR
3350#ifndef __WXOSX__
3351 // If the permanent menu bar is disabled, and we are not in the middle of
3352 // another key combo, then show the menu bar temporarily when Alt is
3353 // released (or hide it if already visible).
3354 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3355 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3356 top_frame::Get()->ApplyGlobalSettings(false);
3357 }
3358 m_bMayToggleMenuBar = true;
3359#endif
3360#endif
3361 break;
3362
3363 case WXK_CONTROL:
3364 m_modkeys &= ~wxMOD_CONTROL;
3365 break;
3366 }
3367
3368 if (event.GetKeyCode() < 128) // ascii
3369 {
3370 int key_char = event.GetKeyCode();
3371
3372 // Handle both QWERTY and AZERTY keyboard separately for a few control
3373 // codes
3374 if (!g_b_assume_azerty) {
3375 switch (key_char) {
3376 case '+':
3377 case '=':
3378 case '-':
3379 case '_':
3380 case 54:
3381 case 56: // '_' alpha/num pad
3382 DoMovement(m_mustmove);
3383
3384 // m_zoom_factor = 1;
3385 break;
3386 case '[':
3387 case ']':
3388 DoMovement(m_mustmove);
3389 m_rotation_speed = 0;
3390 break;
3391 }
3392 } else {
3393 switch (key_char) {
3394 case 43:
3395 case 54: // '-' alpha/num pad
3396 case 56: // '_' alpha/num pad
3397 DoMovement(m_mustmove);
3398
3399 m_zoom_factor = 1;
3400 break;
3401 }
3402 }
3403 }
3404 event.Skip();
3405}
3406
3407void ChartCanvas::ToggleChartOutlines() {
3408 m_bShowOutlines = !m_bShowOutlines;
3409
3410 Refresh(false);
3411
3412#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3413 // needs a full refresh
3414 if (g_bopengl) InvalidateGL();
3415#endif
3416}
3417
3418void ChartCanvas::ToggleLookahead() {
3419 m_bLookAhead = !m_bLookAhead;
3420 m_OSoffsetx = 0; // center ownship
3421 m_OSoffsety = 0;
3422}
3423
3424void ChartCanvas::SetUpMode(int mode) {
3425 m_upMode = mode;
3426
3427 if (mode != NORTH_UP_MODE) {
3428 // Stuff the COGAvg table in case COGUp is selected
3429 double stuff = 0;
3430 if (!std::isnan(gCog)) stuff = gCog;
3431
3432 if (g_COGAvgSec > 0) {
3433 auto cog_table = top_frame::Get()->GetCOGTable();
3434 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3435 }
3436 g_COGAvg = stuff;
3437 top_frame::Get()->StartCogTimer();
3438 } else {
3439 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3440 SetVPRotation(GetVPSkew());
3441 else
3442 SetVPRotation(0); /* reset to north up */
3443 }
3444
3445 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3446 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3447
3448 UpdateGPSCompassStatusBox(true);
3449 top_frame::Get()->DoChartUpdate();
3450}
3451
3452bool ChartCanvas::DoCanvasCOGSet() {
3453 if (GetUpMode() == NORTH_UP_MODE) return false;
3454 double cog_use = g_COGAvg;
3455 if (g_btenhertz) cog_use = gCog;
3456
3457 double rotation = 0;
3458 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3459 rotation = -gHdt * PI / 180.;
3460 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3461 rotation = -cog_use * PI / 180.;
3462
3463 SetVPRotation(rotation);
3464 return true;
3465}
3466
3467double easeOutCubic(double t) {
3468 // Starts quickly and slows down toward the end
3469 return 1.0 - pow(1.0 - t, 3.0);
3470}
3471
3472void ChartCanvas::StartChartDragInertia() {
3473 m_bChartDragging = false;
3474
3475 // Set some parameters
3476 m_chart_drag_inertia_time = 750; // msec
3477 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3478 m_last_elapsed = 0;
3479
3480 // Calculate ending drag velocity
3481 size_t n_vel = 10;
3482 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3483 int xacc = 0;
3484 int yacc = 0;
3485 double tacc = 0;
3486 size_t length = m_drag_vec_t.size();
3487 for (size_t i = 0; i < n_vel; i++) {
3488 xacc += m_drag_vec_x.at(length - 1 - i);
3489 yacc += m_drag_vec_y.at(length - 1 - i);
3490 tacc += m_drag_vec_t.at(length - 1 - i);
3491 }
3492
3493 if (tacc == 0) return;
3494
3495 double drag_velocity_x = xacc / tacc;
3496 double drag_velocity_y = yacc / tacc;
3497 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3498 // drag_velocity_y);
3499
3500 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3501 // touch tap.
3502 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3503
3504 m_chart_drag_velocity_x = drag_velocity_x;
3505 m_chart_drag_velocity_y = drag_velocity_y;
3506
3507 m_chart_drag_inertia_active = true;
3508 // First callback as fast as possible.
3509 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3510}
3511
3512void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3513 if (!m_chart_drag_inertia_active) return;
3514 // Calculate time fraction from 0..1
3515 wxLongLong now = wxGetLocalTimeMillis();
3516 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3517 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3518 if (t > 1.0) t = 1.0;
3519 double e = 1.0 - easeOutCubic(t); // 0..1
3520
3521 double dx =
3522 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3523 double dy =
3524 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3525
3526 m_last_elapsed = elapsed;
3527
3528 // Ensure that target destination lies on whole-pixel boundary
3529 // This allows the render engine to use a faster FBO copy method for drawing
3530 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3531 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3532 double inertia_lat, inertia_lon;
3533 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3534 double prev_clat = VPoint.clat;
3535 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3536 // Stop inertia if we hit a latitude clamp (viewport edge at limit)
3537 if (fabs(VPoint.clat - prev_clat) < 1e-9 && fabs(dy) > 1) {
3538 m_chart_drag_inertia_timer.Stop();
3539 m_chart_drag_inertia_active = false;
3540 DoCanvasUpdate();
3541 return;
3542 }
3543 // Check if ownship has moved off-screen
3544 if (!IsOwnshipOnScreen()) {
3545 m_bFollow = false; // update the follow flag
3546 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3547 UpdateFollowButtonState();
3548 m_OSoffsetx = 0;
3549 m_OSoffsety = 0;
3550 } else {
3551 m_OSoffsetx += dx;
3552 m_OSoffsety -= dy;
3553 }
3554
3555 Refresh(false);
3556
3557 // Stop condition
3558 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3559 m_chart_drag_inertia_timer.Stop();
3560
3561 // Disable chart pan movement logic
3562 m_target_lat = GetVP().clat;
3563 m_target_lon = GetVP().clon;
3564 m_pan_drag.x = m_pan_drag.y = 0;
3565 m_panx = m_pany = 0;
3566 m_chart_drag_inertia_active = false;
3567 DoCanvasUpdate();
3568
3569 } else {
3570 int target_redraw_interval = 40; // msec
3571 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3572 }
3573}
3574
3575void ChartCanvas::StopMovement() {
3576 m_panx = m_pany = 0;
3577 m_panspeed = 0;
3578 m_zoom_factor = 1;
3579 m_rotation_speed = 0;
3580 m_mustmove = 0;
3581#if 0
3582#if !defined(__WXGTK__) && !defined(__WXQT__)
3583 SetFocus();
3584 top_frame::Get()->Raise();
3585#endif
3586#endif
3587}
3588
3589/* instead of integrating in timer callbacks
3590 (which do not always get called fast enough)
3591 we can perform the integration of movement
3592 at each render frame based on the time change */
3593bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3594 // Start/restart the stop movement timer
3595 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3596
3597 if (!pMovementTimer->IsRunning()) {
3598 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3599 }
3600
3601 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3602 // already moving, gets called again because of key-repeat event
3603 return false;
3604 }
3605
3606 m_last_movement_time = wxDateTime::UNow();
3607
3608 return true;
3609}
3610void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3611 int nstep) {
3612 // Save the target
3613 m_target_lat = target_lat;
3614 m_target_lon = target_lon;
3615
3616 // Save the start point
3617 m_start_lat = GetVP().clat;
3618 m_start_lon = GetVP().clon;
3619
3620 m_VPMovementTimer.Start(1, true); // oneshot
3621 m_timed_move_vp_active = true;
3622 m_stvpc = 0;
3623 m_timedVP_step = nstep;
3624}
3625
3626void ChartCanvas::DoTimedMovementVP() {
3627 if (!m_timed_move_vp_active) return; // not active
3628 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3629 StopMovement();
3630 return;
3631 }
3632 // Stop condition
3633 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3634 double d2 =
3635 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3636 d2 = pow(d2, 0.5);
3637
3638 if (d2 < one_pix) {
3639 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3640 StopMovementVP();
3641 return;
3642 }
3643
3644 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3645 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3646 // StopMovementVP();
3647 // return;
3648 // }
3649
3650 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3651 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3652
3653 m_run_lat = new_lat;
3654 m_run_lon = new_lon;
3655
3656 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3657}
3658
3659void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3660
3661void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3662
3663void ChartCanvas::StartTimedMovementTarget() {}
3664
3665void ChartCanvas::DoTimedMovementTarget() {}
3666
3667void ChartCanvas::StopMovementTarget() {}
3668int ntm;
3669
3670void ChartCanvas::DoTimedMovement() {
3671 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3672 !m_rotation_speed)
3673 return; /* not moving */
3674
3675 wxDateTime now = wxDateTime::UNow();
3676 long dt = 0;
3677 if (m_last_movement_time.IsValid())
3678 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3679
3680 m_last_movement_time = now;
3681
3682 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3683 dt = 500;
3684
3685 DoMovement(dt);
3686}
3687
3689 /* if we get here quickly assume 1ms so that some movement occurs */
3690 if (dt == 0) dt = 1;
3691
3692 m_mustmove -= dt;
3693 if (m_mustmove < 0) m_mustmove = 0;
3694
3695 if (!m_inPinch) { // this stops compound zoom/pan
3696 if (m_pan_drag.x || m_pan_drag.y) {
3697 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3698 m_pan_drag.x = m_pan_drag.y = 0;
3699 }
3700
3701 if (m_panx || m_pany) {
3702 const double slowpan = .1, maxpan = 2;
3703 if (m_modkeys == wxMOD_ALT)
3704 m_panspeed = slowpan;
3705 else {
3706 m_panspeed += (double)dt / 500; /* apply acceleration */
3707 m_panspeed = wxMin(maxpan, m_panspeed);
3708 }
3709 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3710 }
3711 }
3712 if (m_zoom_factor != 1) {
3713 double alpha = 400, beta = 1.5;
3714 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3715
3716 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3717
3718 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3719
3720 // Try to hit the zoom target exactly.
3721 // if(m_wheelzoom_stop_oneshot > 0)
3722 {
3723 if (zoom_factor > 1) {
3724 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3725 zoom_factor = VPoint.chart_scale / m_zoom_target;
3726 }
3727
3728 else if (zoom_factor < 1) {
3729 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3730 zoom_factor = VPoint.chart_scale / m_zoom_target;
3731
3732 // Enforce clamp on absolute zoom-out level
3733 if ((GetVPScale()) < (m_absolute_min_scale_ppm * 1.00001))
3734 zoom_factor = 1;
3735 }
3736 }
3737
3738 if (fabs(zoom_factor - 1) > 1e-4) {
3739 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3740 } else {
3741 StopMovement();
3742 }
3743
3744 if (m_wheelzoom_stop_oneshot > 0) {
3745 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3746 m_wheelzoom_stop_oneshot = 0;
3747 StopMovement();
3748 }
3749
3750 // Don't overshoot the zoom target.
3751 if (zoom_factor > 1) {
3752 if (VPoint.chart_scale <= m_zoom_target) {
3753 m_wheelzoom_stop_oneshot = 0;
3754 StopMovement();
3755 }
3756 } else if (zoom_factor < 1) {
3757 if (VPoint.chart_scale >= m_zoom_target) {
3758 m_wheelzoom_stop_oneshot = 0;
3759 StopMovement();
3760 }
3761 }
3762 }
3763 }
3764
3765 if (m_rotation_speed) { /* in degrees per second */
3766 double speed = m_rotation_speed;
3767 if (m_modkeys == wxMOD_ALT) speed /= 10;
3768 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3769 }
3770}
3771
3772void ChartCanvas::SetColorScheme(ColorScheme cs) {
3773 SetAlertString("");
3774
3775 // Setup ownship image pointers
3776 switch (cs) {
3777 case GLOBAL_COLOR_SCHEME_DAY:
3778 m_pos_image_red = &m_os_image_red_day;
3779 m_pos_image_grey = &m_os_image_grey_day;
3780 m_pos_image_yellow = &m_os_image_yellow_day;
3781 m_pos_image_user = m_pos_image_user_day;
3782 m_pos_image_user_grey = m_pos_image_user_grey_day;
3783 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3784 m_cTideBitmap = m_bmTideDay;
3785 m_cCurrentBitmap = m_bmCurrentDay;
3786
3787 break;
3788 case GLOBAL_COLOR_SCHEME_DUSK:
3789 m_pos_image_red = &m_os_image_red_dusk;
3790 m_pos_image_grey = &m_os_image_grey_dusk;
3791 m_pos_image_yellow = &m_os_image_yellow_dusk;
3792 m_pos_image_user = m_pos_image_user_dusk;
3793 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3794 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3795 m_cTideBitmap = m_bmTideDusk;
3796 m_cCurrentBitmap = m_bmCurrentDusk;
3797 break;
3798 case GLOBAL_COLOR_SCHEME_NIGHT:
3799 m_pos_image_red = &m_os_image_red_night;
3800 m_pos_image_grey = &m_os_image_grey_night;
3801 m_pos_image_yellow = &m_os_image_yellow_night;
3802 m_pos_image_user = m_pos_image_user_night;
3803 m_pos_image_user_grey = m_pos_image_user_grey_night;
3804 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3805 m_cTideBitmap = m_bmTideNight;
3806 m_cCurrentBitmap = m_bmCurrentNight;
3807 break;
3808 default:
3809 m_pos_image_red = &m_os_image_red_day;
3810 m_pos_image_grey = &m_os_image_grey_day;
3811 m_pos_image_yellow = &m_os_image_yellow_day;
3812 m_pos_image_user = m_pos_image_user_day;
3813 m_pos_image_user_grey = m_pos_image_user_grey_day;
3814 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3815 m_cTideBitmap = m_bmTideDay;
3816 m_cCurrentBitmap = m_bmCurrentDay;
3817 break;
3818 }
3819
3820 CreateDepthUnitEmbossMaps(cs);
3821 CreateOZEmbossMapData(cs);
3822
3823 // Set up fog effect base color
3824 m_fog_color = wxColor(
3825 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3826 float dim = 1.0;
3827 switch (cs) {
3828 case GLOBAL_COLOR_SCHEME_DUSK:
3829 dim = 0.5;
3830 break;
3831 case GLOBAL_COLOR_SCHEME_NIGHT:
3832 dim = 0.25;
3833 break;
3834 default:
3835 break;
3836 }
3837 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3838 m_fog_color.Blue() * dim);
3839
3840 // Really dark
3841#if 0
3842 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3843 SetBackgroundColour( wxColour(0,0,0) );
3844
3845 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3846 }
3847 else{
3848 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3849#ifndef __WXMAC__
3850 SetBackgroundColour( wxNullColour );
3851#endif
3852 }
3853#endif
3854
3855 // UpdateToolbarColorScheme(cs);
3856
3857 m_Piano->SetColorScheme(cs);
3858
3859 m_Compass->SetColorScheme(cs);
3860
3861 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3862
3863 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3864
3865 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3866 if (m_notification_button) {
3867 m_notification_button->SetColorScheme(cs);
3868 }
3869
3870#ifdef ocpnUSE_GL
3871 if (g_bopengl && m_glcc) {
3872 m_glcc->SetColorScheme(cs);
3873 g_glTextureManager->ClearAllRasterTextures();
3874 // m_glcc->FlushFBO();
3875 }
3876#endif
3877 SetbTCUpdate(true); // force re-render of tide/current locators
3878 m_brepaint_piano = true;
3879
3880 ReloadVP();
3881
3882 m_cs = cs;
3883}
3884
3885wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3886 wxImage img = Bitmap.ConvertToImage();
3887 int sx = img.GetWidth();
3888 int sy = img.GetHeight();
3889
3890 wxImage new_img(img);
3891
3892 for (int i = 0; i < sx; i++) {
3893 for (int j = 0; j < sy; j++) {
3894 if (!img.IsTransparent(i, j)) {
3895 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3896 (unsigned char)(img.GetGreen(i, j) * factor),
3897 (unsigned char)(img.GetBlue(i, j) * factor));
3898 }
3899 }
3900 }
3901
3902 wxBitmap ret = wxBitmap(new_img);
3903
3904 return ret;
3905}
3906
3907void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3908 int max) {
3909 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3910 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3911
3912 if (!m_pBrightPopup) {
3913 // Calculate size
3914 int x, y;
3915 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3916
3917 m_pBrightPopup = new TimedPopupWin(this, 3);
3918
3919 m_pBrightPopup->SetSize(x, y);
3920 m_pBrightPopup->Move(120, 120);
3921 }
3922
3923 int bmpsx = m_pBrightPopup->GetSize().x;
3924 int bmpsy = m_pBrightPopup->GetSize().y;
3925
3926 wxBitmap bmp(bmpsx, bmpsx);
3927 wxMemoryDC mdc(bmp);
3928
3929 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3930 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3931 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3932 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3933 mdc.Clear();
3934
3935 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3936
3937 mdc.SetFont(*pfont);
3938 wxString val;
3939
3940 if (brightness == max)
3941 val = "MAX";
3942 else if (brightness == min)
3943 val = "MIN";
3944 else
3945 val.Printf("%3d", brightness);
3946
3947 mdc.DrawText(val, 0, 0);
3948
3949 mdc.SelectObject(wxNullBitmap);
3950
3951 m_pBrightPopup->SetBitmap(bmp);
3952 m_pBrightPopup->Show();
3953 m_pBrightPopup->Refresh();
3954}
3955
3956void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3957 m_b_rot_hidef = true;
3958 ReloadVP();
3959}
3960
3961void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3962 if (!g_bRollover) return;
3963
3964 bool b_need_refresh = false;
3965
3966 wxSize win_size = GetSize() * m_displayScale;
3967 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3968
3969 // Handle the AIS Rollover Window first
3970 bool showAISRollover = false;
3971 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3972 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3973 SelectItem *pFind = pSelectAIS->FindSelection(
3974 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3975 if (pFind) {
3976 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3977 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3978
3979 if (ptarget) {
3980 showAISRollover = true;
3981
3982 if (NULL == m_pAISRolloverWin) {
3983 m_pAISRolloverWin = new RolloverWin(this);
3984 m_pAISRolloverWin->IsActive(false);
3985 b_need_refresh = true;
3986 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3987 m_AISRollover_MMSI != FoundAIS_MMSI) {
3988 // Sometimes the mouse moves fast enough to get over a new AIS
3989 // target before the one-shot has fired to remove the old target.
3990 // Result: wrong target data is shown.
3991 // Detect this case,close the existing rollover ASAP, and restart
3992 // the timer.
3993 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3994 m_pAISRolloverWin->IsActive(false);
3995 m_AISRollover_MMSI = 0;
3996 Refresh();
3997 return;
3998 }
3999
4000 m_AISRollover_MMSI = FoundAIS_MMSI;
4001
4002 if (!m_pAISRolloverWin->IsActive()) {
4003 wxString s = ptarget->GetRolloverString();
4004 m_pAISRolloverWin->SetString(s);
4005
4006 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4007 AIS_ROLLOVER, win_size);
4008 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
4009 m_pAISRolloverWin->IsActive(true);
4010 b_need_refresh = true;
4011 }
4012 }
4013 } else {
4014 m_AISRollover_MMSI = 0;
4015 showAISRollover = false;
4016 }
4017 }
4018
4019 // Maybe turn the rollover off
4020 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
4021 m_pAISRolloverWin->IsActive(false);
4022 m_AISRollover_MMSI = 0;
4023 b_need_refresh = true;
4024 }
4025
4026 // Now the Route info rollover
4027 // Show the route segment info
4028 bool showRouteRollover = false;
4029
4030 if (NULL == m_pRolloverRouteSeg) {
4031 // Get a list of all selectable sgements, and search for the first
4032 // visible segment as the rollover target.
4033
4034 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4035 SelectableItemList SelList = pSelect->FindSelectionList(
4036 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4037 auto node = SelList.begin();
4038 while (node != SelList.end()) {
4039 SelectItem *pFindSel = *node;
4040
4041 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4042
4043 if (pr && pr->IsVisible()) {
4044 m_pRolloverRouteSeg = pFindSel;
4045 showRouteRollover = true;
4046
4047 if (NULL == m_pRouteRolloverWin) {
4048 m_pRouteRolloverWin = new RolloverWin(this, 10);
4049 m_pRouteRolloverWin->IsActive(false);
4050 }
4051
4052 if (!m_pRouteRolloverWin->IsActive()) {
4053 wxString s;
4054 RoutePoint *segShow_point_a =
4055 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4056 RoutePoint *segShow_point_b =
4057 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4058
4059 // Minimally validate routepoint pointers
4060 int ia = pr->GetIndexOf(segShow_point_a);
4061 int ib = pr->GetIndexOf(segShow_point_b);
4062 if (ia < 0 || ib < 0) {
4063 wxLogMessage("Ignoring stale route segment selection.");
4064 return;
4065 }
4066
4067 double brg, dist;
4068 DistanceBearingMercator(
4069 segShow_point_b->m_lat, segShow_point_b->m_lon,
4070 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4071
4072 if (!pr->m_bIsInLayer)
4073 s.Append(_("Route") + ": ");
4074 else
4075 s.Append(_("Layer Route: "));
4076
4077 if (pr->m_RouteNameString.IsEmpty())
4078 s.Append(_("(unnamed)"));
4079 else
4080 s.Append(pr->m_RouteNameString);
4081
4082 s << "\n"
4083 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4084 << "\n"
4085 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4086 << segShow_point_b->GetName() << "\n";
4087
4088 if (g_bShowTrue)
4089 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4090 (int)floor(brg + 0.5), 0x00B0);
4091 if (g_bShowMag) {
4092 double latAverage =
4093 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4094 double lonAverage =
4095 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4096 double varBrg =
4097 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4098
4099 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4100 (int)floor(varBrg + 0.5), 0x00B0);
4101 }
4102
4103 s << FormatDistanceAdaptive(dist);
4104
4105 // Compute and display cumulative distance from route start point to
4106 // current leg end point and RNG,TTG,ETA from ship to current leg end
4107 // point for active route
4108 double shiptoEndLeg = 0.;
4109 bool validActive = false;
4110 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4111 validActive = true;
4112
4113 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4114 auto node = pr->pRoutePointList->begin();
4115 RoutePoint *prp;
4116 float dist_to_endleg = 0;
4117 wxString t;
4118
4119 for (++node; node != pr->pRoutePointList->end(); ++node) {
4120 prp = *node;
4121 if (validActive)
4122 shiptoEndLeg += prp->m_seg_len;
4123 else if (prp->m_bIsActive)
4124 validActive = true;
4125 dist_to_endleg += prp->m_seg_len;
4126 if (prp->IsSame(segShow_point_a)) break;
4127 }
4128 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4129 }
4130 // write from ship to end selected leg point data if the route is
4131 // active
4132 if (validActive) {
4133 s << "\n"
4134 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4135 shiptoEndLeg +=
4137 ->GetCurrentRngToActivePoint(); // add distance from ship
4138 // to active point
4139 shiptoEndLeg +=
4140 segShow_point_b
4141 ->m_seg_len; // add the lenght of the selected leg
4142 s << FormatDistanceAdaptive(shiptoEndLeg);
4143 // ensure sog/cog are valid and vmg is positive to keep data
4144 // coherent
4145 double vmg = 0.;
4146 if (!std::isnan(gCog) && !std::isnan(gSog))
4147 vmg = gSog *
4148 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4149 PI / 180.);
4150 if (vmg > 0.) {
4151 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4152 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4153 s << " - "
4154 << wxString(ttg_sec > SECONDS_PER_DAY
4155 ? ttg_span.Format(_("%Dd %H:%M"))
4156 : ttg_span.Format(_("%H:%M")));
4157 wxDateTime dtnow, eta;
4158 eta = dtnow.SetToCurrent().Add(ttg_span);
4159 s << " - " << eta.Format("%b").Mid(0, 4)
4160 << eta.Format(" %d %H:%M");
4161 } else
4162 s << " ---- ----";
4163 }
4164 m_pRouteRolloverWin->SetString(s);
4165
4166 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4167 LEG_ROLLOVER, win_size);
4168 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4169 m_pRouteRolloverWin->IsActive(true);
4170 b_need_refresh = true;
4171 showRouteRollover = true;
4172 break;
4173 }
4174 } else {
4175 ++node;
4176 }
4177 }
4178 } else {
4179 // Is the cursor still in select radius, and not timed out?
4180 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4181 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4182 m_pRolloverRouteSeg))
4183 showRouteRollover = false;
4184 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4185 showRouteRollover = false;
4186 else
4187 showRouteRollover = true;
4188 }
4189
4190 // If currently creating a route, do not show this rollover window
4191 if (m_routeState) showRouteRollover = false;
4192
4193 // Similar for AIS target rollover window
4194 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4195 showRouteRollover = false;
4196
4197 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4198 !showRouteRollover) {
4199 m_pRouteRolloverWin->IsActive(false);
4200 m_pRolloverRouteSeg = NULL;
4201 m_pRouteRolloverWin->Destroy();
4202 m_pRouteRolloverWin = NULL;
4203 b_need_refresh = true;
4204 } else if (m_pRouteRolloverWin && showRouteRollover) {
4205 m_pRouteRolloverWin->IsActive(true);
4206 b_need_refresh = true;
4207 }
4208
4209 // Now the Track info rollover
4210 // Show the track segment info
4211 bool showTrackRollover = false;
4212
4213 if (NULL == m_pRolloverTrackSeg) {
4214 // Get a list of all selectable sgements, and search for the first
4215 // visible segment as the rollover target.
4216
4217 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4218 SelectableItemList SelList = pSelect->FindSelectionList(
4219 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4220
4221 auto node = SelList.begin();
4222 while (node != SelList.end()) {
4223 SelectItem *pFindSel = *node;
4224
4225 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4226
4227 if (pt && pt->IsVisible()) {
4228 m_pRolloverTrackSeg = pFindSel;
4229 showTrackRollover = true;
4230
4231 if (NULL == m_pTrackRolloverWin) {
4232 m_pTrackRolloverWin = new RolloverWin(this, 10);
4233 m_pTrackRolloverWin->IsActive(false);
4234 }
4235
4236 if (!m_pTrackRolloverWin->IsActive()) {
4237 wxString s;
4238 TrackPoint *segShow_point_a =
4239 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4240 TrackPoint *segShow_point_b =
4241 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4242
4243 double brg, dist;
4244 DistanceBearingMercator(
4245 segShow_point_b->m_lat, segShow_point_b->m_lon,
4246 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4247
4248 if (!pt->m_bIsInLayer)
4249 s.Append(_("Track") + ": ");
4250 else
4251 s.Append(_("Layer Track: "));
4252
4253 if (pt->GetName().IsEmpty())
4254 s.Append(_("(unnamed)"));
4255 else
4256 s.Append(pt->GetName());
4257 double tlenght = pt->Length();
4258 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4259 if (pt->GetLastPoint()->GetTimeString() &&
4260 pt->GetPoint(0)->GetTimeString()) {
4261 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4262 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4263 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4264 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4265 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4266 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4267 << getUsrSpeedUnit();
4268 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4269 : ttime.Format(" %H:%M"));
4270 }
4271 }
4272
4273 if (g_bShowTrackPointTime &&
4274 strlen(segShow_point_b->GetTimeString())) {
4275 wxString stamp = segShow_point_b->GetTimeString();
4276 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4277 if (timestamp.IsValid()) {
4278 // Format track rollover timestamp to OCPN global TZ setting
4281 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4282 }
4283 s << "\n" << _("Segment Created: ") << stamp;
4284 }
4285
4286 s << "\n";
4287 if (g_bShowTrue)
4288 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4289 0x00B0);
4290
4291 if (g_bShowMag) {
4292 double latAverage =
4293 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4294 double lonAverage =
4295 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4296 double varBrg =
4297 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4298
4299 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4300 0x00B0);
4301 }
4302
4303 s << FormatDistanceAdaptive(dist);
4304
4305 if (segShow_point_a->GetTimeString() &&
4306 segShow_point_b->GetTimeString()) {
4307 wxDateTime apoint = segShow_point_a->GetCreateTime();
4308 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4309 if (apoint.IsValid() && bpoint.IsValid()) {
4310 double segmentSpeed = toUsrSpeed(
4311 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4312 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4313 << getUsrSpeedUnit();
4314 }
4315 }
4316
4317 m_pTrackRolloverWin->SetString(s);
4318
4319 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4320 LEG_ROLLOVER, win_size);
4321 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4322 m_pTrackRolloverWin->IsActive(true);
4323 b_need_refresh = true;
4324 showTrackRollover = true;
4325 break;
4326 }
4327 } else {
4328 ++node;
4329 }
4330 }
4331 } else {
4332 // Is the cursor still in select radius, and not timed out?
4333 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4334 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4335 m_pRolloverTrackSeg))
4336 showTrackRollover = false;
4337 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4338 showTrackRollover = false;
4339 else
4340 showTrackRollover = true;
4341 }
4342
4343 // Similar for AIS target rollover window
4344 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4345 showTrackRollover = false;
4346
4347 // Similar for route rollover window
4348 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4349 showTrackRollover = false;
4350
4351 // TODO We onlt show tracks on primary canvas....
4352 // if(!IsPrimaryCanvas())
4353 // showTrackRollover = false;
4354
4355 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4356 !showTrackRollover) {
4357 m_pTrackRolloverWin->IsActive(false);
4358 m_pRolloverTrackSeg = NULL;
4359 m_pTrackRolloverWin->Destroy();
4360 m_pTrackRolloverWin = NULL;
4361 b_need_refresh = true;
4362 } else if (m_pTrackRolloverWin && showTrackRollover) {
4363 m_pTrackRolloverWin->IsActive(true);
4364 b_need_refresh = true;
4365 }
4366
4367 if (b_need_refresh) Refresh();
4368}
4369
4370void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4371 if ((GetShowENCLights() || m_bsectors_shown) &&
4372 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4373 extendedSectorLegs)) {
4374 if (!m_bsectors_shown) {
4375 ReloadVP(false);
4376 m_bsectors_shown = true;
4377 }
4378 } else {
4379 if (m_bsectors_shown) {
4380 ReloadVP(false);
4381 m_bsectors_shown = false;
4382 }
4383 }
4384
4385// This is here because GTK status window update is expensive..
4386// cairo using pango rebuilds the font every time so is very
4387// inefficient
4388// Anyway, only update the status bar when this timer expires
4389#if defined(__WXGTK__) || defined(__WXQT__)
4390 {
4391 // Check the absolute range of the cursor position
4392 // There could be a window wherein the chart geoereferencing is not
4393 // valid....
4394 double cursor_lat, cursor_lon;
4395 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4396
4397 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4398 while (cursor_lon < -180.) cursor_lon += 360.;
4399
4400 while (cursor_lon > 180.) cursor_lon -= 360.;
4401
4402 SetCursorStatus(cursor_lat, cursor_lon);
4403 }
4404 }
4405#endif
4406}
4407
4408void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4409 if (!top_frame::Get()->GetFrameStatusBar()) return;
4410
4411 wxString s1;
4412 s1 += " ";
4413 s1 += toSDMM(1, cursor_lat);
4414 s1 += " ";
4415 s1 += toSDMM(2, cursor_lon);
4416
4417 if (STAT_FIELD_CURSOR_LL >= 0)
4418 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4419
4420 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4421
4422 double brg, dist;
4423 wxString sm;
4424 wxString st;
4425 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4426 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4427 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4428
4429 wxString s = st + sm;
4430 s << FormatDistanceAdaptive(dist);
4431
4432 // CUSTOMIZATION - LIVE ETA OPTION
4433 // -------------------------------------------------------
4434 // Calculate an "live" ETA based on route starting from the current
4435 // position of the boat and goes to the cursor of the mouse.
4436 // In any case, an standard ETA will be calculated with a default speed
4437 // of the boat to give an estimation of the route (in particular if GPS
4438 // is off).
4439
4440 // Display only if option "live ETA" is selected in Settings > Display >
4441 // General.
4442 if (g_bShowLiveETA) {
4443 float realTimeETA;
4444 float boatSpeed;
4445 float boatSpeedDefault = g_defaultBoatSpeed;
4446
4447 // Calculate Estimate Time to Arrival (ETA) in minutes
4448 // Check before is value not closed to zero (it will make an very big
4449 // number...)
4450 if (!std::isnan(gSog)) {
4451 boatSpeed = gSog;
4452 if (boatSpeed < 0.5) {
4453 realTimeETA = 0;
4454 } else {
4455 realTimeETA = dist / boatSpeed * 60;
4456 }
4457 } else {
4458 realTimeETA = 0;
4459 }
4460
4461 // Add space after distance display
4462 s << " ";
4463 // Display ETA
4464 s << minutesToHoursDays(realTimeETA);
4465
4466 // In any case, display also an ETA with default speed at 6knts
4467
4468 s << " [@";
4469 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4470 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4471 s << " ";
4472 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4473 s << "]";
4474 }
4475 // END OF - LIVE ETA OPTION
4476
4477 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4478}
4479
4480// CUSTOMIZATION - FORMAT MINUTES
4481// -------------------------------------------------------
4482// New function to format minutes into a more readable format:
4483// * Hours + minutes, or
4484// * Days + hours.
4485wxString minutesToHoursDays(float timeInMinutes) {
4486 wxString s;
4487
4488 if (timeInMinutes == 0) {
4489 s << "--min";
4490 }
4491
4492 // Less than 60min, keep time in minutes
4493 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4494 s << wxString::Format("%d", (int)timeInMinutes);
4495 s << "min";
4496 }
4497
4498 // Between 1h and less than 24h, display time in hours, minutes
4499 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4500 int hours;
4501 int min;
4502 hours = (int)timeInMinutes / 60;
4503 min = (int)timeInMinutes % 60;
4504
4505 if (min == 0) {
4506 s << wxString::Format("%d", hours);
4507 s << "h";
4508 } else {
4509 s << wxString::Format("%d", hours);
4510 s << "h";
4511 s << wxString::Format("%d", min);
4512 s << "min";
4513 }
4514
4515 }
4516
4517 // More than 24h, display time in days, hours
4518 else if (timeInMinutes > 24 * 60) {
4519 int days;
4520 int hours;
4521 days = (int)(timeInMinutes / 60) / 24;
4522 hours = (int)(timeInMinutes / 60) % 24;
4523
4524 if (hours == 0) {
4525 s << wxString::Format("%d", days);
4526 s << "d";
4527 } else {
4528 s << wxString::Format("%d", days);
4529 s << "d";
4530 s << wxString::Format("%d", hours);
4531 s << "h";
4532 }
4533 }
4534
4535 return s;
4536}
4537
4538// END OF CUSTOMIZATION - FORMAT MINUTES
4539// Thanks open source code ;-)
4540// -------------------------------------------------------
4541
4542void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4543 double clat, clon;
4544 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4545 *lat = clat;
4546 *lon = clon;
4547}
4548
4549void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4550 wxPoint2DDouble *r) {
4551 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4552}
4553
4555 double rlon, wxPoint2DDouble *r) {
4556 // If the Current Chart is a raster chart, and the
4557 // requested lat/long is within the boundaries of the chart,
4558 // and the VP is not rotated,
4559 // then use the embedded BSB chart georeferencing algorithm
4560 // for greater accuracy
4561 // Additionally, use chart embedded georef if the projection is TMERC
4562 // i.e. NOT MERCATOR and NOT POLYCONIC
4563
4564 // If for some reason the chart rejects the request by returning an error,
4565 // then fall back to Viewport Projection estimate from canvas parameters
4566 if (!g_bopengl && m_singleChart &&
4567 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4568 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4569 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4570 (m_singleChart->GetChartProjectionType() !=
4571 PROJECTION_TRANSVERSE_MERCATOR) &&
4572 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4573 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4574 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4575 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4576 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4577 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4578 // Cur_BSB_Ch->GetCOVRTablenPoints
4579 // ( 0 ), rlon,
4580 // rlat );
4581 // bInside = true;
4582 // if ( bInside )
4583 if (Cur_BSB_Ch) {
4584 // This is a Raster chart....
4585 // If the VP is changing, the raster chart parameters may not yet be
4586 // setup So do that before accessing the chart's embedded
4587 // georeferencing
4588 Cur_BSB_Ch->SetVPRasterParms(vp);
4589 double rpixxd, rpixyd;
4590 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4591 r->m_x = rpixxd;
4592 r->m_y = rpixyd;
4593 return;
4594 }
4595 }
4596 }
4597
4598 // if needed, use the VPoint scaling estimator,
4599 *r = vp.GetDoublePixFromLL(rlat, rlon);
4600}
4601
4602// This routine might be deleted and all of the rendering improved
4603// to have floating point accuracy
4604bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4605 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4606}
4607
4608bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4609 wxPoint *r) {
4610 wxPoint2DDouble p;
4611 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4612
4613 // some projections give nan values when invisible values (other side of
4614 // world) are requested we should stop using integer coordinates or return
4615 // false here (and test it everywhere)
4616 if (std::isnan(p.m_x)) {
4617 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4618 return false;
4619 }
4620
4621 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4622 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4623 else
4624 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4625
4626 return true;
4627}
4628
4629void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4630 double &lon) {
4631 // If the Current Chart is a raster chart, and the
4632 // requested x,y is within the boundaries of the chart,
4633 // and the VP is not rotated,
4634 // then use the embedded BSB chart georeferencing algorithm
4635 // for greater accuracy
4636 // Additionally, use chart embedded georef if the projection is TMERC
4637 // i.e. NOT MERCATOR and NOT POLYCONIC
4638
4639 // If for some reason the chart rejects the request by returning an error,
4640 // then fall back to Viewport Projection estimate from canvas parameters
4641 bool bUseVP = true;
4642
4643 if (!g_bopengl && m_singleChart &&
4644 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4645 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4646 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4647 (m_singleChart->GetChartProjectionType() !=
4648 PROJECTION_TRANSVERSE_MERCATOR) &&
4649 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4650 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4651 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4652 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4653
4654 // TODO maybe need iterative process to validate bInside
4655 // first pass is mercator, then check chart boundaries
4656
4657 if (Cur_BSB_Ch) {
4658 // This is a Raster chart....
4659 // If the VP is changing, the raster chart parameters may not yet be
4660 // setup So do that before accessing the chart's embedded
4661 // georeferencing
4662 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4663
4664 double slat, slon;
4665 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4666 lat = slat;
4667
4668 if (slon < -180.)
4669 slon += 360.;
4670 else if (slon > 180.)
4671 slon -= 360.;
4672
4673 lon = slon;
4674 bUseVP = false;
4675 }
4676 }
4677 }
4678
4679 // if needed, use the VPoint scaling estimator
4680 if (bUseVP) {
4681 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4682 }
4683}
4684
4686 StopMovement();
4687 DoZoomCanvas(factor, false);
4688 extendedSectorLegs.clear();
4689}
4690
4691void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4692 bool stoptimer) {
4693 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4694
4695 if (g_bsmoothpanzoom) {
4696 if (StartTimedMovement(stoptimer)) {
4697 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4698 m_zoom_factor = factor;
4699 }
4700
4701 m_zoom_target = VPoint.chart_scale / factor;
4702 } else {
4703 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4704
4705 DoZoomCanvas(factor, can_zoom_to_cursor);
4706 }
4707
4708 extendedSectorLegs.clear();
4709}
4710
4711void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4712 // possible on startup
4713 if (!ChartData) return;
4714 if (!m_pCurrentStack) return;
4715
4716 /* TODO: queue the quilted loading code to a background thread
4717 so yield is never called from here, and also rendering is not delayed */
4718
4719 // Cannot allow Yield() re-entrancy here
4720 if (m_bzooming) return;
4721 m_bzooming = true;
4722
4723 double old_ppm = GetVP().view_scale_ppm;
4724
4725 // Capture current cursor position for zoom to cursor
4726 double zlat = m_cursor_lat;
4727 double zlon = m_cursor_lon;
4728
4729 double proposed_scale_onscreen =
4730 GetVP().chart_scale /
4731 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4732 bool b_do_zoom = false;
4733
4734 if (factor > 1) {
4735 b_do_zoom = true;
4736
4737 // double zoom_factor = factor;
4738
4739 ChartBase *pc = NULL;
4740
4741 if (!VPoint.b_quilt) {
4742 pc = m_singleChart;
4743 } else {
4744 if (!m_disable_adjust_on_zoom) {
4745 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4746 if (new_db_index >= 0)
4747 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4748 else { // for whatever reason, no reference chart is known
4749 // Choose the smallest scale chart on the current stack
4750 // and then adjust for scale range
4751 int current_ref_stack_index = -1;
4752 if (m_pCurrentStack->nEntry) {
4753 int trial_index =
4754 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4755 m_pQuilt->SetReferenceChart(trial_index);
4756 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4757 if (new_db_index >= 0)
4758 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4759 }
4760 }
4761
4762 if (m_pCurrentStack)
4763 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4764 new_db_index); // highlite the correct bar entry
4765 }
4766 }
4767
4768 if (pc) {
4769 // double target_scale_ppm = GetVPScale() * zoom_factor;
4770 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4771 // target_scale_ppm;
4772
4773 // Query the chart to determine the appropriate zoom range
4774 double min_allowed_scale =
4775 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4776
4777 if (proposed_scale_onscreen < min_allowed_scale) {
4778 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4779 m_zoom_factor = 1; /* stop zooming */
4780 b_do_zoom = false;
4781 } else
4782 proposed_scale_onscreen = min_allowed_scale;
4783 }
4784
4785 } else {
4786 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4787 }
4788
4789 } else if (factor < 1) {
4790 b_do_zoom = true;
4791
4792 ChartBase *pc = NULL;
4793
4794 bool b_smallest = false;
4795
4796 if (!VPoint.b_quilt) { // not quilted
4797 pc = m_singleChart;
4798
4799 if (pc) {
4800 // If m_singleChart is not on the screen, unbound the zoomout
4801 LLBBox viewbox = VPoint.GetBBox();
4802 // BoundingBox chart_box;
4803 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4804 double max_allowed_scale;
4805
4806 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4807
4808 // We can allow essentially unbounded zoomout in single chart mode
4809 // if( ChartData->GetDBBoundingBox( current_index,
4810 // &chart_box ) &&
4811 // !viewbox.IntersectOut( chart_box ) )
4812 // // Clamp the minimum scale zoom-out to the value
4813 // specified by the chart max_allowed_scale =
4814 // wxMin(max_allowed_scale, 4.0 *
4815 // pc->GetNormalScaleMax(
4816 // GetCanvasScaleFactor(),
4817 // GetCanvasWidth() ) );
4818 if (proposed_scale_onscreen > max_allowed_scale) {
4819 m_zoom_factor = 1; /* stop zooming */
4820 proposed_scale_onscreen = max_allowed_scale;
4821 }
4822 }
4823
4824 } else {
4825 if (!m_disable_adjust_on_zoom) {
4826 int new_db_index =
4827 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4828 if (new_db_index >= 0)
4829 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4830
4831 if (m_pCurrentStack)
4832 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4833 new_db_index); // highlite the correct bar entry
4834
4835 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4836
4837 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4838 proposed_scale_onscreen =
4839 wxMin(proposed_scale_onscreen,
4840 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4841 }
4842
4843 // set a minimum scale
4844 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4845 m_absolute_min_scale_ppm)
4846 proposed_scale_onscreen =
4847 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4848 }
4849 }
4850 double new_scale =
4851 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4852
4853 if (b_do_zoom) {
4854 // Disable ZTC if lookahead is ON, and currently b_follow is active
4855 bool b_allow_ztc = true;
4856 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4857 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4858 if (m_bLookAhead) {
4859 double brg, distance;
4860 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4861 &distance);
4862 dir_to_shift = brg;
4863 meters_to_shift = distance * 1852;
4864 }
4865 // Arrange to combine the zoom and pan into one operation for smoother
4866 // appearance
4867 SetVPScale(new_scale, false); // adjust, but deferred refresh
4868 wxPoint r;
4869 GetCanvasPointPix(zlat, zlon, &r);
4870 // this will emit the Refresh()
4871 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4872 } else {
4873 SetVPScale(new_scale);
4874 if (m_bFollow) DoCanvasUpdate();
4875 }
4876 }
4877
4878 m_bzooming = false;
4879}
4880
4881void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4882 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4883 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4884}
4885
4886int rot;
4887void ChartCanvas::RotateCanvas(double dir) {
4888 // SetUpMode(NORTH_UP_MODE);
4889
4890 if (g_bsmoothpanzoom) {
4891 if (StartTimedMovement()) {
4892 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4893 m_rotation_speed = dir * 60;
4894 }
4895 } else {
4896 double speed = dir * 10;
4897 if (m_modkeys == wxMOD_ALT) speed /= 20;
4898 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4899 }
4900}
4901
4902void ChartCanvas::DoRotateCanvas(double rotation) {
4903 while (rotation < 0) rotation += 2 * PI;
4904 while (rotation > 2 * PI) rotation -= 2 * PI;
4905
4906 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4907
4908 SetVPRotation(rotation);
4909 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4910}
4911
4912void ChartCanvas::DoTiltCanvas(double tilt) {
4913 while (tilt < 0) tilt = 0;
4914 while (tilt > .95) tilt = .95;
4915
4916 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4917
4918 VPoint.tilt = tilt;
4919 Refresh(false);
4920}
4921
4922void ChartCanvas::TogglebFollow() {
4923 if (!m_bFollow)
4924 SetbFollow();
4925 else
4926 ClearbFollow();
4927}
4928
4929void ChartCanvas::ClearbFollow() {
4930 m_bFollow = false; // update the follow flag
4931
4932 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4933
4934 UpdateFollowButtonState();
4935
4936 DoCanvasUpdate();
4937 ReloadVP();
4938 top_frame::Get()->SetChartUpdatePeriod();
4939}
4940
4941void ChartCanvas::SetbFollow() {
4942 // Is the OWNSHIP on-screen?
4943 // If not, then reset the OWNSHIP offset to 0 (center screen)
4944 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4945 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4946 m_OSoffsetx = 0;
4947 m_OSoffsety = 0;
4948 }
4949
4950 // Apply the present b_follow offset values to ship position
4951 wxPoint2DDouble p;
4953 p.m_x += m_OSoffsetx;
4954 p.m_y -= m_OSoffsety;
4955
4956 // compute the target center screen lat/lon
4957 double dlat, dlon;
4958 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4959
4960 JumpToPosition(dlat, dlon, GetVPScale());
4961 m_bFollow = true;
4962
4963 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4964 UpdateFollowButtonState();
4965
4966 if (!g_bSmoothRecenter) {
4967 DoCanvasUpdate();
4968 ReloadVP();
4969 }
4970 top_frame::Get()->SetChartUpdatePeriod();
4971}
4972
4973void ChartCanvas::UpdateFollowButtonState() {
4974 if (m_muiBar) {
4975 if (!m_bFollow)
4976 m_muiBar->SetFollowButtonState(0);
4977 else {
4978 if (m_bLookAhead)
4979 m_muiBar->SetFollowButtonState(2);
4980 else
4981 m_muiBar->SetFollowButtonState(1);
4982 }
4983 }
4984
4985#ifdef __ANDROID__
4986 if (!m_bFollow)
4987 androidSetFollowTool(0);
4988 else {
4989 if (m_bLookAhead)
4990 androidSetFollowTool(2);
4991 else
4992 androidSetFollowTool(1);
4993 }
4994#endif
4995
4996 // Look for plugin using API-121 or later
4997 // If found, make the follow state callback.
4998 if (g_pi_manager) {
4999 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
5000 if (pic->m_enabled && pic->m_init_state) {
5001 switch (pic->m_api_version) {
5002 case 121: {
5003 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
5004 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
5005 break;
5006 }
5007 default:
5008 break;
5009 }
5010 }
5011 }
5012 }
5013}
5014
5015void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
5016 if (g_bSmoothRecenter && !m_routeState) {
5017 if (StartSmoothJump(lat, lon, scale_ppm))
5018 return;
5019 else {
5020 // move closer to the target destination, and try again
5021 double gcDist, gcBearingEnd;
5022 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
5023 &gcBearingEnd);
5024 gcBearingEnd += 180;
5025 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
5026 GetCanvasWidth() / GetVPScale(); // meters
5027 double lon_offset =
5028 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
5029 double new_lat = lat + (lat_offset / (1852 * 60));
5030 double new_lon = lon + (lon_offset / (1852 * 60));
5031 SetViewPoint(new_lat, new_lon);
5032 ReloadVP();
5033 StartSmoothJump(lat, lon, scale_ppm);
5034 return;
5035 }
5036 }
5037
5038 if (lon > 180.0) lon -= 360.0;
5039 m_vLat = lat;
5040 m_vLon = lon;
5041 StopMovement();
5042 m_bFollow = false;
5043
5044 if (!GetQuiltMode()) {
5045 double skew = 0;
5046 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5047 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5048 } else {
5049 if (scale_ppm != GetVPScale()) {
5050 // XXX should be done in SetViewPoint
5051 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5052 AdjustQuiltRefChart();
5053 }
5054 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5055 }
5056
5057 ReloadVP();
5058
5059 UpdateFollowButtonState();
5060
5061 // TODO
5062 // if( g_pi_manager ) {
5063 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5064 // }
5065}
5066
5067bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5068 // Check distance to jump, in pixels at current chart scale
5069 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5070 // width.
5071 double gcDist;
5072 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5073 double distance_pixels = gcDist * GetVPScale();
5074 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5075 // Jump is too far, try again
5076 return false;
5077 }
5078
5079 // Save where we're coming from
5080 m_startLat = m_vLat;
5081 m_startLon = m_vLon;
5082 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5083
5084 // Save where we want to end up
5085 m_endLat = lat;
5086 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5087 m_endScale = scale_ppm;
5088
5089 // Setup timing
5090 m_animationDuration = 600; // ms
5091 m_animationStart = wxGetLocalTimeMillis();
5092
5093 // Stop any previous movement, ensure no conflicts
5094 StopMovement();
5095 m_bFollow = false;
5096
5097 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5098 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5099 m_animationActive = true;
5100
5101 return true;
5102}
5103
5104void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5105 // Calculate time fraction from 0..1
5106 wxLongLong now = wxGetLocalTimeMillis();
5107 double elapsed = (now - m_animationStart).ToDouble();
5108 double t = elapsed / m_animationDuration.ToDouble();
5109 if (t > 1.0) t = 1.0;
5110
5111 // Ease function for smoother movement
5112 double e = easeOutCubic(t);
5113
5114 // Interpolate lat/lon/scale
5115 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5116 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5117 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5118
5119 // Update viewpoint
5120 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5121 // portion)
5122 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5123 ReloadVP();
5124
5125 // If we reached the end, stop the timer and finalize
5126 if (t >= 1.0) {
5127 m_easeTimer.Stop();
5128 m_animationActive = false;
5129 UpdateFollowButtonState();
5130 ZoomCanvasSimple(1.0001);
5131 DoCanvasUpdate();
5132 ReloadVP();
5133 }
5134}
5135
5136bool ChartCanvas::PanCanvas(double dx, double dy) {
5137 if (!ChartData) return false;
5138 extendedSectorLegs.clear();
5139
5140 double dlat, dlon;
5141 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5142
5143 int iters = 0;
5144 for (;;) {
5145 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5146
5147 if (iters++ > 5) return false;
5148 if (!std::isnan(dlat)) break;
5149
5150 dx *= .5, dy *= .5;
5151 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5152 }
5153
5154 if (dlon > 360.) dlon -= 360.;
5155 if (dlon < -360.) dlon += 360.;
5156
5157 // This should not really be necessary, but round-trip georef on some
5158 // charts is not perfect, So we can get creep on repeated unidimensional
5159 // pans, and corrupt chart cacheing.......
5160
5161 // But this only works on north-up projections
5162 // TODO: can we remove this now?
5163 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5164 // .001 ) ) {
5165 //
5166 // if( dx == 0 ) dlon = clon;
5167 // if( dy == 0 ) dlat = clat;
5168 // }
5169
5170 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5171
5172 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5173
5174 if (VPoint.b_quilt) {
5175 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5176 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5177 // Tweak the scale slightly for a new ref chart
5178 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5179 if (pc) {
5180 double tweak_scale_ppm =
5181 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5182 SetVPScale(tweak_scale_ppm);
5183 }
5184 }
5185
5186#ifndef __ANDROID__
5187 // ToDo: Have seen crashes on "if (m_pCurrentStack->nEntry)"
5188 // Action: convert m_pCurrentStack to member object, not pointer.
5189
5190 if (new_ref_dbIndex == -1) {
5191#pragma GCC diagnostic push
5192#pragma GCC diagnostic ignored "-Warray-bounds"
5193 // The compiler sees a -1 index being used. Does not happen, though.
5194
5195 // for whatever reason, no reference chart is known
5196 // Probably panned out of the coverage region
5197 // If any charts are anywhere on-screen, choose the smallest
5198 // scale chart on the screen to be a new reference chart.
5199 int trial_index = -1;
5200 if (m_pCurrentStack->nEntry) {
5201 trial_index = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5202 }
5203
5204 if (trial_index < 0) {
5205 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5206 if (full_screen_array.size())
5207 trial_index = full_screen_array[full_screen_array.size() - 1];
5208 }
5209
5210 if (trial_index >= 0) {
5211 m_pQuilt->SetReferenceChart(trial_index);
5212 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5213 VPoint.rotation);
5214 ReloadVP();
5215 }
5216#pragma GCC diagnostic pop
5217 }
5218#endif
5219 }
5220
5221 // Turn off bFollow only if the ownship has left the screen
5222 if (m_bFollow) {
5223 double offx, offy;
5224 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5225
5226 double offset_angle = atan2(offy, offx);
5227 double offset_distance = sqrt((offy * offy) + (offx * offx));
5228 double chart_angle = GetVPRotation();
5229 double target_angle = chart_angle - offset_angle;
5230 double d_east_mod = offset_distance * cos(target_angle);
5231 double d_north_mod = offset_distance * sin(target_angle);
5232
5233 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5234 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5235
5236 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5237 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5238 m_bFollow = false; // update the follow flag
5239 UpdateFollowButtonState();
5240 }
5241 }
5242
5243 Refresh(false);
5244
5245 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5246
5247 return true;
5248}
5249
5250bool ChartCanvas::IsOwnshipOnScreen() {
5251 wxPoint r;
5253 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5254 ((r.y > 0) && r.y < GetCanvasHeight()))
5255 return true;
5256 else
5257 return false;
5258}
5259
5260void ChartCanvas::ReloadVP(bool b_adjust) {
5261 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5262
5263 LoadVP(VPoint, b_adjust);
5264}
5265
5266void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5267#ifdef ocpnUSE_GL
5268 if (g_bopengl && m_glcc) {
5269 m_glcc->Invalidate();
5270 if (m_glcc->GetSize() != GetSize()) {
5271 m_glcc->SetSize(GetSize());
5272 }
5273 } else
5274#endif
5275 {
5276 m_cache_vp.Invalidate();
5277 m_bm_cache_vp.Invalidate();
5278 }
5279
5280 VPoint.Invalidate();
5281
5282 if (m_pQuilt) m_pQuilt->Invalidate();
5283
5284 // Make sure that the Selected Group is sensible...
5285 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5286 // m_groupIndex = 0;
5287 // if( !CheckGroup( m_groupIndex ) )
5288 // m_groupIndex = 0;
5289
5290 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5291 vp.m_projection_type, b_adjust);
5292}
5293
5294void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5295 m_pQuilt->SetReferenceChart(dbIndex);
5296 VPoint.Invalidate();
5297 m_pQuilt->Invalidate();
5298}
5299
5300double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5301 if (m_pQuilt)
5302 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5303 else
5304 return vp.view_scale_ppm;
5305}
5306
5307// Verify and adjust the current reference chart,
5308// so that it will not lead to excessive overzoom or underzoom onscreen
5309int ChartCanvas::AdjustQuiltRefChart() {
5310 int ret = -1;
5311 if (m_pQuilt) {
5312 wxASSERT(ChartData);
5313 ChartBase *pc =
5314 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5315 if (pc) {
5316 double min_ref_scale =
5317 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5318 double max_ref_scale =
5319 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5320
5321 if (VPoint.chart_scale < min_ref_scale) {
5322 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5323 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5324 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5325 } else {
5326 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5327
5328 if (!brender_ok) {
5329 int target_stack_index = wxNOT_FOUND;
5330 int il = 0;
5331 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5332 if (index == m_pQuilt->GetRefChartdbIndex()) {
5333 target_stack_index = il;
5334 break;
5335 }
5336 il++;
5337 }
5338 if (wxNOT_FOUND == target_stack_index) // should never happen...
5339 target_stack_index = 0;
5340
5341 int ref_family = pc->GetChartFamily();
5342 int extended_array_count =
5343 m_pQuilt->GetExtendedStackIndexArray().size();
5344 while ((!brender_ok) &&
5345 ((int)target_stack_index < (extended_array_count - 1))) {
5346 target_stack_index++;
5347 int test_db_index =
5348 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5349
5350 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5351 IsChartQuiltableRef(test_db_index)) {
5352 // open the target, and check the min_scale
5353 ChartBase *ptest_chart =
5354 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5355 if (ptest_chart) {
5356 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5357 }
5358 }
5359 }
5360
5361 if (brender_ok) { // found a better reference chart
5362 int new_db_index =
5363 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5364 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5365 IsChartQuiltableRef(new_db_index)) {
5366 m_pQuilt->SetReferenceChart(new_db_index);
5367 ret = new_db_index;
5368 } else
5369 ret = m_pQuilt->GetRefChartdbIndex();
5370 } else
5371 ret = m_pQuilt->GetRefChartdbIndex();
5372
5373 } else
5374 ret = m_pQuilt->GetRefChartdbIndex();
5375 }
5376 } else
5377 ret = -1;
5378 }
5379
5380 return ret;
5381}
5382
5383void ChartCanvas::UpdateCanvasOnGroupChange() {
5384 delete m_pCurrentStack;
5385 m_pCurrentStack = new ChartStack;
5386 wxASSERT(ChartData);
5387 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5388 m_groupIndex);
5389
5390 if (m_pQuilt) {
5391 m_pQuilt->Compose(VPoint);
5392 SetFocus();
5393 }
5394}
5395
5396bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5397 double latNE, double lonNE) {
5398 // Center Point
5399 double latc = (latSW + latNE) / 2.0;
5400 double lonc = (lonSW + lonNE) / 2.0;
5401
5402 // Get scale in ppm (latitude)
5403 double ne_easting, ne_northing;
5404 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5405
5406 double sw_easting, sw_northing;
5407 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5408
5409 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5410
5411 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5412}
5413
5414bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5415 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5416 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5417}
5418
5419bool ChartCanvas::SetVPProjection(int projection) {
5420 if (!g_bopengl) // alternative projections require opengl
5421 return false;
5422
5423 // the view scale varies depending on geographic location and projection
5424 // rescale to keep the relative scale on the screen the same
5425 double prev_true_scale_ppm = m_true_scale_ppm;
5426 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5427 VPoint.skew, VPoint.rotation, projection) &&
5428 SetVPScale(wxMax(
5429 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5430 m_absolute_min_scale_ppm));
5431}
5432
5433bool ChartCanvas::SetViewPoint(double lat, double lon) {
5434 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5435 VPoint.rotation);
5436}
5437
5438bool ChartCanvas::SetVPRotation(double angle) {
5439 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5440 VPoint.skew, angle);
5441}
5442bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5443 double skew, double rotation, int projection,
5444 bool b_adjust, bool b_refresh) {
5445 if (ChartData->IsBusy()) return false;
5446 bool b_ret = false;
5447 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5448 skew -= 2 * PI;
5449 // Any sensible change?
5450 if (VPoint.IsValid()) {
5451 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5452 (fabs(VPoint.skew - skew) < 1e-9) &&
5453 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5454 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5455 (VPoint.m_projection_type == projection ||
5456 projection == PROJECTION_UNKNOWN))
5457 return false;
5458 }
5459 if (VPoint.m_projection_type != projection)
5460 VPoint.InvalidateTransformCache(); // invalidate
5461
5462 // Take a local copy of the last viewport
5463 ViewPort last_vp = VPoint;
5464
5465 VPoint.skew = skew;
5466 VPoint.clat = lat;
5467 VPoint.clon = lon;
5468 VPoint.rotation = rotation;
5469 VPoint.view_scale_ppm = scale_ppm;
5470 if (projection != PROJECTION_UNKNOWN)
5471 VPoint.SetProjectionType(projection);
5472 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5473 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5474
5475 // Clamp so the viewport edges don't extend beyond the maximum latitude
5476 // coverage. The basemap covers ±85°, and Mercator is undefined at ±90°.
5477 // Iterate to converge since Mercator is non-linear.
5478 static const double MAX_LAT = 85.0;
5479
5480 if (VPoint.pix_height > 0 && scale_ppm > 0) {
5481 double edge_lat, edge_lon;
5482
5483 for (int i = 0; i < 3; i++) {
5484 VPoint.GetLLFromPix(wxPoint2DDouble(VPoint.pix_width / 2.0, 0), &edge_lat,
5485 &edge_lon);
5486 if (!std::isnan(edge_lat) && edge_lat > MAX_LAT) {
5487 VPoint.clat -= (edge_lat - MAX_LAT);
5488 } else {
5489 break;
5490 }
5491 }
5492
5493 for (int i = 0; i < 3; i++) {
5494 VPoint.GetLLFromPix(
5495 wxPoint2DDouble(VPoint.pix_width / 2.0, VPoint.pix_height), &edge_lat,
5496 &edge_lon);
5497 if (!std::isnan(edge_lat) && edge_lat < -MAX_LAT) {
5498 VPoint.clat -= (edge_lat + MAX_LAT);
5499 } else {
5500 break;
5501 }
5502 }
5503 }
5504
5505 // Hard clamp as safety net
5506 if (VPoint.clat > MAX_LAT)
5507 VPoint.clat = MAX_LAT;
5508 else if (VPoint.clat < -MAX_LAT)
5509 VPoint.clat = -MAX_LAT;
5510
5511 // don't zoom out too far for transverse mercator polyconic until we resolve
5512 // issues
5513 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5514 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5515 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5516
5517 // SetVPRotation(rotation);
5518
5519 if (!g_bopengl) // tilt is not possible without opengl
5520 VPoint.tilt = 0;
5521
5522 if ((VPoint.pix_width <= 0) ||
5523 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5524 return false;
5525
5526 bool bwasValid = VPoint.IsValid();
5527 VPoint.Validate(); // Mark this ViewPoint as OK
5528
5529 // Has the Viewport scale changed? If so, invalidate the vp
5530 if (last_vp.view_scale_ppm != scale_ppm) {
5531 m_cache_vp.Invalidate();
5532 InvalidateGL();
5533 }
5534
5535 // A preliminary value, may be tweaked below
5536 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5537
5538 // recompute cursor position
5539 // and send to interested plugins if the mouse is actually in this window
5540 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5541 int mouseX = mouse_x;
5542 int mouseY = mouse_y;
5543 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5544 (mouseY < VPoint.pix_height)) {
5545 double lat_mouse, lon_mouse;
5546 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5547 m_cursor_lat = lat_mouse;
5548 m_cursor_lon = lon_mouse;
5549 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5550 }
5551 }
5552
5553 if (!VPoint.b_quilt && m_singleChart) {
5554 VPoint.SetBoxes();
5555
5556 // Allow the chart to adjust the new ViewPort for performance optimization
5557 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5558 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5559
5560 // If there is a sensible change in the chart render, refresh the whole
5561 // screen
5562 if ((!m_cache_vp.IsValid()) ||
5563 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5564 Refresh(false);
5565 b_ret = true;
5566 } else {
5567 wxPoint cp_last, cp_this;
5568 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5569 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5570
5571 if (cp_last != cp_this) {
5572 Refresh(false);
5573 b_ret = true;
5574 }
5575 }
5576 // Create the stack
5577 if (m_pCurrentStack) {
5578 assert(ChartData != 0);
5579 int current_db_index;
5580 current_db_index =
5581 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5582
5583 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5584 m_groupIndex);
5585 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5586 }
5587
5588 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5589 }
5590
5591 // Handle the quilted case
5592 if (VPoint.b_quilt) {
5593 VPoint.SetBoxes();
5594
5595 if (last_vp.view_scale_ppm != scale_ppm)
5596 m_pQuilt->InvalidateAllQuiltPatchs();
5597
5598 // Create the quilt
5599 if (ChartData /*&& ChartData->IsValid()*/) {
5600 if (!m_pCurrentStack) return false;
5601
5602 int current_db_index;
5603 current_db_index =
5604 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5605
5606 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5607 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5608
5609 // Check to see if the current quilt reference chart is in the new stack
5610 int current_ref_stack_index = -1;
5611 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5612 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5613 current_ref_stack_index = i;
5614 }
5615
5616 if (g_bFullScreenQuilt) {
5617 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5618 }
5619
5620 // We might need a new Reference Chart
5621 bool b_needNewRef = false;
5622
5623 // If the new stack does not contain the current ref chart....
5624 if ((-1 == current_ref_stack_index) &&
5625 (m_pQuilt->GetRefChartdbIndex() >= 0))
5626 b_needNewRef = true;
5627
5628 // Would the current Ref Chart be excessively underzoomed?
5629 // We need to check this here to be sure, since we cannot know where the
5630 // reference chart was assigned. For instance, the reference chart may
5631 // have been selected from the config file, or from a long jump with a
5632 // chart family switch implicit. Anyway, we check to be sure....
5633 bool renderable = true;
5634 ChartBase *referenceChart =
5635 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5636 if (referenceChart) {
5637 double chartMaxScale = referenceChart->GetNormalScaleMax(
5638 GetCanvasScaleFactor(), GetCanvasWidth());
5639 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5640 }
5641 if (!renderable) b_needNewRef = true;
5642
5643 // Need new refchart?
5644 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5645 const ChartTableEntry &cte_ref =
5646 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5647 int target_scale = cte_ref.GetScale();
5648 int target_type = cte_ref.GetChartType();
5649 int candidate_stack_index;
5650
5651 // reset the ref chart in a way that does not lead to excessive
5652 // underzoom, for performance reasons Try to find a chart that is the
5653 // same type, and has a scale of just smaller than the current ref
5654 // chart
5655
5656 candidate_stack_index = 0;
5657 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5658 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5659 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5660 int candidate_scale = cte_candidate.GetScale();
5661 int candidate_type = cte_candidate.GetChartType();
5662
5663 if ((candidate_scale >= target_scale) &&
5664 (candidate_type == target_type)) {
5665 bool renderable = true;
5666 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5667 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5668 if (tentative_referenceChart) {
5669 double chartMaxScale =
5670 tentative_referenceChart->GetNormalScaleMax(
5671 GetCanvasScaleFactor(), GetCanvasWidth());
5672 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5673 }
5674
5675 if (renderable) break;
5676 }
5677
5678 candidate_stack_index++;
5679 }
5680
5681 // If that did not work, look for a chart of just larger scale and
5682 // same type
5683 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5684 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5685 while (candidate_stack_index >= 0) {
5686 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5687 if (idx >= 0) {
5688 const ChartTableEntry &cte_candidate =
5689 ChartData->GetChartTableEntry(idx);
5690 int candidate_scale = cte_candidate.GetScale();
5691 int candidate_type = cte_candidate.GetChartType();
5692
5693 if ((candidate_scale <= target_scale) &&
5694 (candidate_type == target_type))
5695 break;
5696 }
5697 candidate_stack_index--;
5698 }
5699 }
5700
5701 // and if that did not work, chose stack entry 0
5702 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5703 (candidate_stack_index < 0))
5704 candidate_stack_index = 0;
5705
5706 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5707
5708 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5709 }
5710
5711 if (!g_bopengl) {
5712 // Preset the VPoint projection type to match what the quilt projection
5713 // type will be
5714 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5715
5716 // Always keep the default Mercator projection if the reference chart is
5717 // not in the PatchList or the scale is too small for it to render.
5718
5719 bool renderable = true;
5720 ChartBase *referenceChart =
5721 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5722 if (referenceChart) {
5723 double chartMaxScale = referenceChart->GetNormalScaleMax(
5724 GetCanvasScaleFactor(), GetCanvasWidth());
5725 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5726 proj = ChartData->GetDBChartProj(ref_db_index);
5727 } else
5728 proj = PROJECTION_MERCATOR;
5729
5730 VPoint.b_MercatorProjectionOverride =
5731 (m_pQuilt->GetnCharts() == 0 || !renderable);
5732
5733 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5734
5735 VPoint.SetProjectionType(proj);
5736 }
5737
5738 // If this quilt will be a perceptible delta from the existing quilt,
5739 // then refresh the entire screen
5740 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5741 // Allow the quilt to adjust the new ViewPort for performance
5742 // optimization This will normally be only a fractional (i.e.
5743 // sub-pixel) adjustment...
5744 if (b_adjust) {
5745 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5746 }
5747
5748 // ChartData->ClearCacheInUseFlags();
5749 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5750
5751 // wxStopWatch sw;
5752
5753#ifdef __ANDROID__
5754 // This is an optimization for panning on touch screen systems.
5755 // The quilt composition is deferred until the OnPaint() message gets
5756 // finally removed and processed from the message queue.
5757 // Takes advantage of the fact that touch-screen pan gestures are
5758 // usually short in distance,
5759 // so not requiring a full quilt rebuild until the pan gesture is
5760 // complete.
5761 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5762 // qDebug() << "Force compose";
5763 m_pQuilt->Compose(VPoint);
5764 } else {
5765 m_pQuilt->Invalidate();
5766 }
5767#else
5768 m_pQuilt->Compose(VPoint);
5769#endif
5770
5771 // printf("comp time %ld\n", sw.Time());
5772
5773 // If the extended chart stack has changed, invalidate any cached
5774 // render bitmap
5775 // if(m_pQuilt->GetXStackHash() != hash1) {
5776 // m_bm_cache_vp.Invalidate();
5777 // InvalidateGL();
5778 // }
5779
5780 ChartData->PurgeCacheUnusedCharts(0.7);
5781
5782 if (b_refresh) Refresh(false);
5783
5784 b_ret = true;
5785 }
5786 }
5787
5788 VPoint.skew = 0.; // Quilting supports 0 Skew
5789 } else if (!g_bopengl) {
5790 OcpnProjType projection = PROJECTION_UNKNOWN;
5791 if (m_singleChart) // viewport projection must match chart projection
5792 // without opengl
5793 projection = m_singleChart->GetChartProjectionType();
5794 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5795 VPoint.SetProjectionType(projection);
5796 }
5797
5798 // Has the Viewport projection changed? If so, invalidate the vp
5799 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5800 m_cache_vp.Invalidate();
5801 InvalidateGL();
5802 }
5803
5804 UpdateCanvasControlBar(); // Refresh the Piano
5805
5806 VPoint.chart_scale = 1.0; // fallback default value
5807
5808 if (VPoint.GetBBox().GetValid()) {
5809 // Update the viewpoint reference scale
5810 if (m_singleChart)
5811 VPoint.ref_scale = m_singleChart->GetNativeScale();
5812 else {
5813#ifdef __ANDROID__
5814 // This is an optimization for panning on touch screen systems.
5815 // See above.
5816 // Quilt might not be fully composed at this point, so for cm93
5817 // the reference scale may not be known.
5818 // In this case, do not update the VP ref_scale.
5819 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5820 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5821 }
5822#else
5823 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5824#endif
5825 }
5826
5827 // Calculate the on-screen displayed actual scale
5828 // by a simple traverse northward from the center point
5829 // of roughly one eighth of the canvas height
5830 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5831
5832 double delta_check =
5833 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5834 delta_check /= 8.;
5835
5836 double check_point = wxMin(89., VPoint.clat);
5837
5838 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5839
5840 double rhumbDist;
5841 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5842 VPoint.clon, 0, &rhumbDist);
5843
5844 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5845 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5846 // Calculate the distance between r1 and r in physical pixels.
5847 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5848 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5849
5850 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5851
5852 // A fall back in case of very high zoom-out, giving delta_y == 0
5853 // which can probably only happen with vector charts
5854 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5855
5856 // Another fallback, for highly zoomed out charts
5857 // This adjustment makes the displayed TrueScale correspond to the
5858 // same algorithm used to calculate the chart zoom-out limit for
5859 // ChartDummy.
5860 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5861
5862 if (m_true_scale_ppm)
5863 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5864 else
5865 VPoint.chart_scale = 1.0;
5866
5867 // Create a nice renderable string
5868 double round_factor = 1000.;
5869 if (VPoint.chart_scale <= 1000.)
5870 round_factor = 10.;
5871 else if (VPoint.chart_scale <= 10000.)
5872 round_factor = 100.;
5873 else if (VPoint.chart_scale <= 100000.)
5874 round_factor = 1000.;
5875
5876 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5877 double retina_coef = 1;
5878#ifdef ocpnUSE_GL
5879#ifdef __WXOSX__
5880 if (g_bopengl) {
5881 retina_coef = GetContentScaleFactor();
5882 }
5883#endif
5884#endif
5885
5886 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5887 // rounded to the nearest 10, 100 or 1000.
5888 //
5889 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5890 // true_scale_display. That does not make sense. The chart scale should be
5891 // the same as the true scale within the limits of the rounding factor.
5892 double true_scale_display =
5893 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5894 wxString text;
5895
5896 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5897
5898 if (m_displayed_scale_factor > 10.0)
5899 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5900 m_displayed_scale_factor);
5901 else if (m_displayed_scale_factor > 1.0)
5902 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5903 m_displayed_scale_factor);
5904 else if (m_displayed_scale_factor > 0.1) {
5905 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5906 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5907 } else if (m_displayed_scale_factor > 0.01) {
5908 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5909 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5910 } else {
5911 text.Printf(
5912 "%s %4.0f (---)", _("Scale"),
5913 true_scale_display); // Generally, no chart, so no chart scale factor
5914 }
5915
5916 m_scaleValue = true_scale_display;
5917 m_scaleText = text;
5918 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5919
5920 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5921 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5922 STAT_FIELD_SCALE)) {
5923 // Check to see if the text will fit in the StatusBar field...
5924 bool b_noshow = false;
5925 {
5926 int w = 0;
5927 int h;
5928 wxClientDC dc(top_frame::Get()->GetStatusBar());
5929 if (dc.IsOk()) {
5930 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5931 dc.SetFont(*templateFont);
5932 dc.GetTextExtent(text, &w, &h);
5933
5934 // If text is too long for the allocated field, try to reduce the text
5935 // string a bit.
5936 wxRect rect;
5937 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5938 rect);
5939 if (w && w > rect.width) {
5940 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5941 }
5942
5943 // Test again...if too big still, then give it up.
5944 dc.GetTextExtent(text, &w, &h);
5945
5946 if (w && w > rect.width) {
5947 b_noshow = true;
5948 }
5949 }
5950 }
5951
5952 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5953 }
5954 }
5955
5956 // Maintain member vLat/vLon
5957 m_vLat = VPoint.clat;
5958 m_vLon = VPoint.clon;
5959
5960 return b_ret;
5961}
5962
5963// Static Icon definitions for some symbols requiring
5964// scaling/rotation/translation Very specific wxDC draw commands are
5965// necessary to properly render these icons...See the code in
5966// ShipDraw()
5967
5968// This icon was adapted and scaled from the S52 Presentation Library
5969// version 3_03.
5970// Symbol VECGND02
5971
5972static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5973
5974// This ownship icon was adapted and scaled from the S52 Presentation
5975// Library version 3_03 Symbol OWNSHP05
5976static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5977 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5978
5979wxColour ChartCanvas::PredColor() {
5980 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5981 // visibility.
5982 if (SHIP_NORMAL == m_ownship_state)
5983 return GetGlobalColor("URED");
5984
5985 else if (SHIP_LOWACCURACY == m_ownship_state)
5986 return GetGlobalColor("YELO1");
5987
5988 return GetGlobalColor("NODTA");
5989}
5990
5991wxColour ChartCanvas::ShipColor() {
5992 // Establish ship color
5993 // It changes color based on GPS and Chart accuracy/availability
5994
5995 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5996
5997 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5998
5999 return GetGlobalColor("URED"); // default is OK
6000}
6001
6002void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
6003 wxPoint2DDouble lShipMidPoint) {
6004 dc.SetPen(wxPen(PredColor(), 2));
6005
6006 if (SHIP_NORMAL == m_ownship_state)
6007 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
6008 else
6009 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
6010
6011 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
6012 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
6013
6014 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
6015 lShipMidPoint.m_y);
6016 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
6017 lShipMidPoint.m_y + 12);
6018}
6019
6020void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
6021 wxPoint GPSOffsetPixels,
6022 wxPoint2DDouble lGPSPoint) {
6023 // if (m_animationActive) return;
6024 // Develop a uniform length for course predictor line dash length, based on
6025 // physical display size Use this reference length to size all other graphics
6026 // elements
6027 float ref_dim = m_display_size_mm / 24;
6028 ref_dim = wxMin(ref_dim, 12);
6029 ref_dim = wxMax(ref_dim, 6);
6030
6031 wxColour cPred;
6032 cPred.Set(g_cog_predictor_color);
6033 if (cPred == wxNullColour) cPred = PredColor();
6034
6035 // Establish some graphic element line widths dependent on the platform
6036 // display resolution
6037 // double nominal_line_width_pix = wxMax(1.0,
6038 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
6039 // not less than 1 pixel
6040 double nominal_line_width_pix = wxMax(
6041 1.0,
6042 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
6043
6044 // If the calculated value is greater than the config file spec value, then
6045 // use it.
6046 if (nominal_line_width_pix > g_cog_predictor_width)
6047 g_cog_predictor_width = nominal_line_width_pix;
6048
6049 // Calculate ownship Position Predictor
6050 wxPoint lPredPoint, lHeadPoint;
6051
6052 float pCog = std::isnan(gCog) ? 0 : gCog;
6053 float pSog = std::isnan(gSog) ? 0 : gSog;
6054
6055 double pred_lat, pred_lon;
6056 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
6057 &pred_lat, &pred_lon);
6058 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
6059
6060 // test to catch the case where COG/HDG line crosses the screen
6061 LLBBox box;
6062
6063 // Should we draw the Head vector?
6064 // Compare the points lHeadPoint and lPredPoint
6065 // If they differ by more than n pixels, and the head vector is valid, then
6066 // render the head vector
6067
6068 float ndelta_pix = 10.;
6069 double hdg_pred_lat, hdg_pred_lon;
6070 bool b_render_hdt = false;
6071 if (!std::isnan(gHdt)) {
6072 // Calculate ownship Heading pointer as a predictor
6073 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6074 &hdg_pred_lon);
6075 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6076 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6077 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6078 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6079 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6080 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6081 }
6082 }
6083
6084 // draw course over ground if they are longer than the ship
6085 wxPoint lShipMidPoint;
6086 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6087 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6088 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6089 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6090
6091 if (lpp >= img_height / 2) {
6092 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6093 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6094 !std::isnan(gSog)) {
6095 // COG Predictor
6096 float dash_length = ref_dim;
6097 wxDash dash_long[2];
6098 dash_long[0] =
6099 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6100 g_cog_predictor_width); // Long dash , in mm <---------+
6101 dash_long[1] = dash_long[0] / 2.0; // Short gap
6102
6103 // On ultra-hi-res displays, do not allow the dashes to be greater than
6104 // 250, since it is defined as (char)
6105 if (dash_length > 250.) {
6106 dash_long[0] = 250. / g_cog_predictor_width;
6107 dash_long[1] = dash_long[0] / 2;
6108 }
6109
6110 wxPen ppPen2(cPred, g_cog_predictor_width,
6111 (wxPenStyle)g_cog_predictor_style);
6112 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6113 ppPen2.SetDashes(2, dash_long);
6114 dc.SetPen(ppPen2);
6115 dc.StrokeLine(
6116 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6117 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6118
6119 if (g_cog_predictor_width > 1) {
6120 float line_width = g_cog_predictor_width / 3.;
6121
6122 wxDash dash_long3[2];
6123 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6124 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6125
6126 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6127 (wxPenStyle)g_cog_predictor_style);
6128 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6129 ppPen3.SetDashes(2, dash_long3);
6130 dc.SetPen(ppPen3);
6131 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6132 lGPSPoint.m_y + GPSOffsetPixels.y,
6133 lPredPoint.x + GPSOffsetPixels.x,
6134 lPredPoint.y + GPSOffsetPixels.y);
6135 }
6136
6137 if (g_cog_predictor_endmarker) {
6138 // Prepare COG predictor endpoint icon
6139 double png_pred_icon_scale_factor = .4;
6140 if (g_ShipScaleFactorExp > 1.0)
6141 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6142 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6143
6144 wxPoint icon[4];
6145
6146 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6147 (float)(lPredPoint.x - lShipMidPoint.x));
6148 cog_rad += (float)PI;
6149
6150 for (int i = 0; i < 4; i++) {
6151 int j = i * 2;
6152 double pxa = (double)(s_png_pred_icon[j]);
6153 double pya = (double)(s_png_pred_icon[j + 1]);
6154
6155 pya *= png_pred_icon_scale_factor;
6156 pxa *= png_pred_icon_scale_factor;
6157
6158 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6159 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6160
6161 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6162 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6163 }
6164
6165 // Render COG endpoint icon
6166 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6167 wxPENSTYLE_SOLID);
6168 dc.SetPen(ppPen1);
6169 dc.SetBrush(wxBrush(cPred));
6170
6171 dc.StrokePolygon(4, icon);
6172 }
6173 }
6174 }
6175
6176 // HDT Predictor
6177 if (b_render_hdt) {
6178 float hdt_dash_length = ref_dim * 0.4;
6179
6180 cPred.Set(g_ownship_HDTpredictor_color);
6181 if (cPred == wxNullColour) cPred = PredColor();
6182 float hdt_width =
6183 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6184 : g_cog_predictor_width * 0.8);
6185 wxDash dash_short[2];
6186 dash_short[0] =
6187 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6188 hdt_width); // Short dash , in mm <---------+
6189 dash_short[1] =
6190 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6191 hdt_width); // Short gap |
6192
6193 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6194 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6195 ppPen2.SetDashes(2, dash_short);
6196
6197 dc.SetPen(ppPen2);
6198 dc.StrokeLine(
6199 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6200 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6201
6202 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6203 dc.SetPen(ppPen1);
6204 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6205
6206 if (g_ownship_HDTpredictor_endmarker) {
6207 double nominal_circle_size_pixels = wxMax(
6208 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6209
6210 // Scale the circle to ChartScaleFactor, slightly softened....
6211 if (g_ShipScaleFactorExp > 1.0)
6212 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6213
6214 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6215 lHeadPoint.y + GPSOffsetPixels.y,
6216 nominal_circle_size_pixels / 2);
6217 }
6218 }
6219
6220 // Draw radar rings if activated
6221 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6222 double factor = 1.00;
6223 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6224 factor = 1 / 1.852;
6225 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6226 if (std::isnan(gSog))
6227 factor = 0.0;
6228 else
6229 factor = gSog / 60;
6230 }
6231 factor *= g_fNavAidRadarRingsStep;
6232
6233 double tlat, tlon;
6234 wxPoint r;
6235 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6236 GetCanvasPointPix(tlat, tlon, &r);
6237
6238 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6239 pow((double)(lGPSPoint.m_y - r.y), 2));
6240 int pix_radius = (int)lpp;
6241
6242 wxColor rangeringcolour =
6243 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6244
6245 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6246
6247 dc.SetPen(ppPen1);
6248 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6249
6250 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6251 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6252 }
6253}
6254
6255#if ocpnUSE_GL
6256void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6257bool ChartCanvas::CanAccelerateGlPanning() {
6258 return GetglCanvas()->CanAcceleratePanning();
6259}
6260void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6261
6262#else
6263void ChartCanvas::ResetGlGridFont() {}
6264bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6265void ChartCanvas::SetupGlCompression() {}
6266#endif
6267
6268void ChartCanvas::ComputeShipScaleFactor(
6269 float icon_hdt, int ownShipWidth, int ownShipLength,
6270 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6271 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6272 float screenResolution = m_pix_per_mm;
6273
6274 // Calculate the true ship length in exact pixels
6275 double ship_bow_lat, ship_bow_lon;
6276 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6277 &ship_bow_lat, &ship_bow_lon);
6278 wxPoint lShipBowPoint;
6279 wxPoint2DDouble b_point =
6280 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6281 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6282
6283 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6284 powf((float)(b_point.m_y - a_point.m_y), 2));
6285
6286 // And in mm
6287 float shipLength_mm = shipLength_px / screenResolution;
6288
6289 // Set minimum ownship drawing size
6290 float ownship_min_mm = g_n_ownship_min_mm;
6291 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6292
6293 // Calculate Nautical Miles distance from midships to gps antenna
6294 float hdt_ant = icon_hdt + 180.;
6295 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6296 float dx = g_n_gps_antenna_offset_x / 1852.;
6297 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6298 {
6299 hdt_ant = icon_hdt;
6300 dy = -dy;
6301 }
6302
6303 // If the drawn ship size is going to be clamped, adjust the gps antenna
6304 // offsets
6305 if (shipLength_mm < ownship_min_mm) {
6306 dy /= shipLength_mm / ownship_min_mm;
6307 dx /= shipLength_mm / ownship_min_mm;
6308 }
6309
6310 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6311
6312 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6313 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6314 &ship_mid_lon1);
6315
6316 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6317 &lShipMidPoint);
6318
6319 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6320 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6321
6322 float scale_factor = shipLength_px / ownShipLength;
6323
6324 // Calculate a scale factor that would produce a reasonably sized icon
6325 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6326
6327 // And choose the correct one
6328 scale_factor = wxMax(scale_factor, scale_factor_min);
6329
6330 scale_factor_y = scale_factor;
6331 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6332 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6333}
6334
6335void ChartCanvas::ShipDraw(ocpnDC &dc) {
6336 if (!GetVP().IsValid()) return;
6337
6338 wxPoint GPSOffsetPixels(0, 0);
6339 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6340
6341 // COG/SOG may be undefined in NMEA data stream
6342 float pCog = std::isnan(gCog) ? 0 : gCog;
6343 float pSog = std::isnan(gSog) ? 0 : gSog;
6344
6345 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6346
6347 lShipMidPoint = lGPSPoint;
6348
6349 // Draw the icon rotated to the COG
6350 // or to the Hdt if available
6351 float icon_hdt = pCog;
6352 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6353
6354 // COG may be undefined in NMEA data stream
6355 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6356
6357 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6358 // predictor
6359 double osd_head_lat, osd_head_lon;
6360 wxPoint osd_head_point;
6361
6362 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6363 &osd_head_lon);
6364
6365 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6366
6367 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6368 (float)(osd_head_point.x - lShipMidPoint.m_x));
6369 icon_rad += (float)PI;
6370
6371 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6372
6373 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6374 // nominal size and is just barely outside the viewport ....
6375 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6376
6377 // TODO: fix to include actual size of boat that will be rendered
6378 int img_height = 0;
6379 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6380 if (GetVP().chart_scale >
6381 300000) // According to S52, this should be 50,000
6382 {
6383 ShipDrawLargeScale(dc, lShipMidPoint);
6384 img_height = 20;
6385 } else {
6386 wxImage pos_image;
6387
6388 // Substitute user ownship image if found
6389 if (m_pos_image_user)
6390 pos_image = m_pos_image_user->Copy();
6391 else if (SHIP_NORMAL == m_ownship_state)
6392 pos_image = m_pos_image_red->Copy();
6393 if (SHIP_LOWACCURACY == m_ownship_state)
6394 pos_image = m_pos_image_yellow->Copy();
6395 else if (SHIP_NORMAL != m_ownship_state)
6396 pos_image = m_pos_image_grey->Copy();
6397
6398 // Substitute user ownship image if found
6399 if (m_pos_image_user) {
6400 pos_image = m_pos_image_user->Copy();
6401
6402 if (SHIP_LOWACCURACY == m_ownship_state)
6403 pos_image = m_pos_image_user_yellow->Copy();
6404 else if (SHIP_NORMAL != m_ownship_state)
6405 pos_image = m_pos_image_user_grey->Copy();
6406 }
6407
6408 img_height = pos_image.GetHeight();
6409
6410 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6411 g_OwnShipIconType > 0) // use large ship
6412 {
6413 int ownShipWidth = 22; // Default values from s_ownship_icon
6414 int ownShipLength = 84;
6415 if (g_OwnShipIconType == 1) {
6416 ownShipWidth = pos_image.GetWidth();
6417 ownShipLength = pos_image.GetHeight();
6418 }
6419
6420 float scale_factor_x, scale_factor_y;
6421 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6422 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6423 scale_factor_x, scale_factor_y);
6424
6425 if (g_OwnShipIconType == 1) { // Scaled bitmap
6426 pos_image.Rescale(ownShipWidth * scale_factor_x,
6427 ownShipLength * scale_factor_y,
6428 wxIMAGE_QUALITY_HIGH);
6429 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6430 wxImage rot_image =
6431 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6432
6433 // Simple sharpening algorithm.....
6434 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6435 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6436 if (rot_image.GetAlpha(ip, jp) > 64)
6437 rot_image.SetAlpha(ip, jp, 255);
6438
6439 wxBitmap os_bm(rot_image);
6440
6441 int w = os_bm.GetWidth();
6442 int h = os_bm.GetHeight();
6443 img_height = h;
6444
6445 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6446 lShipMidPoint.m_y - h / 2, true);
6447
6448 // Maintain dirty box,, missing in __WXMSW__ library
6449 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6450 lShipMidPoint.m_y - h / 2);
6451 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6452 lShipMidPoint.m_y - h / 2 + h);
6453 }
6454
6455 else if (g_OwnShipIconType == 2) { // Scaled Vector
6456 wxPoint ownship_icon[10];
6457
6458 for (int i = 0; i < 10; i++) {
6459 int j = i * 2;
6460 float pxa = (float)(s_ownship_icon[j]);
6461 float pya = (float)(s_ownship_icon[j + 1]);
6462 pya *= scale_factor_y;
6463 pxa *= scale_factor_x;
6464
6465 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6466 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6467
6468 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6469 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6470 }
6471
6472 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6473 dc.SetPen(ppPen1);
6474 dc.SetBrush(wxBrush(ShipColor()));
6475
6476 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6477
6478 // draw reference point (midships) cross
6479 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6480 ownship_icon[7].y);
6481 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6482 ownship_icon[9].y);
6483 }
6484
6485 img_height = ownShipLength * scale_factor_y;
6486
6487 // Reference point, where the GPS antenna is
6488 int circle_rad = 3;
6489 if (m_pos_image_user) circle_rad = 1;
6490
6491 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6492 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6493 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6494 } else { // Fixed bitmap icon.
6495 /* non opengl, or suboptimal opengl via ocpndc: */
6496 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6497 wxImage rot_image =
6498 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6499
6500 // Simple sharpening algorithm.....
6501 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6502 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6503 if (rot_image.GetAlpha(ip, jp) > 64)
6504 rot_image.SetAlpha(ip, jp, 255);
6505
6506 wxBitmap os_bm(rot_image);
6507
6508 if (g_ShipScaleFactorExp > 1) {
6509 wxImage scaled_image = os_bm.ConvertToImage();
6510 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6511 1.0; // soften the scale factor a bit
6512 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6513 scaled_image.GetHeight() * factor,
6514 wxIMAGE_QUALITY_HIGH));
6515 }
6516 int w = os_bm.GetWidth();
6517 int h = os_bm.GetHeight();
6518 img_height = h;
6519
6520 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6521 lShipMidPoint.m_y - h / 2, true);
6522
6523 // Reference point, where the GPS antenna is
6524 int circle_rad = 3;
6525 if (m_pos_image_user) circle_rad = 1;
6526
6527 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6528 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6529 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6530
6531 // Maintain dirty box,, missing in __WXMSW__ library
6532 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6533 lShipMidPoint.m_y - h / 2);
6534 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6535 lShipMidPoint.m_y - h / 2 + h);
6536 }
6537 } // ownship draw
6538 }
6539
6540 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6541}
6542
6543/* @ChartCanvas::CalcGridSpacing
6544 **
6545 ** Calculate the major and minor spacing between the lat/lon grid
6546 **
6547 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6548 *window
6549 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6550 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6551 ** @return [void]
6552 */
6553void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6554 float &MinorSpacing) {
6555 // table for calculating the distance between the grids
6556 // [0] view_scale ppm
6557 // [1] spacing between major grid lines in degrees
6558 // [2] spacing between minor grid lines in degrees
6559 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6560 {.000001f, 45.0f, 15.0f},
6561 {.0002f, 30.0f, 10.0f},
6562 {.0003f, 10.0f, 2.0f},
6563 {.0008f, 5.0f, 1.0f},
6564 {.001f, 2.0f, 30.0f / 60.0f},
6565 {.003f, 1.0f, 20.0f / 60.0f},
6566 {.006f, 0.5f, 10.0f / 60.0f},
6567 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6568 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6569 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6570 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6571 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6572 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6573 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6574 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6575
6576 unsigned int tabi;
6577 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6578 if (view_scale_ppm < lltab[tabi][0]) break;
6579 MajorSpacing = lltab[tabi][1]; // major latitude distance
6580 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6581 return;
6582}
6583/* @ChartCanvas::CalcGridText *************************************
6584 **
6585 ** Calculates text to display at the major grid lines
6586 **
6587 ** @param [r] latlon [float] latitude or longitude of grid line
6588 ** @param [r] spacing [float] distance between two major grid lines
6589 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6590 **
6591 ** @return
6592 */
6593
6594wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6595 int deg = (int)fabs(latlon); // degrees
6596 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6597 char postfix;
6598
6599 // calculate postfix letter (NSEW)
6600 if (latlon > 0.0) {
6601 if (bPostfix) {
6602 postfix = 'N';
6603 } else {
6604 postfix = 'E';
6605 }
6606 } else if (latlon < 0.0) {
6607 if (bPostfix) {
6608 postfix = 'S';
6609 } else {
6610 postfix = 'W';
6611 }
6612 } else {
6613 postfix = ' '; // no postfix for equator and greenwich
6614 }
6615 // calculate text, display minutes only if spacing is smaller than one degree
6616
6617 wxString ret;
6618 if (spacing >= 1.0) {
6619 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6620 } else if (spacing >= (1.0 / 60.0)) {
6621 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6622 } else {
6623 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6624 }
6625
6626 return ret;
6627}
6628
6629/* @ChartCanvas::GridDraw *****************************************
6630 **
6631 ** Draws major and minor Lat/Lon Grid on the chart
6632 ** - distance between Grid-lm ines are calculated automatic
6633 ** - major grid lines will be across the whole chart window
6634 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6635 **
6636 ** @param [w] dc [wxDC&] the wx drawing context
6637 **
6638 ** @return [void]
6639 ************************************************************************/
6640void ChartCanvas::GridDraw(ocpnDC &dc) {
6641 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6642
6643 double nlat, elon, slat, wlon;
6644 float lat, lon;
6645 float dlon;
6646 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6647 wxCoord w, h;
6648 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6649 dc.SetPen(GridPen);
6650 if (!m_pgridFont) SetupGridFont();
6651 dc.SetFont(*m_pgridFont);
6652 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6653
6654 w = m_canvas_width;
6655 h = m_canvas_height;
6656
6657 GetCanvasPixPoint(0, 0, nlat,
6658 wlon); // get lat/lon of upper left point of the window
6659 GetCanvasPixPoint(w, h, slat,
6660 elon); // get lat/lon of lower right point of the window
6661 dlon =
6662 elon -
6663 wlon; // calculate how many degrees of longitude are shown in the window
6664 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6665 {
6666 dlon = dlon + 360.0;
6667 }
6668 // calculate distance between latitude grid lines
6669 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6670
6671 // calculate position of first major latitude grid line
6672 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6673
6674 // Draw Major latitude grid lines and text
6675 while (lat < nlat) {
6676 wxPoint r;
6677 wxString st =
6678 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6679 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6680 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6681 dc.DrawText(st, 0, r.y); // draw text
6682 lat = lat + gridlatMajor;
6683
6684 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6685 }
6686
6687 // calculate position of first minor latitude grid line
6688 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6689
6690 // Draw minor latitude grid lines
6691 while (lat < nlat) {
6692 wxPoint r;
6693 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6694 dc.DrawLine(0, r.y, 10, r.y, false);
6695 dc.DrawLine(w - 10, r.y, w, r.y, false);
6696 lat = lat + gridlatMinor;
6697 }
6698
6699 // calculate distance between grid lines
6700 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6701
6702 // calculate position of first major latitude grid line
6703 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6704
6705 // draw major longitude grid lines
6706 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6707 wxPoint r;
6708 wxString st = CalcGridText(lon, gridlonMajor, false);
6709 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6710 dc.DrawLine(r.x, 0, r.x, h, false);
6711 dc.DrawText(st, r.x, 0);
6712 lon = lon + gridlonMajor;
6713 if (lon > 180.0) {
6714 lon = lon - 360.0;
6715 }
6716
6717 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6718 }
6719
6720 // calculate position of first minor longitude grid line
6721 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6722 // draw minor longitude grid lines
6723 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6724 wxPoint r;
6725 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6726 dc.DrawLine(r.x, 0, r.x, 10, false);
6727 dc.DrawLine(r.x, h - 10, r.x, h, false);
6728 lon = lon + gridlonMinor;
6729 if (lon > 180.0) {
6730 lon = lon - 360.0;
6731 }
6732 }
6733}
6734
6735void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6736 if (0 ) {
6737 double blat, blon, tlat, tlon;
6738 wxPoint r;
6739
6740 int x_origin = m_bDisplayGrid ? 60 : 20;
6741 int y_origin = m_canvas_height - 50;
6742
6743 float dist;
6744 int count;
6745 wxPen pen1, pen2;
6746
6747 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6748 {
6749 dist = 10.0;
6750 count = 5;
6751 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6752 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6753 } else // Draw 1 mile scale as SCALEB10
6754 {
6755 dist = 1.0;
6756 count = 10;
6757 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6758 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6759 }
6760
6761 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6762 double rotation = -VPoint.rotation;
6763
6764 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6765 GetCanvasPointPix(tlat, tlon, &r);
6766 int l1 = (y_origin - r.y) / count;
6767
6768 for (int i = 0; i < count; i++) {
6769 int y = l1 * i;
6770 if (i & 1)
6771 dc.SetPen(pen1);
6772 else
6773 dc.SetPen(pen2);
6774
6775 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6776 }
6777 } else {
6778 double blat, blon, tlat, tlon;
6779
6780 int x_origin = 5.0 * GetPixPerMM();
6781 int chartbar_height = GetChartbarHeight();
6782 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6783 // if (style->chartStatusWindowTransparent)
6784 // chartbar_height = 0;
6785 int y_origin = m_canvas_height - chartbar_height - 5;
6786#ifdef __WXOSX__
6787 if (!g_bopengl)
6788 y_origin =
6789 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6790#endif
6791
6792 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6793 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6794
6795 double d;
6796 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6797 d /= 2;
6798
6799 int unit = g_iDistanceFormat;
6800 if (d < .5 &&
6801 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6802 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6803
6804 // nice number
6805 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6806 float places = floor(logdist), rem = logdist - places;
6807 dist = pow(10, places);
6808
6809 if (rem < .2)
6810 dist /= 5;
6811 else if (rem < .5)
6812 dist /= 2;
6813
6814 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6815 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6816 double rotation = -VPoint.rotation;
6817
6818 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6819 &tlat, &tlon);
6820 wxPoint r;
6821 GetCanvasPointPix(tlat, tlon, &r);
6822 int l1 = r.x - x_origin;
6823
6824 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6825 12); // Store this for later reference
6826
6827 dc.SetPen(pen1);
6828
6829 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6830 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6831 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6832
6833 if (!m_pgridFont) SetupGridFont();
6834 dc.SetFont(*m_pgridFont);
6835 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6836 int w, h;
6837 dc.GetTextExtent(s, &w, &h);
6838 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6839 if (g_bopengl) {
6840 w /= dpi_factor;
6841 h /= dpi_factor;
6842 }
6843 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6844 }
6845}
6846
6847void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6848 // Constants?
6849 double da_min = 2.;
6850 double da_max = 6.;
6851 double ra_min = 0.;
6852 double ra_max = 40.;
6853
6854 wxPen pen_save = dc.GetPen();
6855
6856 wxDateTime now = wxDateTime::Now();
6857
6858 dc.SetPen(pen);
6859
6860 int x0, y0, x1, y1;
6861
6862 x0 = x1 = x + radius; // Start point
6863 y0 = y1 = y;
6864 double angle = 0.;
6865 int i = 0;
6866
6867 while (angle < 360.) {
6868 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6869 angle += da;
6870
6871 if (angle > 360.) angle = 360.;
6872
6873 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6874
6875 double r;
6876 if (i & 1)
6877 r = radius + ra;
6878 else
6879 r = radius - ra;
6880
6881 x1 = (int)(x + cos(angle * PI / 180.) * r);
6882 y1 = (int)(y + sin(angle * PI / 180.) * r);
6883
6884 dc.DrawLine(x0, y0, x1, y1);
6885
6886 x0 = x1;
6887 y0 = y1;
6888
6889 i++;
6890 }
6891
6892 dc.DrawLine(x + radius, y, x1, y1); // closure
6893
6894 dc.SetPen(pen_save);
6895}
6896
6897static bool bAnchorSoundPlaying = false;
6898
6899static void onAnchorSoundFinished(void *ptr) {
6900 o_sound::g_anchorwatch_sound->UnLoad();
6901 bAnchorSoundPlaying = false;
6902}
6903
6904void ChartCanvas::AlertDraw(ocpnDC &dc) {
6905 using namespace o_sound;
6906 // Visual and audio alert for anchorwatch goes here
6907 bool play_sound = false;
6908 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6909 if (AnchorAlertOn1) {
6910 wxPoint TargetPoint;
6912 &TargetPoint);
6913 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6914 TargetPoint.y, 100);
6915 play_sound = true;
6916 }
6917 } else
6918 AnchorAlertOn1 = false;
6919
6920 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6921 if (AnchorAlertOn2) {
6922 wxPoint TargetPoint;
6924 &TargetPoint);
6925 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6926 TargetPoint.y, 100);
6927 play_sound = true;
6928 }
6929 } else
6930 AnchorAlertOn2 = false;
6931
6932 if (play_sound) {
6933 if (!bAnchorSoundPlaying) {
6934 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6935 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6936 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6937 if (g_anchorwatch_sound->IsOk()) {
6938 bAnchorSoundPlaying = true;
6939 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6940 g_anchorwatch_sound->Play();
6941 }
6942 }
6943 }
6944}
6945
6946void ChartCanvas::UpdateShips() {
6947 // Get the rectangle in the current dc which bounds the "ownship" symbol
6948
6949 wxClientDC dc(this);
6950 // Sometimes during startup the dc x or y size is zero, which causes the
6951 // bitmap creation to fail. Just skip the update in this case.
6952 if (!dc.IsOk() || dc.GetSize().x < 1 || dc.GetSize().y < 1) return;
6953
6954 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6955 if (!test_bitmap.IsOk()) return;
6956
6957 wxMemoryDC temp_dc(test_bitmap);
6958
6959 temp_dc.ResetBoundingBox();
6960 temp_dc.DestroyClippingRegion();
6961 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6962
6963 // Draw the ownship on the temp_dc
6964 ocpnDC ocpndc = ocpnDC(temp_dc);
6965 ShipDraw(ocpndc);
6966
6967 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6968 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6969 if (p) {
6970 wxPoint px;
6971 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6972 ocpndc.CalcBoundingBox(px.x, px.y);
6973 }
6974 }
6975
6976 ship_draw_rect =
6977 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6978 temp_dc.MaxY() - temp_dc.MinY());
6979
6980 wxRect own_ship_update_rect = ship_draw_rect;
6981
6982 if (!own_ship_update_rect.IsEmpty()) {
6983 // The required invalidate rectangle is the union of the last drawn
6984 // rectangle and this drawn rectangle
6985 own_ship_update_rect.Union(ship_draw_last_rect);
6986 own_ship_update_rect.Inflate(2);
6987 }
6988
6989 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6990
6991 ship_draw_last_rect = ship_draw_rect;
6992
6993 temp_dc.SelectObject(wxNullBitmap);
6994}
6995
6996void ChartCanvas::UpdateAlerts() {
6997 // Get the rectangle in the current dc which bounds the detected Alert
6998 // targets
6999
7000 // Use this dc
7001 wxClientDC dc(this);
7002
7003 // Get dc boundary
7004 int sx, sy;
7005 dc.GetSize(&sx, &sy);
7006
7007 // Need a bitmap
7008 wxBitmap test_bitmap(sx, sy, -1);
7009
7010 // Create a memory DC
7011 wxMemoryDC temp_dc;
7012 temp_dc.SelectObject(test_bitmap);
7013
7014 temp_dc.ResetBoundingBox();
7015 temp_dc.DestroyClippingRegion();
7016 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7017
7018 // Draw the Alert Targets on the temp_dc
7019 ocpnDC ocpndc = ocpnDC(temp_dc);
7020 AlertDraw(ocpndc);
7021
7022 // Retrieve the drawing extents
7023 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
7024 temp_dc.MaxX() - temp_dc.MinX(),
7025 temp_dc.MaxY() - temp_dc.MinY());
7026
7027 if (!alert_rect.IsEmpty())
7028 alert_rect.Inflate(2); // clear all drawing artifacts
7029
7030 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
7031 // The required invalidate rectangle is the union of the last drawn
7032 // rectangle and this drawn rectangle
7033 wxRect alert_update_rect = alert_draw_rect;
7034 alert_update_rect.Union(alert_rect);
7035
7036 // Invalidate the rectangular region
7037 RefreshRect(alert_update_rect, false);
7038 }
7039
7040 // Save this rectangle for next time
7041 alert_draw_rect = alert_rect;
7042
7043 temp_dc.SelectObject(wxNullBitmap); // clean up
7044}
7045
7046void ChartCanvas::UpdateAIS() {
7047 if (!g_pAIS) return;
7048
7049 // Get the rectangle in the current dc which bounds the detected AIS targets
7050
7051 // Use this dc
7052 wxClientDC dc(this);
7053
7054 // Get dc boundary
7055 int sx, sy;
7056 dc.GetSize(&sx, &sy);
7057
7058 wxRect ais_rect;
7059
7060 // How many targets are there?
7061
7062 // If more than "some number", it will be cheaper to refresh the entire
7063 // screen than to build update rectangles for each target.
7064 if (g_pAIS->GetTargetList().size() > 10) {
7065 ais_rect = wxRect(0, 0, sx, sy); // full screen
7066 } else {
7067 // Need a bitmap
7068 wxBitmap test_bitmap(sx, sy, -1);
7069
7070 // Create a memory DC
7071 wxMemoryDC temp_dc;
7072 temp_dc.SelectObject(test_bitmap);
7073
7074 temp_dc.ResetBoundingBox();
7075 temp_dc.DestroyClippingRegion();
7076 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7077
7078 // Draw the AIS Targets on the temp_dc
7079 ocpnDC ocpndc = ocpnDC(temp_dc);
7080 AISDraw(ocpndc, GetVP(), this);
7081 AISDrawAreaNotices(ocpndc, GetVP(), this);
7082
7083 // Retrieve the drawing extents
7084 ais_rect =
7085 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7086 temp_dc.MaxY() - temp_dc.MinY());
7087
7088 if (!ais_rect.IsEmpty())
7089 ais_rect.Inflate(2); // clear all drawing artifacts
7090
7091 temp_dc.SelectObject(wxNullBitmap); // clean up
7092 }
7093
7094 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7095 // The required invalidate rectangle is the union of the last drawn
7096 // rectangle and this drawn rectangle
7097 wxRect ais_update_rect = ais_draw_rect;
7098 ais_update_rect.Union(ais_rect);
7099
7100 // Invalidate the rectangular region
7101 RefreshRect(ais_update_rect, false);
7102 }
7103
7104 // Save this rectangle for next time
7105 ais_draw_rect = ais_rect;
7106}
7107
7108void ChartCanvas::ToggleCPAWarn() {
7109 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7110 wxString mess;
7111 if (g_bCPAWarn) {
7112 g_bTCPA_Max = true;
7113 mess = _("ON");
7114 } else {
7115 g_bTCPA_Max = false;
7116 mess = _("OFF");
7117 }
7118 // Print to status bar if available.
7119 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7120 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7121 } else {
7122 if (!g_AisFirstTimeUse) {
7123 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7124 _("CPA") + " " + mess, 4, 4);
7125 }
7126 }
7127}
7128
7129void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7130
7131void ChartCanvas::OnSize(wxSizeEvent &event) {
7132 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7133 // GetClientSize returns the size of the canvas area in logical pixels.
7134 GetClientSize(&m_canvas_width, &m_canvas_height);
7135
7136#ifdef __WXOSX__
7137 // Support scaled HDPI displays.
7138 m_displayScale = GetContentScaleFactor();
7139#endif
7140
7141 // Convert to physical pixels.
7142 m_canvas_width *= m_displayScale;
7143 m_canvas_height *= m_displayScale;
7144
7145 // Resize the current viewport
7146 VPoint.pix_width = m_canvas_width;
7147 VPoint.pix_height = m_canvas_height;
7148 VPoint.SetPixelScale(m_displayScale);
7149
7150 // Get some canvas metrics
7151
7152 // Rescale to current value, in order to rebuild VPoint data
7153 // structures for new canvas size
7155
7156 m_absolute_min_scale_ppm =
7157 m_canvas_width /
7158 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7159
7160 // Inform the parent Frame that I am being resized...
7161 top_frame::Get()->ProcessCanvasResize();
7162
7163 // if MUIBar is active, size the bar
7164 // if(g_useMUI && !m_muiBar){ // rebuild if
7165 // necessary
7166 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7167 // m_muiBarHOSize = m_muiBar->GetSize();
7168 // }
7169
7170 if (m_muiBar) {
7171 SetMUIBarPosition();
7172 UpdateFollowButtonState();
7173 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7174 }
7175
7176 // Set up the scroll margins
7177 xr_margin = m_canvas_width * 95 / 100;
7178 xl_margin = m_canvas_width * 5 / 100;
7179 yt_margin = m_canvas_height * 5 / 100;
7180 yb_margin = m_canvas_height * 95 / 100;
7181
7182 if (m_pQuilt)
7183 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7184
7185 // Resize the scratch BM
7186 delete pscratch_bm;
7187 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7188 m_brepaint_piano = true;
7189
7190 // Resize the Route Calculation BM
7191 m_dc_route.SelectObject(wxNullBitmap);
7192 delete proute_bm;
7193 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7194 m_dc_route.SelectObject(*proute_bm);
7195
7196 // Resize the saved Bitmap
7197 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7198
7199 // Resize the working Bitmap
7200 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7201
7202 // Rescale again, to capture all the changes for new canvas size
7204
7205#ifdef ocpnUSE_GL
7206 if (/*g_bopengl &&*/ m_glcc) {
7207 // FIXME (dave) This can go away?
7208 m_glcc->OnSize(event);
7209 }
7210#endif
7211
7212 FormatPianoKeys();
7213 // Invalidate the whole window
7214 ReloadVP();
7215}
7216
7217void ChartCanvas::ProcessNewGUIScale() {
7218 // m_muiBar->Hide();
7219 delete m_muiBar;
7220 m_muiBar = 0;
7221
7222 CreateMUIBar();
7223}
7224
7225void ChartCanvas::CreateMUIBar() {
7226 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7227 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7228 m_muiBar->SetColorScheme(m_cs);
7229 m_muiBarHOSize = m_muiBar->m_size;
7230 }
7231
7232 if (m_muiBar) {
7233 // We need to update the m_bENCGroup flag, not least for the initial
7234 // creation of a MUIBar
7235 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7236
7237 SetMUIBarPosition();
7238 UpdateFollowButtonState();
7239 m_muiBar->UpdateDynamicValues();
7240 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7241 }
7242}
7243
7244void ChartCanvas::SetMUIBarPosition() {
7245 // if MUIBar is active, size the bar
7246 if (m_muiBar) {
7247 // We estimate the piano width based on the canvas width
7248 int pianoWidth = GetClientSize().x * 0.6f;
7249 // If the piano already exists, we can use its exact width
7250 // if(m_Piano)
7251 // pianoWidth = m_Piano->GetWidth();
7252
7253 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7254 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7255 delete m_muiBar;
7256 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7257 m_muiBar->SetColorScheme(m_cs);
7258 }
7259 }
7260
7261 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7262 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7263 delete m_muiBar;
7264 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7265 m_muiBar->SetColorScheme(m_cs);
7266 }
7267 }
7268
7269 m_muiBar->SetBestPosition();
7270 }
7271}
7272
7273void ChartCanvas::DestroyMuiBar() {
7274 if (m_muiBar) {
7275 delete m_muiBar;
7276 m_muiBar = NULL;
7277 }
7278}
7279
7280void ChartCanvas::ShowCompositeInfoWindow(
7281 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7282 if (n_charts > 0) {
7283 if (NULL == m_pCIWin) {
7284 m_pCIWin = new ChInfoWin(this);
7285 m_pCIWin->Hide();
7286 }
7287
7288 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7289 wxString s;
7290
7291 s = _("Composite of ");
7292
7293 wxString s1;
7294 s1.Printf("%d ", n_charts);
7295 if (n_charts > 1)
7296 s1 += _("charts");
7297 else
7298 s1 += _("chart");
7299 s += s1;
7300 s += '\n';
7301
7302 s1.Printf(_("Chart scale"));
7303 s1 += ": ";
7304 wxString s2;
7305 s2.Printf("1:%d\n", scale);
7306 s += s1;
7307 s += s2;
7308
7309 s1 = _("Zoom in for more information");
7310 s += s1;
7311 s += '\n';
7312
7313 int char_width = s1.Length();
7314 int char_height = 3;
7315
7316 if (g_bChartBarEx) {
7317 s += '\n';
7318 int j = 0;
7319 for (int i : index_vector) {
7320 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7321 wxString path = cte.GetFullSystemPath();
7322 s += path;
7323 s += '\n';
7324 char_height++;
7325 char_width = wxMax(char_width, path.Length());
7326 if (j++ >= 9) break;
7327 }
7328 if (j >= 9) {
7329 s += " .\n .\n .\n";
7330 char_height += 3;
7331 }
7332 s += '\n';
7333 char_height += 1;
7334
7335 char_width += 4; // Fluff
7336 }
7337
7338 m_pCIWin->SetString(s);
7339
7340 m_pCIWin->FitToChars(char_width, char_height);
7341
7342 wxPoint p;
7343 p.x = x / GetContentScaleFactor();
7344 if ((p.x + m_pCIWin->GetWinSize().x) >
7345 (m_canvas_width / GetContentScaleFactor()))
7346 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7347 m_pCIWin->GetWinSize().x) /
7348 2; // centered
7349
7350 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7351 4 - m_pCIWin->GetWinSize().y;
7352
7353 m_pCIWin->dbIndex = 0;
7354 m_pCIWin->chart_scale = 0;
7355 m_pCIWin->SetPosition(p);
7356 m_pCIWin->SetBitmap();
7357 m_pCIWin->Refresh();
7358 m_pCIWin->Show();
7359 }
7360 } else {
7361 HideChartInfoWindow();
7362 }
7363}
7364
7365void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7366 if (dbIndex >= 0) {
7367 if (NULL == m_pCIWin) {
7368 m_pCIWin = new ChInfoWin(this);
7369 m_pCIWin->Hide();
7370 }
7371
7372 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7373 wxString s;
7374 ChartBase *pc = NULL;
7375
7376 // TOCTOU race but worst case will reload chart.
7377 // need to lock it or the background spooler may evict charts in
7378 // OpenChartFromDBAndLock
7379 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7380 pc = ChartData->OpenChartFromDBAndLock(
7381 dbIndex, FULL_INIT); // this must come from cache
7382
7383 int char_width, char_height;
7384 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7385 if (pc) ChartData->UnLockCacheChart(dbIndex);
7386
7387 m_pCIWin->SetString(s);
7388 m_pCIWin->FitToChars(char_width, char_height);
7389
7390 wxPoint p;
7391 p.x = x / GetContentScaleFactor();
7392 if ((p.x + m_pCIWin->GetWinSize().x) >
7393 (m_canvas_width / GetContentScaleFactor()))
7394 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7395 m_pCIWin->GetWinSize().x) /
7396 2; // centered
7397
7398 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7399 4 - m_pCIWin->GetWinSize().y;
7400
7401 m_pCIWin->dbIndex = dbIndex;
7402 m_pCIWin->SetPosition(p);
7403 m_pCIWin->SetBitmap();
7404 m_pCIWin->Refresh();
7405 m_pCIWin->Show();
7406 }
7407 } else {
7408 HideChartInfoWindow();
7409 }
7410}
7411
7412void ChartCanvas::HideChartInfoWindow() {
7413 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7414 m_pCIWin->Hide();
7415 m_pCIWin->Destroy();
7416 m_pCIWin = NULL;
7417
7418#ifdef __ANDROID__
7419 androidForceFullRepaint();
7420#endif
7421 }
7422}
7423
7424void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7425 wxMouseEvent ev(wxEVT_MOTION);
7426 ev.m_x = mouse_x;
7427 ev.m_y = mouse_y;
7428 ev.m_leftDown = mouse_leftisdown;
7429
7430 wxEvtHandler *evthp = GetEventHandler();
7431
7432 ::wxPostEvent(evthp, ev);
7433}
7434
7435void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7436 if ((m_panx_target_final - m_panx_target_now) ||
7437 (m_pany_target_final - m_pany_target_now)) {
7438 DoTimedMovementTarget();
7439 } else
7440 DoTimedMovement();
7441}
7442
7443void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7444
7445bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7446 int delta) {
7447 if (m_disable_edge_pan) return false;
7448
7449 bool bft = false;
7450 int pan_margin = m_canvas_width * margin / 100;
7451 int pan_timer_set = 200;
7452 double pan_delta = GetVP().pix_width * delta / 100;
7453 int pan_x = 0;
7454 int pan_y = 0;
7455
7456 if (x > m_canvas_width - pan_margin) {
7457 bft = true;
7458 pan_x = pan_delta;
7459 }
7460
7461 else if (x < pan_margin) {
7462 bft = true;
7463 pan_x = -pan_delta;
7464 }
7465
7466 if (y < pan_margin) {
7467 bft = true;
7468 pan_y = -pan_delta;
7469 }
7470
7471 else if (y > m_canvas_height - pan_margin) {
7472 bft = true;
7473 pan_y = pan_delta;
7474 }
7475
7476 // Of course, if dragging, and the mouse left button is not down, we must
7477 // stop the event injection
7478 if (bdragging) {
7479 if (!g_btouch) {
7480 wxMouseState state = ::wxGetMouseState();
7481#if wxCHECK_VERSION(3, 0, 0)
7482 if (!state.LeftIsDown())
7483#else
7484 if (!state.LeftDown())
7485#endif
7486 bft = false;
7487 }
7488 }
7489 if ((bft) && !pPanTimer->IsRunning()) {
7490 PanCanvas(pan_x, pan_y);
7491 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7492 return true;
7493 }
7494
7495 // This mouse event must not be due to pan timer event injector
7496 // Mouse is out of the pan zone, so prevent any orphan event injection
7497 if ((!bft) && pPanTimer->IsRunning()) {
7498 pPanTimer->Stop();
7499 }
7500
7501 return (false);
7502}
7503
7504// Look for waypoints at the current position.
7505// Used to determine what a mouse event should act on.
7506
7507void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7508 bool setBeingEdited) {
7509 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7510 m_pRoutePointEditTarget = NULL;
7511 m_pFoundPoint = NULL;
7512
7513 SelectItem *pFind = NULL;
7514 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7515 SelectableItemList SelList = pSelect->FindSelectionList(
7516 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7517 for (SelectItem *pFind : SelList) {
7518 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7519
7520 // Get an array of all routes using this point
7521 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7522 // TODO: delete m_pEditRouteArray after use?
7523
7524 // Use route array to determine actual visibility for the point
7525 bool brp_viz = false;
7526 if (m_pEditRouteArray) {
7527 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7528 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7529 if (pr->IsVisible()) {
7530 brp_viz = true;
7531 break;
7532 }
7533 }
7534 } else
7535 brp_viz = frp->IsVisible(); // isolated point
7536
7537 if (brp_viz) {
7538 // Use route array to rubberband all affected routes
7539 if (m_pEditRouteArray) // Editing Waypoint as part of route
7540 {
7541 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7542 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7543 pr->m_bIsBeingEdited = setBeingEdited;
7544 }
7545 m_bRouteEditing = setBeingEdited;
7546 } else // editing Mark
7547 {
7548 frp->m_bRPIsBeingEdited = setBeingEdited;
7549 m_bMarkEditing = setBeingEdited;
7550 }
7551
7552 m_pRoutePointEditTarget = frp;
7553 m_pFoundPoint = pFind;
7554 break; // out of the while(node)
7555 }
7556 } // for (SelectItem...
7557}
7558std::shared_ptr<HostApi121::PiPointContext>
7559ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7560 // General Right Click
7561 // Look for selectable objects
7562 double slat, slon;
7563 GetCanvasPixPoint(x, y, slat, slon);
7564
7565 SelectItem *pFindAIS = NULL;
7566 SelectItem *pFindRP;
7567 SelectItem *pFindRouteSeg;
7568 SelectItem *pFindTrackSeg;
7569 SelectItem *pFindCurrent = NULL;
7570 SelectItem *pFindTide = NULL;
7571
7572 // Get all the selectable things at the selected point
7573 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7574 if (m_bShowAIS) // look for AIS targets only if they are shown
7575 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7576 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7577 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7578 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7579
7580 if (m_bShowCurrent)
7581 pFindCurrent =
7582 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7583
7584 if (m_bShowTide) // look for tide stations
7585 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7586
7587 int seltype = 0;
7588
7589 // Try for AIS targets first
7590 int FoundAIS_MMSI = 0;
7591 if (pFindAIS) {
7592 FoundAIS_MMSI = pFindAIS->GetUserData();
7593
7594 // Make sure the target data is available
7595 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7596 seltype |= SELTYPE_AISTARGET;
7597 }
7598
7599 // Now the various Route Parts
7600
7601 RoutePoint *FoundRoutePoint = NULL;
7602 Route *SelectedRoute = NULL;
7603
7604 if (pFindRP) {
7605 RoutePoint *pFirstVizPoint = NULL;
7606 RoutePoint *pFoundActiveRoutePoint = NULL;
7607 RoutePoint *pFoundVizRoutePoint = NULL;
7608 Route *pSelectedActiveRoute = NULL;
7609 Route *pSelectedVizRoute = NULL;
7610
7611 // There is at least one routepoint, so get the whole list
7612 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7613 SelectableItemList SelList =
7614 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7615 for (SelectItem *pFindSel : SelList) {
7616 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7617
7618 // Get an array of all routes using this point
7619 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7620
7621 // Use route array (if any) to determine actual visibility for this point
7622 bool brp_viz = false;
7623 if (proute_array) {
7624 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7625 Route *pr = (Route *)proute_array->Item(ir);
7626 if (pr->IsVisible()) {
7627 brp_viz = true;
7628 break;
7629 }
7630 }
7631 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7632 // but still exists as a waypoint
7633 brp_viz = prp->IsVisible(); // so treat as isolated point
7634
7635 } else
7636 brp_viz = prp->IsVisible(); // isolated point
7637
7638 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7639
7640 // Use route array to choose the appropriate route
7641 // Give preference to any active route, otherwise select the first visible
7642 // route in the array for this point
7643 if (proute_array) {
7644 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7645 Route *pr = (Route *)proute_array->Item(ir);
7646 if (pr->m_bRtIsActive) {
7647 pSelectedActiveRoute = pr;
7648 pFoundActiveRoutePoint = prp;
7649 break;
7650 }
7651 }
7652
7653 if (NULL == pSelectedVizRoute) {
7654 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7655 Route *pr = (Route *)proute_array->Item(ir);
7656 if (pr->IsVisible()) {
7657 pSelectedVizRoute = pr;
7658 pFoundVizRoutePoint = prp;
7659 break;
7660 }
7661 }
7662 }
7663
7664 delete proute_array;
7665 }
7666 }
7667
7668 // Now choose the "best" selections
7669 if (pFoundActiveRoutePoint) {
7670 FoundRoutePoint = pFoundActiveRoutePoint;
7671 SelectedRoute = pSelectedActiveRoute;
7672 } else if (pFoundVizRoutePoint) {
7673 FoundRoutePoint = pFoundVizRoutePoint;
7674 SelectedRoute = pSelectedVizRoute;
7675 } else
7676 // default is first visible point in list
7677 FoundRoutePoint = pFirstVizPoint;
7678
7679 if (SelectedRoute) {
7680 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7681 } else if (FoundRoutePoint) {
7682 seltype |= SELTYPE_MARKPOINT;
7683 }
7684
7685 // Highlight the selected point, to verify the proper right click selection
7686#if 0
7687 if (m_pFoundRoutePoint) {
7688 m_pFoundRoutePoint->m_bPtIsSelected = true;
7689 wxRect wp_rect;
7690 RoutePointGui(*m_pFoundRoutePoint)
7691 .CalculateDCRect(m_dc_route, this, &wp_rect);
7692 RefreshRect(wp_rect, true);
7693 }
7694#endif
7695 }
7696
7697 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7698 // routes But call the popup handler with identifier appropriate to the type
7699 if (pFindRouteSeg) // there is at least one select item
7700 {
7701 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7702 SelectableItemList SelList =
7703 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7704
7705 if (NULL == SelectedRoute) // the case where a segment only is selected
7706 {
7707 // Choose the first visible route containing segment in the list
7708 for (SelectItem *pFindSel : SelList) {
7709 Route *pr = (Route *)pFindSel->m_pData3;
7710 if (pr->IsVisible()) {
7711 SelectedRoute = pr;
7712 break;
7713 }
7714 }
7715 }
7716
7717 if (SelectedRoute) {
7718 if (NULL == FoundRoutePoint)
7719 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7720
7721 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7722 seltype |= SELTYPE_ROUTESEGMENT;
7723 }
7724 }
7725
7726 if (pFindTrackSeg) {
7727 m_pSelectedTrack = NULL;
7728 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7729 SelectableItemList SelList =
7730 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7731
7732 // Choose the first visible track containing segment in the list
7733 for (SelectItem *pFindSel : SelList) {
7734 Track *pt = (Track *)pFindSel->m_pData3;
7735 if (pt->IsVisible()) {
7736 m_pSelectedTrack = pt;
7737 break;
7738 }
7739 }
7740 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7741 }
7742
7743 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7744
7745 // Populate the return struct
7746 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7747 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7748 rstruct->object_ident = "";
7749
7750 if (seltype == SELTYPE_AISTARGET) {
7751 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7752 wxString val;
7753 val.Printf("%d", FoundAIS_MMSI);
7754 rstruct->object_ident = val.ToStdString();
7755 } else if (seltype & SELTYPE_MARKPOINT) {
7756 if (FoundRoutePoint) {
7757 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7758 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7759 }
7760 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7761 if (SelectedRoute) {
7762 rstruct->object_type =
7763 HostApi121::PiContextObjectType::kObjectRoutesegment;
7764 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7765 }
7766 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7767 if (m_pSelectedTrack) {
7768 rstruct->object_type =
7769 HostApi121::PiContextObjectType::kObjectTracksegment;
7770 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7771 }
7772 }
7773
7774 return rstruct;
7775}
7776
7777void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7778 if (singleClickEventIsValid) {
7779 const bool wasReplaying = m_isReplayingClickEvent;
7780 m_isReplayingClickEvent = true;
7781 MouseEvent(singleClickEvent);
7782 m_isReplayingClickEvent = wasReplaying;
7783 }
7784
7785 singleClickEventIsValid = false;
7786 m_DoubleClickTimer->Stop();
7787}
7788
7789bool leftIsDown;
7790
7791bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7792 if (!m_bChartDragging && !m_bDrawingRoute) {
7793 /*
7794 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7795 * mouse event coordinates are in logical pixels.
7796 */
7797 if (m_Compass && m_Compass->IsShown()) {
7798 wxRect logicalRect = m_Compass->GetLogicalRect();
7799 bool isInCompass = logicalRect.Contains(event.GetPosition());
7800 if (isInCompass || m_mouseWasInCompass) {
7801 if (m_Compass->MouseEvent(event)) {
7802 cursor_region = CENTER;
7803 if (!g_btouch) SetCanvasCursor(event);
7804 m_mouseWasInCompass = isInCompass;
7805 return true;
7806 }
7807 }
7808 m_mouseWasInCompass = isInCompass;
7809 }
7810
7811 if (m_notification_button && m_notification_button->IsShown()) {
7812 wxRect logicalRect = m_notification_button->GetLogicalRect();
7813 bool isinButton = logicalRect.Contains(event.GetPosition());
7814 if (isinButton) {
7815 SetCursor(*pCursorArrow);
7816 if (event.LeftDown()) HandleNotificationMouseClick();
7817 return true;
7818 }
7819 }
7820
7821 if (MouseEventToolbar(event)) return true;
7822
7823 if (MouseEventChartBar(event)) return true;
7824
7825 if (MouseEventMUIBar(event)) return true;
7826
7827 if (MouseEventIENCBar(event)) return true;
7828 }
7829 return false;
7830}
7831
7832void ChartCanvas::HandleNotificationMouseClick() {
7833 if (!m_NotificationsList) {
7834 m_NotificationsList = new NotificationsList(this);
7835
7836 // calculate best size for Notification list
7837 m_NotificationsList->RecalculateSize();
7838 m_NotificationsList->Hide();
7839 }
7840
7841 if (m_NotificationsList->IsShown()) {
7842 m_NotificationsList->Hide();
7843 } else {
7844 m_NotificationsList->RecalculateSize();
7845 m_NotificationsList->ReloadNotificationList();
7846 m_NotificationsList->Show();
7847 }
7848}
7849bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7850 if (!g_bShowChartBar) return false;
7851
7852 if (!m_Piano->MouseEvent(event)) return false;
7853
7854 cursor_region = CENTER;
7855 if (!g_btouch) SetCanvasCursor(event);
7856 return true;
7857}
7858
7859bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7860 if (!IsPrimaryCanvas()) return false;
7861
7862 if (g_MainToolbar) {
7863 if (!g_MainToolbar->MouseEvent(event))
7864 return false;
7865 else
7866 g_MainToolbar->RefreshToolbar();
7867 }
7868
7869 cursor_region = CENTER;
7870 if (!g_btouch) SetCanvasCursor(event);
7871 return true;
7872}
7873
7874bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7875 if (!IsPrimaryCanvas()) return false;
7876
7877 if (g_iENCToolbar) {
7878 if (!g_iENCToolbar->MouseEvent(event))
7879 return false;
7880 else {
7881 g_iENCToolbar->RefreshToolbar();
7882 return true;
7883 }
7884 }
7885 return false;
7886}
7887
7888bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7889 if (m_muiBar) {
7890 if (!m_muiBar->MouseEvent(event)) return false;
7891 }
7892
7893 cursor_region = CENTER;
7894 if (!g_btouch) SetCanvasCursor(event);
7895 if (m_muiBar)
7896 return true;
7897 else
7898 return false;
7899}
7900
7901bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7902 int x, y;
7903
7904 bool bret = false;
7905
7906 event.GetPosition(&x, &y);
7907
7908 x *= m_displayScale;
7909 y *= m_displayScale;
7910
7911 m_MouseDragging = event.Dragging();
7912
7913 // Some systems produce null drag events, where the pointer position has not
7914 // changed from the previous value. Detect this case, and abort further
7915 // processing (FS#1748)
7916#ifdef __WXMSW__
7917 if (event.Dragging()) {
7918 if ((x == mouse_x) && (y == mouse_y)) return true;
7919 }
7920#endif
7921
7922 mouse_x = x;
7923 mouse_y = y;
7924 mouse_leftisdown = event.LeftDown();
7926
7927 // Establish the event region
7928 cursor_region = CENTER;
7929
7930 int chartbar_height = GetChartbarHeight();
7931
7932 if (m_Compass && m_Compass->IsShown() &&
7933 m_Compass->GetRect().Contains(event.GetPosition())) {
7934 cursor_region = CENTER;
7935 } else if (x > xr_margin) {
7936 cursor_region = MID_RIGHT;
7937 } else if (x < xl_margin) {
7938 cursor_region = MID_LEFT;
7939 } else if (y > yb_margin - chartbar_height &&
7940 y < m_canvas_height - chartbar_height) {
7941 cursor_region = MID_TOP;
7942 } else if (y < yt_margin) {
7943 cursor_region = MID_BOT;
7944 } else {
7945 cursor_region = CENTER;
7946 }
7947
7948 if (!g_btouch) SetCanvasCursor(event);
7949
7950 // Protect from leftUp's coming from event handlers in child
7951 // windows who return focus to the canvas.
7952 leftIsDown = event.LeftDown();
7953
7954#ifndef __WXOSX__
7955 if (event.LeftDown()) {
7956 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7957 // The menu bar is temporarily visible due to alt having been pressed.
7958 // Clicking will hide it, and do nothing else.
7959 g_bTempShowMenuBar = false;
7960 top_frame::Get()->ApplyGlobalSettings(false);
7961 return (true);
7962 }
7963 }
7964#endif
7965
7966 // Update modifiers here; some window managers never send the key event
7967 m_modkeys = 0;
7968 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7969 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7970
7971#ifdef __WXMSW__
7972 // TODO Test carefully in other platforms, remove ifdef....
7973 // Avoid capture release on delayed/replayed timer events.
7974 if (!m_isReplayingClickEvent) {
7975 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7976 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7977 }
7978#endif
7979
7980 event.SetEventObject(this);
7981 if (SendMouseEventToPlugins(event))
7982 return (true); // PlugIn did something, and does not want the canvas to
7983 // do anything else
7984
7985 // Capture LeftUp's and time them, unless it already came from the timer.
7986
7987 // Detect end of chart dragging
7988 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7989 StartChartDragInertia();
7990 }
7991
7992 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7993 !singleClickEventIsValid) {
7994 // Ignore the second LeftUp after the DClick.
7995 if (m_DoubleClickTimer->IsRunning()) {
7996 m_DoubleClickTimer->Stop();
7997 return (true);
7998 }
7999
8000 // Save the event for later running if there is no DClick.
8001 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
8002 singleClickEvent = event;
8003 singleClickEventIsValid = true;
8004 return (true);
8005 }
8006
8007 // This logic is necessary on MSW to handle the case where
8008 // a context (right-click) menu is dismissed without action
8009 // by clicking on the chart surface.
8010 // We need to avoid an unintentional pan by eating some clicks...
8011#ifdef __WXMSW__
8012 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
8013 if (g_click_stop > 0) {
8014 g_click_stop--;
8015 return (true);
8016 }
8017 }
8018#endif
8019
8020 // Kick off the Rotation control timer
8021 if (GetUpMode() == COURSE_UP_MODE) {
8022 m_b_rot_hidef = false;
8023 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
8024 } else
8025 pRotDefTimer->Stop();
8026
8027 // Retrigger the route leg / AIS target popup timer
8028 bool bRoll = !g_btouch;
8029#ifdef __ANDROID__
8030 bRoll = g_bRollover;
8031#endif
8032 if (bRoll) {
8033 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
8034 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
8035 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
8036 m_RolloverPopupTimer.Start(
8037 10,
8038 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
8039 else
8040 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
8041 }
8042
8043 // Retrigger the cursor tracking timer
8044 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
8045
8046// Show cursor position on Status Bar, if present
8047// except for GTK, under which status bar updates are very slow
8048// due to Update() call.
8049// In this case, as a workaround, update the status window
8050// after an interval timer (pCurTrackTimer) pops, which will happen
8051// whenever the mouse has stopped moving for specified interval.
8052// See the method OnCursorTrackTimerEvent()
8053#if !defined(__WXGTK__) && !defined(__WXQT__)
8054 SetCursorStatus(m_cursor_lat, m_cursor_lon);
8055#endif
8056
8057 // Send the current cursor lat/lon to all PlugIns requesting it
8058 if (g_pi_manager) {
8059 // Occasionally, MSW will produce nonsense events on right click....
8060 // This results in an error in cursor geo position, so we skip this case
8061 if ((x >= 0) && (y >= 0))
8062 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8063 }
8064
8065 if (!g_btouch) {
8066 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8067 wxPoint p = ClientToScreen(wxPoint(x, y));
8068 }
8069 }
8070
8071 if (1 ) {
8072 // Route Creation Rubber Banding
8073 if (m_routeState >= 2) {
8074 r_rband.x = x;
8075 r_rband.y = y;
8076 m_bDrawingRoute = true;
8077
8078 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8079 Refresh(false);
8080 }
8081
8082 // Measure Tool Rubber Banding
8083 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8084 r_rband.x = x;
8085 r_rband.y = y;
8086 m_bDrawingRoute = true;
8087
8088 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8089 Refresh(false);
8090 }
8091 }
8092 return bret;
8093}
8094
8095int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8096 // On general Right Click
8097 // Look for selectable objects
8098 double slat = lat;
8099 double slon = lon;
8100
8101#if defined(__WXMAC__) || defined(__ANDROID__)
8102 wxScreenDC sdc;
8103 ocpnDC dc(sdc);
8104#else
8105 wxClientDC cdc(GetParent());
8106 ocpnDC dc(cdc);
8107#endif
8108
8109 SelectItem *pFindAIS;
8110 SelectItem *pFindRP;
8111 SelectItem *pFindRouteSeg;
8112 SelectItem *pFindTrackSeg;
8113 SelectItem *pFindCurrent = NULL;
8114 SelectItem *pFindTide = NULL;
8115
8116 // Deselect any current objects
8117 if (m_pSelectedRoute) {
8118 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8119 m_pSelectedRoute->DeSelectRoute();
8120#ifdef ocpnUSE_GL
8121 if (g_bopengl && m_glcc) {
8122 InvalidateGL();
8123 Update();
8124 } else
8125#endif
8126 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8127 }
8128
8129 if (m_pFoundRoutePoint) {
8130 m_pFoundRoutePoint->m_bPtIsSelected = false;
8131 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8132 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8133 }
8134
8137 if (g_btouch && m_pRoutePointEditTarget) {
8138 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8139 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8140 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8141 }
8142
8143 // Get all the selectable things at the cursor
8144 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8145 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8146 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8147 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8148 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8149
8150 if (m_bShowCurrent)
8151 pFindCurrent =
8152 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8153
8154 if (m_bShowTide) // look for tide stations
8155 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8156
8157 int seltype = 0;
8158
8159 // Try for AIS targets first
8160 if (pFindAIS) {
8161 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8162
8163 // Make sure the target data is available
8164 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8165 seltype |= SELTYPE_AISTARGET;
8166 }
8167
8168 // Now examine the various Route parts
8169
8170 m_pFoundRoutePoint = NULL;
8171 if (pFindRP) {
8172 RoutePoint *pFirstVizPoint = NULL;
8173 RoutePoint *pFoundActiveRoutePoint = NULL;
8174 RoutePoint *pFoundVizRoutePoint = NULL;
8175 Route *pSelectedActiveRoute = NULL;
8176 Route *pSelectedVizRoute = NULL;
8177
8178 // There is at least one routepoint, so get the whole list
8179 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8180 SelectableItemList SelList =
8181 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8182 for (SelectItem *pFindSel : SelList) {
8183 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8184
8185 // Get an array of all routes using this point
8186 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8187
8188 // Use route array (if any) to determine actual visibility for this point
8189 bool brp_viz = false;
8190 if (proute_array) {
8191 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8192 Route *pr = (Route *)proute_array->Item(ir);
8193 if (pr->IsVisible()) {
8194 brp_viz = true;
8195 break;
8196 }
8197 }
8198 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8199 // but still exists as a waypoint
8200 brp_viz = prp->IsVisible(); // so treat as isolated point
8201
8202 } else
8203 brp_viz = prp->IsVisible(); // isolated point
8204
8205 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8206
8207 // Use route array to choose the appropriate route
8208 // Give preference to any active route, otherwise select the first visible
8209 // route in the array for this point
8210 m_pSelectedRoute = NULL;
8211 if (proute_array) {
8212 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8213 Route *pr = (Route *)proute_array->Item(ir);
8214 if (pr->m_bRtIsActive) {
8215 pSelectedActiveRoute = pr;
8216 pFoundActiveRoutePoint = prp;
8217 break;
8218 }
8219 }
8220
8221 if (NULL == pSelectedVizRoute) {
8222 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8223 Route *pr = (Route *)proute_array->Item(ir);
8224 if (pr->IsVisible()) {
8225 pSelectedVizRoute = pr;
8226 pFoundVizRoutePoint = prp;
8227 break;
8228 }
8229 }
8230 }
8231
8232 delete proute_array;
8233 }
8234 }
8235
8236 // Now choose the "best" selections
8237 if (pFoundActiveRoutePoint) {
8238 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8239 m_pSelectedRoute = pSelectedActiveRoute;
8240 } else if (pFoundVizRoutePoint) {
8241 m_pFoundRoutePoint = pFoundVizRoutePoint;
8242 m_pSelectedRoute = pSelectedVizRoute;
8243 } else
8244 // default is first visible point in list
8245 m_pFoundRoutePoint = pFirstVizPoint;
8246
8247 if (m_pSelectedRoute) {
8248 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8249 } else if (m_pFoundRoutePoint) {
8250 seltype |= SELTYPE_MARKPOINT;
8251 }
8252
8253 // Highlight the selected point, to verify the proper right click selection
8254 if (m_pFoundRoutePoint) {
8255 m_pFoundRoutePoint->m_bPtIsSelected = true;
8256 wxRect wp_rect;
8257 RoutePointGui(*m_pFoundRoutePoint)
8258 .CalculateDCRect(m_dc_route, this, &wp_rect);
8259 RefreshRect(wp_rect, true);
8260 }
8261 }
8262
8263 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8264 // routes But call the popup handler with identifier appropriate to the type
8265 if (pFindRouteSeg) // there is at least one select item
8266 {
8267 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8268 SelectableItemList SelList =
8269 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8270
8271 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8272 {
8273 // Choose the first visible route containing segment in the list
8274 for (SelectItem *pFindSel : SelList) {
8275 Route *pr = (Route *)pFindSel->m_pData3;
8276 if (pr->IsVisible()) {
8277 m_pSelectedRoute = pr;
8278 break;
8279 }
8280 }
8281 }
8282
8283 if (m_pSelectedRoute) {
8284 if (NULL == m_pFoundRoutePoint)
8285 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8286
8287 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8288 if (m_pSelectedRoute->m_bRtIsSelected) {
8289#ifdef ocpnUSE_GL
8290 if (g_bopengl && m_glcc) {
8291 InvalidateGL();
8292 Update();
8293 } else
8294#endif
8295 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8296 }
8297 seltype |= SELTYPE_ROUTESEGMENT;
8298 }
8299 }
8300
8301 if (pFindTrackSeg) {
8302 m_pSelectedTrack = NULL;
8303 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8304 SelectableItemList SelList =
8305 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8306
8307 // Choose the first visible track containing segment in the list
8308 for (SelectItem *pFindSel : SelList) {
8309 Track *pt = (Track *)pFindSel->m_pData3;
8310 if (pt->IsVisible()) {
8311 m_pSelectedTrack = pt;
8312 break;
8313 }
8314 }
8315 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8316 }
8317
8318#if 0 // disable tide and current graph on right click
8319 {
8320 if (pFindCurrent) {
8321 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8322 seltype |= SELTYPE_CURRENTPOINT;
8323 }
8324
8325 else if (pFindTide) {
8326 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8327 seltype |= SELTYPE_TIDEPOINT;
8328 }
8329 }
8330#endif
8331
8332 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8333
8334 return seltype;
8335}
8336
8337IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8338 // There may be multiple current entries at the same point.
8339 // For example, there often is a current substation (with directions
8340 // specified) co-located with its master. We want to select the
8341 // substation, so that the direction will be properly indicated on the
8342 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8343 // substation)
8344 IDX_entry *pIDX_best_candidate;
8345
8346 SelectItem *pFind = NULL;
8347 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8348 SelectableItemList SelList =
8349 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8350
8351 // Default is first entry
8352 pFind = *SelList.begin();
8353 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8354
8355 auto node = SelList.begin();
8356 if (SelList.size() > 1) {
8357 for (++node; node != SelList.end(); ++node) {
8358 pFind = *node;
8359 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8360 if (pIDX_candidate->IDX_type == 'c') {
8361 pIDX_best_candidate = pIDX_candidate;
8362 break;
8363 }
8364 } // while (node)
8365 } else {
8366 pFind = *SelList.begin();
8367 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8368 }
8369
8370 return pIDX_best_candidate;
8371}
8372void ChartCanvas::CallPopupMenu(int x, int y) {
8373 last_drag.x = x;
8374 last_drag.y = y;
8375 if (m_routeState) { // creating route?
8376 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8377 return;
8378 }
8379
8381
8382 // If tide or current point is selected, then show the TC dialog immediately
8383 // without context menu
8384 if (SELTYPE_CURRENTPOINT == seltype) {
8385 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8386 Refresh(false);
8387 return;
8388 }
8389
8390 if (SELTYPE_TIDEPOINT == seltype) {
8391 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8392 Refresh(false);
8393 return;
8394 }
8395
8396 InvokeCanvasMenu(x, y, seltype);
8397
8398 // Clean up if not deleted in InvokeCanvasMenu
8399 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8400 m_pSelectedRoute->m_bRtIsSelected = false;
8401 }
8402
8403 m_pSelectedRoute = NULL;
8404
8405 if (m_pFoundRoutePoint) {
8406 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8407 m_pFoundRoutePoint->m_bPtIsSelected = false;
8408 }
8409 m_pFoundRoutePoint = NULL;
8410
8411 // A route/mark edit may have been in progress (m_pRoutePointEditTarget set)
8412 // when the menu action deleted that very RoutePoint (e.g. "Delete" on a
8413 // waypoint of a route). Nulling m_pSelectedRoute/m_pFoundRoutePoint above is
8414 // not enough: m_pRoutePointEditTarget (and m_pFoundPoint) would still point
8415 // at the freed point, so the next mouse motion in MouseEventProcessObjects
8416 // dereferences it (GetIconName()) -> use-after-free -> crash. Drop the edit
8417 // state if its target no longer exists.
8418 if (m_pRoutePointEditTarget &&
8419 !pSelect->IsSelectableRoutePointValid(m_pRoutePointEditTarget)) {
8420 m_pRoutePointEditTarget = NULL;
8421 m_lastRoutePointEditTarget = NULL;
8422 m_pFoundPoint = NULL;
8423 m_bRouteEditing = false;
8424 m_bMarkEditing = false;
8425 }
8426
8427 Refresh(true);
8428 // Refresh(false); // needed for MSW, not GTK Why??
8429}
8430
8431bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8432 // For now just bail out completely if the point clicked is not on the chart
8433 if (std::isnan(m_cursor_lat)) return false;
8434
8435 // Mouse Clicks
8436 bool ret = false; // return true if processed
8437
8438 int x, y, mx, my;
8439 event.GetPosition(&x, &y);
8440 mx = x;
8441 my = y;
8442
8443 // Calculate meaningful SelectRadius
8444 float SelectRadius;
8445 SelectRadius = g_Platform->GetSelectRadiusPix() /
8446 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8447
8449 // We start with Double Click processing. The first left click just starts a
8450 // timer and is remembered, then we actually do something if there is a
8451 // LeftDClick. If there is, the two single clicks are ignored.
8452
8453 if (event.LeftDClick() && (cursor_region == CENTER)) {
8454 m_DoubleClickTimer->Start();
8455 singleClickEventIsValid = false;
8456
8457 double zlat, zlon;
8459 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8460
8461 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8462 if (m_bShowAIS) {
8463 SelectItem *pFindAIS;
8464 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8465
8466 if (pFindAIS) {
8467 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8468 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8469 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8470 }
8471 return true;
8472 }
8473 }
8474
8475 SelectableItemList rpSelList =
8476 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8477 bool b_onRPtarget = false;
8478 for (SelectItem *pFind : rpSelList) {
8479 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8480 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8481 b_onRPtarget = true;
8482 break;
8483 }
8484 }
8485
8486 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8487
8488 // Get and honor the plugin API ContextMenuMask
8489 std::unique_ptr<HostApi> host_api = GetHostApi();
8490 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8491
8492 if (m_pRoutePointEditTarget) {
8493 if (b_onRPtarget) {
8494 if ((api_121->GetContextMenuMask() &
8495 api_121->kContextMenuDisableWaypoint))
8496 return true;
8497 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8498 return true;
8499 } else {
8500 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8501 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8502 if (g_btouch)
8503 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8504 wxRect wp_rect;
8505 RoutePointGui(*m_pRoutePointEditTarget)
8506 .CalculateDCRect(m_dc_route, this, &wp_rect);
8507 m_pRoutePointEditTarget = NULL; // cancel selection
8508 RefreshRect(wp_rect, true);
8509 return true;
8510 }
8511 } else {
8512 auto node = rpSelList.begin();
8513 if (node != rpSelList.end()) {
8514 SelectItem *pFind = *node;
8515 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8516 if (frp) {
8517 wxArrayPtrVoid *proute_array =
8519
8520 // Use route array (if any) to determine actual visibility for this
8521 // point
8522 bool brp_viz = false;
8523 if (proute_array) {
8524 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8525 Route *pr = (Route *)proute_array->Item(ir);
8526 if (pr->IsVisible()) {
8527 brp_viz = true;
8528 break;
8529 }
8530 }
8531 delete proute_array;
8532 if (!brp_viz &&
8533 frp->IsShared()) // is not visible as part of route, but
8534 // still exists as a waypoint
8535 brp_viz = frp->IsVisible(); // so treat as isolated point
8536 } else
8537 brp_viz = frp->IsVisible(); // isolated point
8538
8539 if (brp_viz) {
8540 if ((api_121->GetContextMenuMask() &
8541 api_121->kContextMenuDisableWaypoint))
8542 return true;
8543
8544 ShowMarkPropertiesDialog(frp);
8545 return true;
8546 }
8547 }
8548 }
8549 }
8550
8551 SelectItem *cursorItem;
8552
8553 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8554 if (cursorItem) {
8555 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8556 return true;
8557 Route *pr = (Route *)cursorItem->m_pData3;
8558 if (pr->IsVisible()) {
8559 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8560 return true;
8561 }
8562 }
8563
8564 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8565 if (cursorItem) {
8566 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8567 return true;
8568 Track *pt = (Track *)cursorItem->m_pData3;
8569 if (pt->IsVisible()) {
8570 ShowTrackPropertiesDialog(pt);
8571 return true;
8572 }
8573 }
8574
8575 // Tide and current points
8576 SelectItem *pFindCurrent = NULL;
8577 SelectItem *pFindTide = NULL;
8578
8579 if (m_bShowCurrent) { // look for current stations
8580 pFindCurrent =
8581 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8582 if (pFindCurrent) {
8583 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8584 // Check for plugin graphic override
8585 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8586 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8587 PlugInContainer *pic = plugin_array->Item(i);
8588 if (pic->m_enabled && pic->m_init_state &&
8589 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8590 if (ptcmgr) {
8591 TCClickInfo info;
8592 if (m_pIDXCandidate) {
8593 info.point_type = CURRENT_STATION;
8594 info.index = m_pIDXCandidate->IDX_rec_num;
8595 info.name = m_pIDXCandidate->IDX_station_name;
8596 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8597 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8598 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8599 };
8600 auto plugin =
8601 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8602 if (plugin) plugin->OnTideCurrentClick(info);
8603 return true;
8604 }
8605 }
8606 }
8607 }
8608
8609 // Default action
8610 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8611 Refresh(false);
8612 return true;
8613 }
8614 }
8615
8616 if (m_bShowTide) { // look for tide stations
8617 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8618 if (pFindTide) {
8619 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8620 // Check for plugin graphic override
8621 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8622 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8623 PlugInContainer *pic = plugin_array->Item(i);
8624 if (pic->m_enabled && pic->m_init_state &&
8625 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8626 if (ptcmgr) {
8627 TCClickInfo info;
8628 if (m_pIDXCandidate) {
8629 info.point_type = TIDE_STATION;
8630 info.index = m_pIDXCandidate->IDX_rec_num;
8631 info.name = m_pIDXCandidate->IDX_station_name;
8632 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8633 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8634 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8635 };
8636 auto plugin =
8637 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8638 if (plugin) plugin->OnTideCurrentClick(info);
8639 return true;
8640 }
8641 }
8642 }
8643 }
8644
8645 // Default action
8646 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8647 Refresh(false);
8648 return true;
8649 }
8650 }
8651
8652 // Found no object to act on, so show chart info.
8653 ShowObjectQueryWindow(x, y, zlat, zlon);
8654 return true;
8655 }
8656
8658 if (event.LeftDown()) {
8659 // This really should not be needed, but....
8660 // on Windows, when using wxAUIManager, sometimes the focus is lost
8661 // when clicking into another pane, e.g.the AIS target list, and then back
8662 // to this pane. Oddly, some mouse events are not lost, however. Like this
8663 // one....
8664 SetFocus();
8665
8666 last_drag.x = mx;
8667 last_drag.y = my;
8668 leftIsDown = true;
8669
8670 if (!g_btouch) {
8671 if (m_routeState) // creating route?
8672 {
8673 double rlat, rlon;
8674 bool appending = false;
8675 bool inserting = false;
8676 Route *tail = 0;
8677
8678 SetCursor(*pCursorPencil);
8679 rlat = m_cursor_lat;
8680 rlon = m_cursor_lon;
8681
8682 m_bRouteEditing = true;
8683
8684 if (m_routeState == 1) {
8685 m_pMouseRoute = new Route();
8686 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8687 pRouteList->push_back(m_pMouseRoute);
8688 r_rband.x = x;
8689 r_rband.y = y;
8690 }
8691
8692 // Check to see if there is a nearby point which may be reused
8693 RoutePoint *pMousePoint = NULL;
8694
8695 // Calculate meaningful SelectRadius
8696 double nearby_radius_meters =
8697 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8698
8699 RoutePoint *pNearbyPoint =
8700 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8701 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8702 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8703 wxArrayPtrVoid *proute_array =
8704 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8705
8706 // Use route array (if any) to determine actual visibility for this
8707 // point
8708 bool brp_viz = false;
8709 if (proute_array) {
8710 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8711 Route *pr = (Route *)proute_array->Item(ir);
8712 if (pr->IsVisible()) {
8713 brp_viz = true;
8714 break;
8715 }
8716 }
8717 delete proute_array;
8718 if (!brp_viz &&
8719 pNearbyPoint->IsShared()) // is not visible as part of route,
8720 // but still exists as a waypoint
8721 brp_viz =
8722 pNearbyPoint->IsVisible(); // so treat as isolated point
8723 } else
8724 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8725
8726 if (brp_viz) {
8727 wxString msg = _("Use nearby waypoint?");
8728 // Don't add a mark without name to the route. Name it if needed
8729 const bool noname(pNearbyPoint->GetName() == "");
8730 if (noname) {
8731 msg =
8732 _("Use nearby nameless waypoint and name it M with"
8733 " a unique number?");
8734 }
8735 // Avoid route finish on focus change for message dialog
8736 m_FinishRouteOnKillFocus = false;
8737 int dlg_return =
8738 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8739 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8740 m_FinishRouteOnKillFocus = true;
8741 if (dlg_return == wxID_YES) {
8742 if (noname) {
8743 if (m_pMouseRoute) {
8744 int last_wp_num = m_pMouseRoute->GetnPoints();
8745 // AP-ECRMB will truncate to 6 characters
8746 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8747 wxString wp_name = wxString::Format(
8748 "M%002i-%s", last_wp_num + 1, guid_short);
8749 pNearbyPoint->SetName(wp_name);
8750 } else
8751 pNearbyPoint->SetName("WPXX");
8752 }
8753 pMousePoint = pNearbyPoint;
8754
8755 // Using existing waypoint, so nothing to delete for undo.
8756 if (m_routeState > 1)
8757 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8758 Undo_HasParent, NULL);
8759
8760 tail =
8761 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8762 bool procede = false;
8763 if (tail) {
8764 procede = true;
8765 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8766 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8767 procede = false;
8768 }
8769
8770 if (procede) {
8771 int dlg_return;
8772 m_FinishRouteOnKillFocus = false;
8773 if (m_routeState ==
8774 1) { // first point in new route, preceeding route to be
8775 // added? Not touch case
8776
8777 wxString dmsg =
8778 _("Insert first part of this route in the new route?");
8779 if (tail->GetIndexOf(pMousePoint) ==
8780 tail->GetnPoints()) // Starting on last point of another
8781 // route?
8782 dmsg = _("Insert this route in the new route?");
8783
8784 if (tail->GetIndexOf(pMousePoint) > 0) { // Anything to do?
8785 dlg_return = OCPNMessageBox(
8786 this, dmsg, _("OpenCPN Route Create"),
8787 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8788 m_FinishRouteOnKillFocus = true;
8789
8790 if (dlg_return == wxID_YES) {
8791 inserting = true; // part of the other route will be
8792 // preceeding the new route
8793 }
8794 }
8795 } else {
8796 wxString dmsg =
8797 _("Append last part of this route to the new route?");
8798 if (tail->GetIndexOf(pMousePoint) == 1)
8799 dmsg = _(
8800 "Append this route to the new route?"); // Picking the
8801 // first point
8802 // of another
8803 // route?
8804
8805 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8806 dlg_return = OCPNMessageBox(
8807 this, dmsg, _("OpenCPN Route Create"),
8808 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8809 m_FinishRouteOnKillFocus = true;
8810
8811 if (dlg_return == wxID_YES) {
8812 appending = true; // part of the other route will be
8813 // appended to the new route
8814 }
8815 }
8816 }
8817 }
8818
8819 // check all other routes to see if this point appears in any
8820 // other route If it appears in NO other route, then it should e
8821 // considered an isolated mark
8822 if (!FindRouteContainingWaypoint(pMousePoint))
8823 pMousePoint->SetShared(true);
8824 }
8825 }
8826 }
8827
8828 if (NULL == pMousePoint) { // need a new point
8829 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8830 "", wxEmptyString);
8831 pMousePoint->SetNameShown(false);
8832
8833 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8834
8835 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8836
8837 if (m_routeState > 1)
8838 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8839 Undo_IsOrphanded, NULL);
8840 }
8841
8842 if (m_pMouseRoute) {
8843 if (m_routeState == 1) {
8844 // First point in the new route.
8845 m_pMouseRoute->AddPoint(pMousePoint);
8846 } else {
8847 if (m_pMouseRoute->m_NextLegGreatCircle) {
8848 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8849 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8850 &rhumbBearing, &rhumbDist);
8851 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8852 rlat, &gcDist, &gcBearing, NULL);
8853 double gcDistNM = gcDist / 1852.0;
8854
8855 // Empirically found expression to get reasonable route segments.
8856 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8857 pow(rhumbDist - gcDistNM - 1, 0.5);
8858
8859 wxString msg;
8860 msg << _("For this leg the Great Circle route is ")
8861 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8862 << _(" shorter than rhumbline.\n\n")
8863 << _("Would you like include the Great Circle routing points "
8864 "for this leg?");
8865
8866 m_FinishRouteOnKillFocus = false;
8867 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8868 // does not fully capture mouse
8869
8870 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8871 wxYES_NO | wxNO_DEFAULT);
8872
8873 m_disable_edge_pan = false;
8874 m_FinishRouteOnKillFocus = true;
8875
8876 if (answer == wxID_YES) {
8877 RoutePoint *gcPoint;
8878 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8879 wxRealPoint gcCoord;
8880
8881 for (int i = 1; i <= segmentCount; i++) {
8882 double fraction = (double)i * (1.0 / (double)segmentCount);
8883 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8884 gcDist * fraction, gcBearing,
8885 &gcCoord.x, &gcCoord.y, NULL);
8886
8887 if (i < segmentCount) {
8888 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8889 wxEmptyString);
8890 gcPoint->SetNameShown(false);
8891 // pConfig->AddNewWayPoint(gcPoint, -1);
8892 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8893
8894 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8895 gcPoint);
8896 } else {
8897 gcPoint = pMousePoint; // Last point, previously exsisting!
8898 }
8899
8900 m_pMouseRoute->AddPoint(gcPoint);
8901 pSelect->AddSelectableRouteSegment(
8902 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8903 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8904 prevGcPoint = gcPoint;
8905 }
8906
8907 undo->CancelUndoableAction(true);
8908
8909 } else {
8910 m_pMouseRoute->AddPoint(pMousePoint);
8911 pSelect->AddSelectableRouteSegment(
8912 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8913 pMousePoint, m_pMouseRoute);
8914 undo->AfterUndoableAction(m_pMouseRoute);
8915 }
8916 } else {
8917 // Ordinary rhumblinesegment.
8918 m_pMouseRoute->AddPoint(pMousePoint);
8919 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8920 rlon, m_prev_pMousePoint,
8921 pMousePoint, m_pMouseRoute);
8922 undo->AfterUndoableAction(m_pMouseRoute);
8923 }
8924 }
8925 }
8926 m_prev_rlat = rlat;
8927 m_prev_rlon = rlon;
8928 m_prev_pMousePoint = pMousePoint;
8929 if (m_pMouseRoute)
8930 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8931
8932 m_routeState++;
8933
8934 if (appending ||
8935 inserting) { // Appending a route or making a new route
8936 int connect = tail->GetIndexOf(pMousePoint);
8937 if (connect == 0) {
8938 inserting = false; // there is nothing to insert
8939 appending = true; // so append
8940 }
8941 int length = tail->GetnPoints();
8942
8943 int i;
8944 int start, stop;
8945 if (appending) {
8946 start = connect + 1;
8947 stop = length;
8948 } else { // inserting
8949 start = 1;
8950 stop = connect + 1;
8951 // Remove the first and only point of the new route
8952 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8953 }
8954 for (i = start; i <= stop; i++) {
8955 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8956 if (m_pMouseRoute)
8957 m_pMouseRoute->m_lastMousePointIndex =
8958 m_pMouseRoute->GetnPoints();
8959 m_routeState++;
8960 top_frame::Get()->RefreshAllCanvas();
8961 ret = true;
8962 }
8963 m_prev_rlat =
8964 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8965 m_prev_rlon =
8966 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8967 m_pMouseRoute->FinalizeForRendering();
8968 }
8969 top_frame::Get()->RefreshAllCanvas();
8970 ret = true;
8971 }
8972
8973 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8974 {
8975 SetCursor(*pCursorPencil);
8976
8977 if (!m_pMeasureRoute) {
8978 m_pMeasureRoute = new Route();
8979 pRouteList->push_back(m_pMeasureRoute);
8980 }
8981
8982 if (m_nMeasureState == 1) {
8983 r_rband.x = x;
8984 r_rband.y = y;
8985 }
8986
8987 if (!g_pRouteMan->IsRouteInList(m_pMeasureRoute)) {
8988 wxLogMessage("Stale measure route");
8989 CancelMeasureRoute();
8990 top_frame::Get()->RefreshAllCanvas();
8991 ret = true;
8992 } else {
8993 RoutePoint *pMousePoint =
8994 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8995 wxEmptyString, wxEmptyString);
8996 pMousePoint->m_bShowName = false;
8997 pMousePoint->SetShowWaypointRangeRings(false);
8998
8999 m_pMeasureRoute->AddPoint(pMousePoint);
9000
9001 m_prev_rlat = m_cursor_lat;
9002 m_prev_rlon = m_cursor_lon;
9003 m_prev_pMousePoint = pMousePoint;
9004 m_pMeasureRoute->m_lastMousePointIndex =
9005 m_pMeasureRoute->GetnPoints();
9006
9007 m_nMeasureState++;
9008 top_frame::Get()->RefreshAllCanvas();
9009 ret = true;
9010 }
9011 }
9012
9013 else {
9014 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
9015 }
9016 } // !g_btouch
9017 else { // g_btouch
9018 m_last_touch_down_pos = event.GetPosition();
9019
9020 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
9021 // if near screen edge, pan with injection
9022 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
9023 // return;
9024 // }
9025 }
9026 }
9027
9028 if (ret) return true;
9029 }
9030
9031 if (event.Dragging()) {
9032 // The route/mark edit target is a raw RoutePoint* that can be freed out
9033 // from under us by a delete (context menu, Routes panel, or a plugin)
9034 // between the press that armed the edit and this drag event. The canvas is
9035 // not notified, so the pointer dangles. Validate it against the Select list
9036 // before any use below; if it is gone, drop the stale edit state, otherwise
9037 // the drag logic dereferences freed memory (e.g. GetIconName()) -> crash.
9038 if (m_pRoutePointEditTarget &&
9039 !pSelect->IsSelectableRoutePointValid(m_pRoutePointEditTarget)) {
9040 m_pRoutePointEditTarget = NULL;
9041 m_lastRoutePointEditTarget = NULL;
9042 m_pFoundPoint = NULL;
9043 m_bRouteEditing = false;
9044 m_bMarkEditing = false;
9045 return false;
9046 }
9047
9048 // in touch screen mode ensure the finger/cursor is on the selected point's
9049 // radius to allow dragging
9050 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9051 if (g_btouch) {
9052 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
9053 SelectItem *pFind = NULL;
9054 SelectableItemList SelList = pSelect->FindSelectionList(
9055 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
9056 for (SelectItem *pFind : SelList) {
9057 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
9058 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
9059 }
9060 }
9061
9062 // Check for use of dragHandle
9063 if (m_pRoutePointEditTarget &&
9064 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
9065 SelectItem *pFind = NULL;
9066 SelectableItemList SelList = pSelect->FindSelectionList(
9067 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
9068 for (SelectItem *pFind : SelList) {
9069 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
9070 if (m_pRoutePointEditTarget == frp) {
9071 m_bIsInRadius = true;
9072 break;
9073 }
9074 }
9075
9076 if (!m_dragoffsetSet) {
9077 RoutePointGui(*m_pRoutePointEditTarget)
9078 .PresetDragOffset(this, mouse_x, mouse_y);
9079 m_dragoffsetSet = true;
9080 }
9081 }
9082 }
9083
9084 if (m_bRouteEditing && m_pRoutePointEditTarget) {
9085 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9086
9087 if (NULL == g_pMarkInfoDialog) {
9088 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9089 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9090 DraggingAllowed = false;
9091
9092 if (m_pRoutePointEditTarget &&
9093 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9094 DraggingAllowed = false;
9095
9096 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9097
9098 if (DraggingAllowed) {
9099 if (!undo->InUndoableAction()) {
9100 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9101 Undo_NeedsCopy, m_pFoundPoint);
9102 }
9103
9104 // Get the update rectangle for the union of the un-edited routes
9105 wxRect pre_rect;
9106
9107 if (!g_bopengl && m_pEditRouteArray) {
9108 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9109 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9110 // Need to validate route pointer
9111 // Route may be gone due to drgging close to ownship with
9112 // "Delete On Arrival" state set, as in the case of
9113 // navigating to an isolated waypoint on a temporary route
9114 if (g_pRouteMan->IsRouteValid(pr)) {
9115 wxRect route_rect;
9116 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9117 pre_rect.Union(route_rect);
9118 }
9119 }
9120 }
9121
9122 double new_cursor_lat = m_cursor_lat;
9123 double new_cursor_lon = m_cursor_lon;
9124
9125 if (CheckEdgePan(x, y, true, 5, 2))
9126 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9127
9128 // update the point itself
9129 if (g_btouch) {
9130 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9131 // new_cursor_lat, new_cursor_lon);
9132 RoutePointGui(*m_pRoutePointEditTarget)
9133 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9134 // update the Drag Handle entry in the pSelect list
9135 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9136 m_pRoutePointEditTarget,
9137 SELTYPE_DRAGHANDLE);
9138 m_pFoundPoint->m_slat =
9139 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9140 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9141 } else {
9142 m_pRoutePointEditTarget->m_lat =
9143 new_cursor_lat; // update the RoutePoint entry
9144 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9145 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9146 m_pFoundPoint->m_slat =
9147 new_cursor_lat; // update the SelectList entry
9148 m_pFoundPoint->m_slon = new_cursor_lon;
9149 }
9150
9151 // Update the MarkProperties Dialog, if currently shown
9152 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9153 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9154 g_pMarkInfoDialog->UpdateProperties(true);
9155 }
9156
9157 if (g_bopengl) {
9158 // InvalidateGL();
9159 Refresh(false);
9160 } else {
9161 // Get the update rectangle for the edited route
9162 wxRect post_rect;
9163
9164 if (m_pEditRouteArray) {
9165 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9166 ir++) {
9167 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9168 if (g_pRouteMan->IsRouteValid(pr)) {
9169 wxRect route_rect;
9170 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9171 post_rect.Union(route_rect);
9172 }
9173 }
9174 }
9175
9176 // Invalidate the union region
9177 pre_rect.Union(post_rect);
9178 RefreshRect(pre_rect, false);
9179 }
9180 top_frame::Get()->RefreshCanvasOther(this);
9181 m_bRoutePoinDragging = true;
9182 }
9183 ret = true;
9184 } // if Route Editing
9185
9186 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9187 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9188
9189 if (NULL == g_pMarkInfoDialog) {
9190 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9191 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9192 DraggingAllowed = false;
9193
9194 if (m_pRoutePointEditTarget &&
9195 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9196 DraggingAllowed = false;
9197
9198 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9199
9200 if (DraggingAllowed) {
9201 if (!undo->InUndoableAction()) {
9202 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9203 Undo_NeedsCopy, m_pFoundPoint);
9204 }
9205
9206 // The mark may be an anchorwatch
9207 double lpp1 = 0.;
9208 double lpp2 = 0.;
9209 double lppmax;
9210
9211 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9212 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9213 }
9214 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9215 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9216 }
9217 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9218
9219 // Get the update rectangle for the un-edited mark
9220 wxRect pre_rect;
9221 if (!g_bopengl) {
9222 RoutePointGui(*m_pRoutePointEditTarget)
9223 .CalculateDCRect(m_dc_route, this, &pre_rect);
9224 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9225 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9226 (int)(lppmax - (pre_rect.height / 2)));
9227 }
9228
9229 // update the point itself
9230 if (g_btouch) {
9231 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9232 // m_cursor_lat, m_cursor_lon);
9233 RoutePointGui(*m_pRoutePointEditTarget)
9234 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9235 // update the Drag Handle entry in the pSelect list
9236 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9237 m_pRoutePointEditTarget,
9238 SELTYPE_DRAGHANDLE);
9239 m_pFoundPoint->m_slat =
9240 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9241 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9242 } else {
9243 m_pRoutePointEditTarget->m_lat =
9244 m_cursor_lat; // update the RoutePoint entry
9245 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9246 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9247 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9248 m_pFoundPoint->m_slon = m_cursor_lon;
9249 }
9250
9251 // Update the MarkProperties Dialog, if currently shown
9252 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9253 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9254 g_pMarkInfoDialog->UpdateProperties(true);
9255 }
9256
9257 // Invalidate the union region
9258 if (g_bopengl) {
9259 if (!g_btouch) InvalidateGL();
9260 Refresh(false);
9261 } else {
9262 // Get the update rectangle for the edited mark
9263 wxRect post_rect;
9264 RoutePointGui(*m_pRoutePointEditTarget)
9265 .CalculateDCRect(m_dc_route, this, &post_rect);
9266 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9267 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9268 (int)(lppmax - (post_rect.height / 2)));
9269
9270 // Invalidate the union region
9271 pre_rect.Union(post_rect);
9272 RefreshRect(pre_rect, false);
9273 }
9274 top_frame::Get()->RefreshCanvasOther(this);
9275 m_bRoutePoinDragging = true;
9276 }
9277 ret = g_btouch ? m_bRoutePoinDragging : true;
9278 }
9279
9280 if (ret) return true;
9281 } // dragging
9282
9283 if (event.LeftUp()) {
9284 bool b_startedit_route = false;
9285 m_dragoffsetSet = false;
9286
9287 if (g_btouch) {
9288 m_bChartDragging = false;
9289 m_bIsInRadius = false;
9290
9291 if (m_routeState) // creating route?
9292 {
9293 // Check displacement from touchdown to distinguish taps from pans.
9294 // On touchscreens, even a "tap" can have several pixels of jitter,
9295 // so use a generous threshold (20px) to avoid false positives.
9296 int dx = x - m_touchdownPos.x;
9297 int dy = y - m_touchdownPos.y;
9298 int dist2 = dx * dx + dy * dy;
9299 bool wasPan = (dist2 > 20 * 20);
9300
9301 if (wasPan) {
9302 return false;
9303 }
9304
9305 if (m_bedge_pan) {
9306 m_bedge_pan = false;
9307 return false;
9308 }
9309
9310 double rlat, rlon;
9311 bool appending = false;
9312 bool inserting = false;
9313 Route *tail = 0;
9314
9315 rlat = m_cursor_lat;
9316 rlon = m_cursor_lon;
9317
9318 if (m_pRoutePointEditTarget) {
9319 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9320 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9321 if (!g_bopengl) {
9322 wxRect wp_rect;
9323 RoutePointGui(*m_pRoutePointEditTarget)
9324 .CalculateDCRect(m_dc_route, this, &wp_rect);
9325 RefreshRect(wp_rect, true);
9326 }
9327 m_pRoutePointEditTarget = NULL;
9328 }
9329 m_bRouteEditing = true;
9330
9331 if (m_routeState == 1) {
9332 m_pMouseRoute = new Route();
9333 m_pMouseRoute->SetHiLite(50);
9334 pRouteList->push_back(m_pMouseRoute);
9335 r_rband.x = x;
9336 r_rband.y = y;
9337 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9338 }
9339
9340 // Check to see if there is a nearby point which may be reused
9341 RoutePoint *pMousePoint = NULL;
9342
9343 // Calculate meaningful SelectRadius
9344 double nearby_radius_meters =
9345 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9346
9347 RoutePoint *pNearbyPoint =
9348 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9349 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9350 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9351 int dlg_return;
9352#ifndef __WXOSX__
9353 m_FinishRouteOnKillFocus =
9354 false; // Avoid route finish on focus change for message dialog
9355 dlg_return = OCPNMessageBox(
9356 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9357 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9358 m_FinishRouteOnKillFocus = true;
9359#else
9360 dlg_return = wxID_YES;
9361#endif
9362 if (dlg_return == wxID_YES) {
9363 pMousePoint = pNearbyPoint;
9364
9365 // Using existing waypoint, so nothing to delete for undo.
9366 if (m_routeState > 1)
9367 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9368 Undo_HasParent, NULL);
9369 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9370
9371 bool procede = false;
9372 if (tail) {
9373 procede = true;
9374 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9375 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9376 procede = false;
9377 }
9378
9379 if (procede) {
9380 int dlg_return;
9381 m_FinishRouteOnKillFocus = false;
9382 if (m_routeState == 1) { // first point in new route, preceeding
9383 // route to be added? touch case
9384
9385 wxString dmsg =
9386 _("Insert first part of this route in the new route?");
9387 if (tail->GetIndexOf(pMousePoint) ==
9388 tail->GetnPoints()) // Starting on last point of another
9389 // route?
9390 dmsg = _("Insert this route in the new route?");
9391
9392 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9393 dlg_return =
9394 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9395 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9396 m_FinishRouteOnKillFocus = true;
9397
9398 if (dlg_return == wxID_YES) {
9399 inserting = true; // part of the other route will be
9400 // preceeding the new route
9401 }
9402 }
9403 } else {
9404 wxString dmsg =
9405 _("Append last part of this route to the new route?");
9406 if (tail->GetIndexOf(pMousePoint) == 1)
9407 dmsg = _(
9408 "Append this route to the new route?"); // Picking the
9409 // first point of
9410 // another route?
9411
9412 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9413 dlg_return =
9414 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9415 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9416 m_FinishRouteOnKillFocus = true;
9417
9418 if (dlg_return == wxID_YES) {
9419 appending = true; // part of the other route will be
9420 // appended to the new route
9421 }
9422 }
9423 }
9424 }
9425
9426 // check all other routes to see if this point appears in any other
9427 // route If it appears in NO other route, then it should e
9428 // considered an isolated mark
9429 if (!FindRouteContainingWaypoint(pMousePoint))
9430 pMousePoint->SetShared(true);
9431 }
9432 }
9433
9434 if (NULL == pMousePoint) { // need a new point
9435 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9436 "", wxEmptyString);
9437 pMousePoint->SetNameShown(false);
9438
9439 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9440
9441 if (m_routeState > 1)
9442 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9443 Undo_IsOrphanded, NULL);
9444 }
9445
9446 if (m_routeState == 1) {
9447 // First point in the route.
9448 m_pMouseRoute->AddPoint(pMousePoint);
9449 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9450
9451 } else {
9452 if (m_pMouseRoute->m_NextLegGreatCircle) {
9453 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9454 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9455 &rhumbBearing, &rhumbDist);
9456 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9457 &gcDist, &gcBearing, NULL);
9458 double gcDistNM = gcDist / 1852.0;
9459
9460 // Empirically found expression to get reasonable route segments.
9461 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9462 pow(rhumbDist - gcDistNM - 1, 0.5);
9463
9464 wxString msg;
9465 msg << _("For this leg the Great Circle route is ")
9466 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9467 << _(" shorter than rhumbline.\n\n")
9468 << _("Would you like include the Great Circle routing points "
9469 "for this leg?");
9470
9471#ifndef __WXOSX__
9472 m_FinishRouteOnKillFocus = false;
9473 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9474 wxYES_NO | wxNO_DEFAULT);
9475 m_FinishRouteOnKillFocus = true;
9476#else
9477 int answer = wxID_NO;
9478#endif
9479
9480 if (answer == wxID_YES) {
9481 RoutePoint *gcPoint;
9482 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9483 wxRealPoint gcCoord;
9484
9485 for (int i = 1; i <= segmentCount; i++) {
9486 double fraction = (double)i * (1.0 / (double)segmentCount);
9487 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9488 gcDist * fraction, gcBearing,
9489 &gcCoord.x, &gcCoord.y, NULL);
9490
9491 if (i < segmentCount) {
9492 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9493 wxEmptyString);
9494 gcPoint->SetNameShown(false);
9495 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9496 gcPoint);
9497 } else {
9498 gcPoint = pMousePoint; // Last point, previously exsisting!
9499 }
9500
9501 m_pMouseRoute->AddPoint(gcPoint);
9502 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9503
9504 pSelect->AddSelectableRouteSegment(
9505 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9506 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9507 prevGcPoint = gcPoint;
9508 }
9509
9510 undo->CancelUndoableAction(true);
9511
9512 } else {
9513 m_pMouseRoute->AddPoint(pMousePoint);
9514 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9515 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9516 rlon, m_prev_pMousePoint,
9517 pMousePoint, m_pMouseRoute);
9518 undo->AfterUndoableAction(m_pMouseRoute);
9519 }
9520 } else {
9521 // Ordinary rhumblinesegment.
9522 m_pMouseRoute->AddPoint(pMousePoint);
9523 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9524
9525 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9526 rlon, m_prev_pMousePoint,
9527 pMousePoint, m_pMouseRoute);
9528 undo->AfterUndoableAction(m_pMouseRoute);
9529 }
9530 }
9531
9532 m_prev_rlat = rlat;
9533 m_prev_rlon = rlon;
9534 m_prev_pMousePoint = pMousePoint;
9535 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9536
9537 m_routeState++;
9538
9539 if (appending ||
9540 inserting) { // Appending a route or making a new route
9541 int connect = tail->GetIndexOf(pMousePoint);
9542 if (connect == 0) {
9543 inserting = false; // there is nothing to insert
9544 appending = true; // so append
9545 }
9546 int length = tail->GetnPoints();
9547
9548 int i;
9549 int start, stop;
9550 if (appending) {
9551 start = connect;
9552 stop = length;
9553 } else { // inserting
9554 start = 0;
9555 stop = connect + 1;
9556 m_pMouseRoute->RemovePoint(
9557 m_pMouseRoute
9558 ->GetLastPoint()); // Remove the first and only point
9559 }
9560 for (i = start; i < stop; i++) {
9561 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i + 1), false);
9562 if (m_pMouseRoute)
9563 m_pMouseRoute->m_lastMousePointIndex =
9564 m_pMouseRoute->GetnPoints();
9565 m_routeState++;
9566 top_frame::Get()->RefreshAllCanvas();
9567 ret = true;
9568 }
9569 g_pRouteMan->DeleteRoute(tail);
9570
9571 m_prev_rlat =
9572 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9573 m_prev_rlon =
9574 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9575 m_pMouseRoute->FinalizeForRendering();
9576 }
9577
9578 Refresh(true);
9579 ret = true;
9580 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9581 {
9582 if (m_bedge_pan) {
9583 m_bedge_pan = false;
9584 return false;
9585 }
9586
9587 // Skip if user panned (same logic as route handler above)
9588 {
9589 int mdx = x - m_touchdownPos.x;
9590 int mdy = y - m_touchdownPos.y;
9591 if (mdx * mdx + mdy * mdy > 20 * 20) return false;
9592 }
9593
9594 if (m_nMeasureState == 1) {
9595 m_pMeasureRoute = new Route();
9596 pRouteList->push_back(m_pMeasureRoute);
9597 r_rband.x = x;
9598 r_rband.y = y;
9599 }
9600
9601 if (!g_pRouteMan->IsRouteInList(m_pMeasureRoute)) {
9602 wxLogMessage("Stale measure route");
9603 CancelMeasureRoute();
9604 top_frame::Get()->RefreshAllCanvas();
9605 ret = true;
9606 } else {
9607 if (m_pMeasureRoute) {
9608 RoutePoint *pMousePoint =
9609 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9610 wxEmptyString, wxEmptyString);
9611 pMousePoint->m_bShowName = false;
9612
9613 m_pMeasureRoute->AddPoint(pMousePoint);
9614
9615 m_prev_rlat = m_cursor_lat;
9616 m_prev_rlon = m_cursor_lon;
9617 m_prev_pMousePoint = pMousePoint;
9618 m_pMeasureRoute->m_lastMousePointIndex =
9619 m_pMeasureRoute->GetnPoints();
9620
9621 m_nMeasureState++;
9622 } else {
9623 CancelMeasureRoute();
9624 }
9625 }
9626 Refresh(true);
9627 ret = true;
9628 } else {
9629 bool bSelectAllowed = true;
9630 if (NULL == g_pMarkInfoDialog) {
9631 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9632 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9633 bSelectAllowed = false;
9634
9635 // Avoid accidental selection of routepoint if last touchdown started
9636 // a significant chart drag operation
9637 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9638 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9639 significant_drag) ||
9640 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9641 significant_drag)) {
9642 bSelectAllowed = false;
9643 }
9644
9645 /*if this left up happens at the end of a route point dragging and if
9646 the cursor/thumb is on the draghandle icon, not on the point iself a new
9647 selection will select nothing and the drag will never be ended, so the
9648 legs around this point never selectable. At this step we don't need a
9649 new selection, just keep the previoulsly selected and dragged point */
9650 if (m_bRoutePoinDragging) bSelectAllowed = false;
9651
9652 if (bSelectAllowed) {
9653 bool b_was_editing_mark = m_bMarkEditing;
9654 bool b_was_editing_route = m_bRouteEditing;
9655 FindRoutePointsAtCursor(SelectRadius,
9656 true); // Possibly selecting a point in a
9657 // route for later dragging
9658
9659 /*route and a mark points in layer can't be dragged so should't be
9660 * selected and no draghandle icon*/
9661 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9662 m_pRoutePointEditTarget = NULL;
9663
9664 if (!b_was_editing_route) {
9665 if (m_pEditRouteArray) {
9666 b_startedit_route = true;
9667
9668 // Hide the track and route rollover during route point edit, not
9669 // needed, and may be confusing
9670 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9671 m_pTrackRolloverWin->IsActive(false);
9672 }
9673 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9674 m_pRouteRolloverWin->IsActive(false);
9675 }
9676
9677 wxRect pre_rect;
9678 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9679 ir++) {
9680 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9681 // Need to validate route pointer
9682 // Route may be gone due to drgging close to ownship with
9683 // "Delete On Arrival" state set, as in the case of
9684 // navigating to an isolated waypoint on a temporary route
9685 if (g_pRouteMan->IsRouteValid(pr)) {
9686 // pr->SetHiLite(50);
9687 wxRect route_rect;
9688 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9689 pre_rect.Union(route_rect);
9690 }
9691 }
9692 RefreshRect(pre_rect, true);
9693 }
9694 } else {
9695 b_startedit_route = false;
9696 }
9697
9698 // Mark editing in touch mode, left-up event.
9699 if (m_pRoutePointEditTarget) {
9700 if (b_was_editing_mark ||
9701 b_was_editing_route) { // kill previous hilight
9702 if (m_lastRoutePointEditTarget) {
9703 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9704 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9705 RoutePointGui(*m_lastRoutePointEditTarget)
9706 .EnableDragHandle(false);
9707 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9708 SELTYPE_DRAGHANDLE);
9709 }
9710 }
9711
9712 if (m_pRoutePointEditTarget) {
9713 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9714 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9715 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9716 wxPoint2DDouble dragHandlePoint =
9717 RoutePointGui(*m_pRoutePointEditTarget)
9718 .GetDragHandlePoint(this);
9719 pSelect->AddSelectablePoint(
9720 dragHandlePoint.m_y, dragHandlePoint.m_x,
9721 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9722 }
9723 } else { // Deselect everything
9724 if (m_lastRoutePointEditTarget) {
9725 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9726 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9727 RoutePointGui(*m_lastRoutePointEditTarget)
9728 .EnableDragHandle(false);
9729 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9730 SELTYPE_DRAGHANDLE);
9731
9732 // Clear any routes being edited, probably orphans
9733 wxArrayPtrVoid *lastEditRouteArray =
9735 m_lastRoutePointEditTarget);
9736 if (lastEditRouteArray) {
9737 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9738 ir++) {
9739 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9740 if (g_pRouteMan->IsRouteValid(pr)) {
9741 pr->m_bIsBeingEdited = false;
9742 }
9743 }
9744 delete lastEditRouteArray;
9745 }
9746 }
9747 }
9748
9749 // Do the refresh
9750
9751 if (g_bopengl) {
9752 InvalidateGL();
9753 Refresh(false);
9754 } else {
9755 if (m_lastRoutePointEditTarget) {
9756 wxRect wp_rect;
9757 RoutePointGui(*m_lastRoutePointEditTarget)
9758 .CalculateDCRect(m_dc_route, this, &wp_rect);
9759 RefreshRect(wp_rect, true);
9760 }
9761
9762 if (m_pRoutePointEditTarget) {
9763 wxRect wp_rect;
9764 RoutePointGui(*m_pRoutePointEditTarget)
9765 .CalculateDCRect(m_dc_route, this, &wp_rect);
9766 RefreshRect(wp_rect, true);
9767 }
9768 }
9769 }
9770 } // bSelectAllowed
9771
9772 // Check to see if there is a route or AIS target under the cursor
9773 // If so, start the rollover timer which creates the popup
9774 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9775 bool b_start_rollover = false;
9776 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9777 SelectItem *pFind = pSelectAIS->FindSelection(
9778 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9779 if (pFind) b_start_rollover = true;
9780 }
9781
9782 if (!b_start_rollover && !b_startedit_route) {
9783 SelectableItemList SelList = pSelect->FindSelectionList(
9784 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9785 for (SelectItem *pFindSel : SelList) {
9786 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9787 if (pr && pr->IsVisible()) {
9788 b_start_rollover = true;
9789 break;
9790 }
9791 } // while
9792 }
9793
9794 if (!b_start_rollover && !b_startedit_route) {
9795 SelectableItemList SelList = pSelect->FindSelectionList(
9796 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9797 for (SelectItem *pFindSel : SelList) {
9798 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9799 if (tr && tr->IsVisible()) {
9800 b_start_rollover = true;
9801 break;
9802 }
9803 } // while
9804 }
9805
9806 if (b_start_rollover)
9807 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9808 wxTIMER_ONE_SHOT);
9809 Route *tail = 0;
9810 Route *current = 0;
9811 bool appending = false;
9812 bool inserting = false;
9813 int connect = 0;
9814 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9815 // drag
9816 if (m_pRoutePointEditTarget) {
9817 // Check to see if there is a nearby point which may replace the
9818 // dragged one
9819 RoutePoint *pMousePoint = NULL;
9820
9821 int index_last;
9822 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9823 double nearby_radius_meters =
9824 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9825 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9826 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9827 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9828 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9829 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9830 bool duplicate =
9831 false; // ensure we won't create duplicate point in routes
9832 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9833 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9834 ir++) {
9835 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9836 if (pr && pr->pRoutePointList) {
9837 auto *list = pr->pRoutePointList;
9838 auto pos =
9839 std::find(list->begin(), list->end(), pNearbyPoint);
9840 if (pos != list->end()) {
9841 duplicate = true;
9842 break;
9843 }
9844 }
9845 }
9846 }
9847
9848 // Special case:
9849 // Allow "re-use" of a route's waypoints iff it is a simple
9850 // isolated route. This allows, for instance, creation of a closed
9851 // polygon route
9852 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9853
9854 if (!duplicate) {
9855 int dlg_return;
9856 dlg_return =
9857 OCPNMessageBox(this,
9858 _("Replace this RoutePoint by the nearby "
9859 "Waypoint?"),
9860 _("OpenCPN RoutePoint change"),
9861 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9862 if (dlg_return == wxID_YES) {
9863 /*double confirmation if the dragged point has been manually
9864 * created which can be important and could be deleted
9865 * unintentionally*/
9866
9867 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9868 pNearbyPoint);
9869 current =
9870 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9871
9872 if (tail && current && (tail != current)) {
9873 int dlg_return1;
9874 connect = tail->GetIndexOf(pNearbyPoint);
9875 int index_current_route =
9876 current->GetIndexOf(m_pRoutePointEditTarget);
9877 index_last = current->GetIndexOf(current->GetLastPoint());
9878 dlg_return1 = wxID_NO;
9879 if (index_last ==
9880 index_current_route) { // we are dragging the last
9881 // point of the route
9882 if (connect != tail->GetnPoints()) { // anything to do?
9883
9884 wxString dmsg(
9885 _("Last part of route to be appended to dragged "
9886 "route?"));
9887 if (connect == 0)
9888 dmsg =
9889 _("Full route to be appended to dragged route?");
9890
9891 dlg_return1 = OCPNMessageBox(
9892 this, dmsg, _("OpenCPN Route Create"),
9893 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9894 if (dlg_return1 == wxID_YES) {
9895 appending = true;
9896 }
9897 }
9898 } else if (index_current_route ==
9899 0) { // dragging the first point of the route
9900 if (connect != 0) { // anything to do?
9901
9902 wxString dmsg(
9903 _("First part of route to be inserted into dragged "
9904 "route?"));
9905 if (connect == tail->GetnPoints())
9906 dmsg = _(
9907 "Full route to be inserted into dragged route?");
9908
9909 dlg_return1 = OCPNMessageBox(
9910 this, dmsg, _("OpenCPN Route Create"),
9911 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9912 if (dlg_return1 == wxID_YES) {
9913 inserting = true;
9914 }
9915 }
9916 }
9917 }
9918
9919 if (m_pRoutePointEditTarget->IsShared()) {
9920 // dlg_return = wxID_NO;
9921 dlg_return = OCPNMessageBox(
9922 this,
9923 _("Do you really want to delete and replace this "
9924 "WayPoint") +
9925 "\n" + _("which has been created manually?"),
9926 ("OpenCPN RoutePoint warning"),
9927 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9928 }
9929 }
9930 if (dlg_return == wxID_YES) {
9931 pMousePoint = pNearbyPoint;
9932 if (pMousePoint->m_bIsolatedMark) {
9933 pMousePoint->SetShared(true);
9934 }
9935 pMousePoint->m_bIsolatedMark =
9936 false; // definitely no longer isolated
9937 pMousePoint->m_bIsInRoute = true;
9938 }
9939 }
9940 }
9941 }
9942 if (!pMousePoint)
9943 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9944
9945 if (m_pEditRouteArray) {
9946 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9947 ir++) {
9948 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9949 if (g_pRouteMan->IsRouteValid(pr)) {
9950 if (pMousePoint) { // remove the dragged point and insert the
9951 // nearby
9952 auto *list = pr->pRoutePointList;
9953 auto pos = std::find(list->begin(), list->end(),
9954 m_pRoutePointEditTarget);
9955
9956 pSelect->DeleteAllSelectableRoutePoints(pr);
9957 pSelect->DeleteAllSelectableRouteSegments(pr);
9958
9959 pr->pRoutePointList->insert(pos, pMousePoint);
9960 pos = std::find(list->begin(), list->end(),
9961 m_pRoutePointEditTarget);
9962 pr->pRoutePointList->erase(pos);
9963
9964 pSelect->AddAllSelectableRouteSegments(pr);
9965 pSelect->AddAllSelectableRoutePoints(pr);
9966 }
9967 pr->FinalizeForRendering();
9968 pr->UpdateSegmentDistances();
9969 if (m_bRoutePoinDragging) {
9970 // pConfig->UpdateRoute(pr);
9971 NavObj_dB::GetInstance().UpdateRoute(pr);
9972 }
9973 }
9974 }
9975 }
9976
9977 // Update the RouteProperties Dialog, if currently shown
9978 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9979 if (m_pEditRouteArray) {
9980 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9981 ir++) {
9982 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9983 if (g_pRouteMan->IsRouteValid(pr)) {
9984 if (pRoutePropDialog->GetRoute() == pr) {
9985 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9986 }
9987 /* cannot edit track points anyway
9988 else if ( ( NULL !=
9989 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9990 pTrackPropDialog->m_pTrack == pr ) {
9991 pTrackPropDialog->SetTrackAndUpdate(
9992 pr );
9993 }
9994 */
9995 }
9996 }
9997 }
9998 }
9999 if (pMousePoint) { // clear all about the dragged point
10000 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10001 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10002 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10003 // Hide mark properties dialog if open on the replaced point
10004 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10005 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10006 g_pMarkInfoDialog->Hide();
10007
10008 delete m_pRoutePointEditTarget;
10009 m_lastRoutePointEditTarget = NULL;
10010 m_pRoutePointEditTarget = NULL;
10011 undo->AfterUndoableAction(pMousePoint);
10012 undo->InvalidateUndo();
10013 }
10014 }
10015 }
10016
10017 else if (m_bMarkEditing) { // End of way point drag
10018 if (m_pRoutePointEditTarget)
10019 if (m_bRoutePoinDragging) {
10020 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10021 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10022 }
10023 }
10024
10025 if (m_pRoutePointEditTarget)
10026 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10027
10028 if (!m_pRoutePointEditTarget) {
10029 delete m_pEditRouteArray;
10030 m_pEditRouteArray = NULL;
10031 m_bRouteEditing = false;
10032 }
10033 m_bRoutePoinDragging = false;
10034
10035 if (appending) { // Appending to the route of which the last point is
10036 // dragged onto another route
10037
10038 // copy tail from connect until length to end of current after dragging
10039
10040 int length = tail->GetnPoints();
10041 for (int i = connect + 1; i <= length; i++) {
10042 current->AddPointAndSegment(tail->GetPoint(i), false);
10043 if (current) current->m_lastMousePointIndex = current->GetnPoints();
10044 m_routeState++;
10045 top_frame::Get()->RefreshAllCanvas();
10046 ret = true;
10047 }
10048 current->FinalizeForRendering();
10049 current->m_bIsBeingEdited = false;
10050 FinishRoute();
10051 g_pRouteMan->DeleteRoute(tail);
10052 NavObj_dB::GetInstance().UpdateRoute(current);
10053 }
10054 if (inserting) {
10055 pSelect->DeleteAllSelectableRoutePoints(current);
10056 pSelect->DeleteAllSelectableRouteSegments(current);
10057 for (int i = 1; i < connect + 1; i++) { // numbering in the tail route
10058 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10059 }
10060 pSelect->AddAllSelectableRouteSegments(current);
10061 pSelect->AddAllSelectableRoutePoints(current);
10062 current->FinalizeForRendering();
10063 current->m_bIsBeingEdited = false;
10064 g_pRouteMan->DeleteRoute(tail);
10065 NavObj_dB::GetInstance().UpdateRoute(current);
10066 }
10067
10068 // Update the RouteProperties Dialog, if currently shown
10069 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10070 if (m_pEditRouteArray) {
10071 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
10072 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10073 if (g_pRouteMan->IsRouteValid(pr)) {
10074 if (pRoutePropDialog->GetRoute() == pr) {
10075 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10076 }
10077 }
10078 }
10079 }
10080 }
10081
10082 } // g_btouch
10083
10084 else { // !g_btouch
10085 if (m_bRouteEditing) { // End of RoutePoint drag
10086 Route *tail = 0;
10087 Route *current = 0;
10088 bool appending = false;
10089 bool inserting = false;
10090 int connect = 0;
10091 int index_last;
10092 if (m_pRoutePointEditTarget) {
10093 m_pRoutePointEditTarget->m_bBlink = false;
10094 // Check to see if there is a nearby point which may replace the
10095 // dragged one
10096 RoutePoint *pMousePoint = NULL;
10097 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
10098 double nearby_radius_meters =
10099 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
10100 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
10101 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
10102 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
10103 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
10104 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
10105 bool duplicate = false; // don't create duplicate point in routes
10106 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
10107 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10108 ir++) {
10109 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10110 if (pr && pr->pRoutePointList) {
10111 auto *list = pr->pRoutePointList;
10112 auto pos =
10113 std::find(list->begin(), list->end(), pNearbyPoint);
10114 if (pos != list->end()) {
10115 duplicate = true;
10116 break;
10117 }
10118 }
10119 }
10120 }
10121
10122 // Special case:
10123 // Allow "re-use" of a route's waypoints iff it is a simple
10124 // isolated route. This allows, for instance, creation of a closed
10125 // polygon route
10126 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
10127
10128 if (!duplicate) {
10129 int dlg_return;
10130 dlg_return =
10131 OCPNMessageBox(this,
10132 _("Replace this RoutePoint by the nearby "
10133 "Waypoint?"),
10134 _("OpenCPN RoutePoint change"),
10135 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10136 if (dlg_return == wxID_YES) {
10137 /*double confirmation if the dragged point has been manually
10138 * created which can be important and could be deleted
10139 * unintentionally*/
10140 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10141 pNearbyPoint);
10142 current =
10143 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10144
10145 if (tail && current && (tail != current)) {
10146 int dlg_return1;
10147 connect = tail->GetIndexOf(pNearbyPoint);
10148 int index_current_route =
10149 current->GetIndexOf(m_pRoutePointEditTarget);
10150 index_last = current->GetIndexOf(current->GetLastPoint());
10151 dlg_return1 = wxID_NO;
10152 if (index_last ==
10153 index_current_route) { // we are dragging the last
10154 // point of the route
10155 if (connect != tail->GetnPoints()) { // anything to do?
10156
10157 wxString dmsg(
10158 _("Last part of route to be appended to dragged "
10159 "route?"));
10160 if (connect == 0)
10161 dmsg =
10162 _("Full route to be appended to dragged route?");
10163
10164 dlg_return1 = OCPNMessageBox(
10165 this, dmsg, _("OpenCPN Route Create"),
10166 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10167 if (dlg_return1 == wxID_YES) {
10168 appending = true;
10169 }
10170 }
10171 } else if (index_current_route ==
10172 0) { // dragging the first point of the route
10173 if (connect != 0) { // anything to do?
10174
10175 wxString dmsg(
10176 _("First part of route to be inserted into dragged "
10177 "route?"));
10178 if (connect == tail->GetnPoints())
10179 dmsg = _(
10180 "Full route to be inserted into dragged route?");
10181
10182 dlg_return1 = OCPNMessageBox(
10183 this, dmsg, _("OpenCPN Route Create"),
10184 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10185 if (dlg_return1 == wxID_YES) {
10186 inserting = true;
10187 }
10188 }
10189 }
10190 }
10191
10192 if (m_pRoutePointEditTarget->IsShared()) {
10193 dlg_return = wxID_NO;
10194 dlg_return = OCPNMessageBox(
10195 this,
10196 _("Do you really want to delete and replace this "
10197 "WayPoint") +
10198 "\n" + _("which has been created manually?"),
10199 ("OpenCPN RoutePoint warning"),
10200 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10201 }
10202 }
10203 if (dlg_return == wxID_YES) {
10204 pMousePoint = pNearbyPoint;
10205 if (pMousePoint->m_bIsolatedMark) {
10206 pMousePoint->SetShared(true);
10207 }
10208 pMousePoint->m_bIsolatedMark =
10209 false; // definitely no longer isolated
10210 pMousePoint->m_bIsInRoute = true;
10211 }
10212 }
10213 }
10214 }
10215 if (!pMousePoint)
10216 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10217
10218 if (m_pEditRouteArray) {
10219 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10220 ir++) {
10221 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10222 if (g_pRouteMan->IsRouteValid(pr)) {
10223 if (pMousePoint) { // replace dragged point by nearby one
10224 auto *list = pr->pRoutePointList;
10225 auto pos = std::find(list->begin(), list->end(),
10226 m_pRoutePointEditTarget);
10227
10228 pSelect->DeleteAllSelectableRoutePoints(pr);
10229 pSelect->DeleteAllSelectableRouteSegments(pr);
10230
10231 pr->pRoutePointList->insert(pos, pMousePoint);
10232 pos = std::find(list->begin(), list->end(),
10233 m_pRoutePointEditTarget);
10234 if (pos != list->end()) list->erase(pos);
10235 // pr->pRoutePointList->erase(pos + 1);
10236
10237 pSelect->AddAllSelectableRouteSegments(pr);
10238 pSelect->AddAllSelectableRoutePoints(pr);
10239 }
10240 pr->FinalizeForRendering();
10241 pr->UpdateSegmentDistances();
10242 pr->m_bIsBeingEdited = false;
10243
10244 if (m_bRoutePoinDragging) {
10245 // Special case optimization.
10246 // Dragging a single point of a route
10247 // without any point additions or re-ordering
10248 if (!pMousePoint)
10249 NavObj_dB::GetInstance().UpdateRoutePoint(
10250 m_pRoutePointEditTarget);
10251 else
10252 NavObj_dB::GetInstance().UpdateRoute(pr);
10253 }
10254 pr->SetHiLite(0);
10255 }
10256 }
10257 Refresh(false);
10258 }
10259
10260 if (appending) {
10261 // copy tail from connect until length to end of current after
10262 // dragging
10263
10264 int length = tail->GetnPoints();
10265 for (int i = connect + 1; i <= length; i++) {
10266 current->AddPointAndSegment(tail->GetPoint(i), false);
10267 if (current)
10268 current->m_lastMousePointIndex = current->GetnPoints();
10269 m_routeState++;
10270 top_frame::Get()->RefreshAllCanvas();
10271 ret = true;
10272 }
10273 current->FinalizeForRendering();
10274 current->m_bIsBeingEdited = false;
10275 FinishRoute();
10276 g_pRouteMan->DeleteRoute(tail);
10277 NavObj_dB::GetInstance().UpdateRoute(current);
10278 }
10279 if (inserting) {
10280 pSelect->DeleteAllSelectableRoutePoints(current);
10281 pSelect->DeleteAllSelectableRouteSegments(current);
10282 for (int i = 1; i < connect + 1;
10283 i++) { // numbering in the tail route
10284 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10285 }
10286
10287 pSelect->AddAllSelectableRouteSegments(current);
10288 pSelect->AddAllSelectableRoutePoints(current);
10289 current->FinalizeForRendering();
10290 current->m_bIsBeingEdited = false;
10291 g_pRouteMan->DeleteRoute(tail);
10292 NavObj_dB::GetInstance().UpdateRoute(current);
10293 }
10294
10295 // Update the RouteProperties Dialog, if currently shown
10296 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10297 if (m_pEditRouteArray) {
10298 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10299 ir++) {
10300 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10301 if (g_pRouteMan->IsRouteValid(pr)) {
10302 if (pRoutePropDialog->GetRoute() == pr) {
10303 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10304 }
10305 }
10306 }
10307 }
10308 }
10309
10310 if (pMousePoint) {
10311 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10312 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10313 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10314 // Hide mark properties dialog if open on the replaced point
10315 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10316 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10317 g_pMarkInfoDialog->Hide();
10318
10319 delete m_pRoutePointEditTarget;
10320 m_lastRoutePointEditTarget = NULL;
10321 undo->AfterUndoableAction(pMousePoint);
10322 undo->InvalidateUndo();
10323 } else {
10324 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10325 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10326
10327 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10328 }
10329
10330 delete m_pEditRouteArray;
10331 m_pEditRouteArray = NULL;
10332 }
10333
10334 InvalidateGL();
10335 m_bRouteEditing = false;
10336 m_pRoutePointEditTarget = NULL;
10337
10338 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10339 ret = true;
10340 }
10341
10342 else if (m_bMarkEditing) { // end of Waypoint drag
10343 if (m_pRoutePointEditTarget) {
10344 if (m_bRoutePoinDragging) {
10345 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10346 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10347 }
10348 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10349 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10350 if (!g_bopengl) {
10351 wxRect wp_rect;
10352 RoutePointGui(*m_pRoutePointEditTarget)
10353 .CalculateDCRect(m_dc_route, this, &wp_rect);
10354 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10355 RefreshRect(wp_rect, true);
10356 }
10357 }
10358 m_pRoutePointEditTarget = NULL;
10359 m_bMarkEditing = false;
10360 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10361 ret = true;
10362 }
10363
10364 else if (leftIsDown) { // left click for chart center
10365 leftIsDown = false;
10366 ret = false;
10367
10368 if (!g_btouch) {
10369 if (!m_bChartDragging && !m_bMeasure_Active) {
10370 } else {
10371 m_bChartDragging = false;
10372 }
10373 }
10374 }
10375 m_bRoutePoinDragging = false;
10376 } // !btouch
10377
10378 if (ret) return true;
10379 } // left up
10380
10381 if (event.RightDown()) {
10382 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10383 last_drag.x = mx;
10384 last_drag.y = my;
10385
10386 if (g_btouch) {
10387 // if( m_pRoutePointEditTarget )
10388 // return false;
10389 }
10390
10391 ret = true;
10392 m_FinishRouteOnKillFocus = false;
10393 CallPopupMenu(mx, my);
10394 m_FinishRouteOnKillFocus = true;
10395 } // Right down
10396
10397 return ret;
10398}
10399
10400bool panleftIsDown;
10401bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10402 // Skip all mouse processing if shift is held.
10403 // This allows plugins to implement shift+drag behaviors.
10404 if (event.ShiftDown()) {
10405 return false;
10406 }
10407 int x, y;
10408 event.GetPosition(&x, &y);
10409
10410 x *= m_displayScale;
10411 y *= m_displayScale;
10412
10413 // Check for wheel rotation
10414 // ideally, should be just longer than the time between
10415 // processing accumulated mouse events from the event queue
10416 // as would happen during screen redraws.
10417 int wheel_dir = event.GetWheelRotation();
10418
10419 if (wheel_dir) {
10420 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10421 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10422
10423 double factor = g_mouse_zoom_sensitivity;
10424 if (wheel_dir < 0) factor = 1 / factor;
10425
10426 if (g_bsmoothpanzoom) {
10427 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10428 if (wheel_dir == m_last_wheel_dir) {
10429 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10430 // m_zoom_target /= factor;
10431 } else
10432 StopMovement();
10433 } else {
10434 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10435 m_wheelstopwatch.Start(0);
10436 // m_zoom_target = VPoint.chart_scale / factor;
10437 }
10438 }
10439
10440 m_last_wheel_dir = wheel_dir;
10441
10442 ZoomCanvas(factor, true, false);
10443 }
10444
10445 if (event.LeftDown()) {
10446 // Skip the first left click if it will cause a canvas focus shift
10447 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10448 return false;
10449 }
10450
10451 last_drag.x = x, last_drag.y = y;
10452 m_touchdownPos = wxPoint(x, y);
10453 panleftIsDown = true;
10454 }
10455
10456 if (event.LeftUp()) {
10457 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10458 // seen here.
10459 panleftIsDown = false;
10460
10461 if (!g_btouch) {
10462 if (!m_bChartDragging && !m_bMeasure_Active) {
10463 switch (cursor_region) {
10464 case MID_RIGHT: {
10465 PanCanvas(100, 0);
10466 break;
10467 }
10468
10469 case MID_LEFT: {
10470 PanCanvas(-100, 0);
10471 break;
10472 }
10473
10474 case MID_TOP: {
10475 PanCanvas(0, 100);
10476 break;
10477 }
10478
10479 case MID_BOT: {
10480 PanCanvas(0, -100);
10481 break;
10482 }
10483
10484 case CENTER: {
10485 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10486 break;
10487 }
10488 }
10489 } else {
10490 m_bChartDragging = false;
10491 }
10492 }
10493 }
10494 }
10495
10496 if (event.Dragging() && event.LeftIsDown()) {
10497 /*
10498 * fixed dragging.
10499 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10500 * before the drag event. Hence, as there is no mouse down event, last_drag
10501 * is not reset before the drag. And that results in one single drag
10502 * session, meaning you cannot drag the map a few miles north, lift your
10503 * finger, and the go even further north. Instead, the map resets itself
10504 * always to the very first drag start (since there is not reset of
10505 * last_drag).
10506 *
10507 * Besides, should not left down and dragging be enough of a situation to
10508 * start a drag procedure?
10509 *
10510 * Anyways, guarded it to be active in touch situations only.
10511 */
10512 if (g_btouch && !m_inPinch) {
10513 struct timespec now;
10514 clock_gettime(CLOCK_MONOTONIC, &now);
10515 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10516
10517 bool trigger_hold = false;
10518 if (false == m_bChartDragging) {
10519 if (m_DragTrigger < 0) {
10520 // printf("\ntrigger1\n");
10521 m_DragTrigger = 0;
10522 m_DragTriggerStartTime = tnow;
10523 trigger_hold = true;
10524 } else {
10525 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10526 m_DragTrigger = -1; // Reset trigger
10527 // printf("trigger fired\n");
10528 }
10529 }
10530 }
10531 if (trigger_hold) return true;
10532
10533 if (false == m_bChartDragging) {
10534 // printf("starting drag\n");
10535 // Reset drag calculation members
10536 last_drag.x = x - 1, last_drag.y = y - 1;
10537 m_bChartDragging = true;
10538 m_chart_drag_total_time = 0;
10539 m_chart_drag_total_x = 0;
10540 m_chart_drag_total_y = 0;
10541 m_inertia_last_drag_x = x;
10542 m_inertia_last_drag_y = y;
10543 m_drag_vec_x.clear();
10544 m_drag_vec_y.clear();
10545 m_drag_vec_t.clear();
10546 m_last_drag_time = tnow;
10547 }
10548
10549 // Calculate and store drag dynamics.
10550 uint64_t delta_t = tnow - m_last_drag_time;
10551 double delta_tf = delta_t / 1e9;
10552
10553 m_chart_drag_total_time += delta_tf;
10554 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10555 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10556
10557 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10558 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10559 m_drag_vec_t.push_back(delta_tf);
10560
10561 m_inertia_last_drag_x = x;
10562 m_inertia_last_drag_y = y;
10563 m_last_drag_time = tnow;
10564
10565 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10566 m_bChartDragging = true;
10567 StartTimedMovement();
10568 m_pan_drag.x += last_drag.x - x;
10569 m_pan_drag.y += last_drag.y - y;
10570 last_drag.x = x, last_drag.y = y;
10571 }
10572 } else if (!g_btouch) {
10573 if ((last_drag.x != x) || (last_drag.y != y)) {
10574 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10575 // dragging on route create.
10576 // github #2994
10577 m_bChartDragging = true;
10578 StartTimedMovement();
10579 m_pan_drag.x += last_drag.x - x;
10580 m_pan_drag.y += last_drag.y - y;
10581 last_drag.x = x, last_drag.y = y;
10582 }
10583 }
10584 }
10585
10586 // Handle some special cases
10587 if (g_btouch) {
10588 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10589 // LeftUp handler uses displacement check to distinguish taps from pans.
10590 m_DoubleClickTimer->Start();
10591 singleClickEventIsValid = false;
10592 }
10593 }
10594 }
10595
10596 return true;
10597}
10598
10599void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10600 if (MouseEventOverlayWindows(event)) return;
10601
10602 if (MouseEventSetup(event)) return; // handled, no further action required
10603
10604 bool nm = MouseEventProcessObjects(event);
10605 if (!nm) MouseEventProcessCanvas(event);
10606}
10607
10608void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10609 // Switch to the appropriate cursor on mouse movement
10610
10611 wxCursor *ptarget_cursor = pCursorArrow;
10612 if (!pPlugIn_Cursor) {
10613 ptarget_cursor = pCursorArrow;
10614 if ((!m_routeState) &&
10615 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10616 if (cursor_region == MID_RIGHT) {
10617 ptarget_cursor = pCursorRight;
10618 } else if (cursor_region == MID_LEFT) {
10619 ptarget_cursor = pCursorLeft;
10620 } else if (cursor_region == MID_TOP) {
10621 ptarget_cursor = pCursorDown;
10622 } else if (cursor_region == MID_BOT) {
10623 ptarget_cursor = pCursorUp;
10624 } else {
10625 ptarget_cursor = pCursorArrow;
10626 }
10627 } else if (m_bMeasure_Active ||
10628 m_routeState) // If Measure tool use Pencil Cursor
10629 ptarget_cursor = pCursorPencil;
10630 } else {
10631 ptarget_cursor = pPlugIn_Cursor;
10632 }
10633
10634 SetCursor(*ptarget_cursor);
10635}
10636
10637void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10638 SetCursor(*pCursorArrow);
10639}
10640
10641void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10642 ChartPlugInWrapper *target_plugin_chart = NULL;
10643 s57chart *Chs57 = NULL;
10644 wxFileName file;
10645 wxArrayString files;
10646
10647 ChartBase *target_chart = GetChartAtCursor();
10648 if (target_chart) {
10649 file.Assign(target_chart->GetFullPath());
10650 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10651 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10652 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10653 else
10654 Chs57 = dynamic_cast<s57chart *>(target_chart);
10655 } else { // target_chart = null, might be mbtiles
10656 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10657 unsigned int im = stackIndexArray.size();
10658 int scale = 2147483647; // max 32b integer
10659 if (VPoint.b_quilt && im > 0) {
10660 for (unsigned int is = 0; is < im; is++) {
10661 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10662 CHART_TYPE_MBTILES) {
10663 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10664 double lat, lon;
10665 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10666 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10667 .GetBBox()
10668 .Contains(lat, lon)) {
10669 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10670 scale) {
10671 scale =
10672 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10673 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10674 }
10675 }
10676 }
10677 }
10678 }
10679 }
10680
10681 std::vector<Ais8_001_22 *> area_notices;
10682
10683 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10684 float vp_scale = GetVPScale();
10685
10686 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10687 auto target_data = target.second;
10688 if (!target_data->area_notices.empty()) {
10689 for (auto &ani : target_data->area_notices) {
10690 Ais8_001_22 &area_notice = ani.second;
10691
10692 BoundingBox bbox;
10693
10694 for (Ais8_001_22_SubAreaList::iterator sa =
10695 area_notice.sub_areas.begin();
10696 sa != area_notice.sub_areas.end(); ++sa) {
10697 switch (sa->shape) {
10698 case AIS8_001_22_SHAPE_CIRCLE: {
10699 wxPoint target_point;
10700 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10701 bbox.Expand(target_point);
10702 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10703 break;
10704 }
10705 case AIS8_001_22_SHAPE_RECT: {
10706 wxPoint target_point;
10707 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10708 bbox.Expand(target_point);
10709 if (sa->e_dim_m > sa->n_dim_m)
10710 bbox.EnLarge(sa->e_dim_m * vp_scale);
10711 else
10712 bbox.EnLarge(sa->n_dim_m * vp_scale);
10713 break;
10714 }
10715 case AIS8_001_22_SHAPE_POLYGON:
10716 case AIS8_001_22_SHAPE_POLYLINE: {
10717 for (int i = 0; i < 4; ++i) {
10718 double lat = sa->latitude;
10719 double lon = sa->longitude;
10720 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10721 &lat, &lon);
10722 wxPoint target_point;
10723 GetCanvasPointPix(lat, lon, &target_point);
10724 bbox.Expand(target_point);
10725 }
10726 break;
10727 }
10728 case AIS8_001_22_SHAPE_SECTOR: {
10729 double lat1 = sa->latitude;
10730 double lon1 = sa->longitude;
10731 double lat, lon;
10732 wxPoint target_point;
10733 GetCanvasPointPix(lat1, lon1, &target_point);
10734 bbox.Expand(target_point);
10735 for (int i = 0; i < 18; ++i) {
10736 ll_gc_ll(
10737 lat1, lon1,
10738 sa->left_bound_deg +
10739 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10740 sa->radius_m / 1852.0, &lat, &lon);
10741 GetCanvasPointPix(lat, lon, &target_point);
10742 bbox.Expand(target_point);
10743 }
10744 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10745 &lat, &lon);
10746 GetCanvasPointPix(lat, lon, &target_point);
10747 bbox.Expand(target_point);
10748 break;
10749 }
10750 }
10751 }
10752
10753 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10754 area_notices.push_back(&area_notice);
10755 }
10756 }
10757 }
10758 }
10759 }
10760
10761 if (target_chart || !area_notices.empty() || file.HasName()) {
10762 // Go get the array of all objects at the cursor lat/lon
10763 int sel_rad_pix = 5;
10764 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10765
10766 // Make sure we always get the lights from an object, even if we are
10767 // currently not displaying lights on the chart.
10768
10769 SetCursor(wxCURSOR_WAIT);
10770 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10771 if (!lightsVis) SetShowENCLights(true);
10772 ;
10773
10774 ListOfObjRazRules *rule_list = NULL;
10775 ListOfPI_S57Obj *pi_rule_list = NULL;
10776 if (Chs57)
10777 rule_list =
10778 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10779 else if (target_plugin_chart)
10780 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10781 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10782
10783 ListOfObjRazRules *overlay_rule_list = NULL;
10784 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10785 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10786
10787 if (CHs57_Overlay) {
10788 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10789 zlat, zlon, SelectRadius, &GetVP());
10790 }
10791
10792 if (!lightsVis) SetShowENCLights(false);
10793
10794 wxString objText;
10795 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10796 wxString face = dFont->GetFaceName();
10797
10798 if (NULL == g_pObjectQueryDialog) {
10800 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10801 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10802 }
10803
10804 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10805 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10806
10807#ifdef __WXOSX__
10808 // Auto Adjustment for dark mode
10809 fg = g_pObjectQueryDialog->GetForegroundColour();
10810#endif
10811
10812 objText.Printf(
10813 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10814 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10815
10816#ifdef __WXOSX__
10817 int points = dFont->GetPointSize();
10818#else
10819 int points = dFont->GetPointSize() + 1;
10820#endif
10821
10822 int sizes[7];
10823 for (int i = -2; i < 5; i++) {
10824 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10825 }
10826 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10827
10828 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10829
10830 if (overlay_rule_list && CHs57_Overlay) {
10831 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10832 objText << "<hr noshade>";
10833 }
10834
10835 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10836 an != area_notices.end(); ++an) {
10837 objText << "<b>AIS Area Notice:</b> ";
10838 objText << ais8_001_22_notice_names[(*an)->notice_type];
10839 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10840 (*an)->sub_areas.begin();
10841 sa != (*an)->sub_areas.end(); ++sa)
10842 if (!sa->text.empty()) objText << sa->text;
10843 objText << "<br>expires: " << (*an)->expiry_time.Format();
10844 objText << "<hr noshade>";
10845 }
10846
10847 if (Chs57)
10848 objText << Chs57->CreateObjDescriptions(rule_list);
10849 else if (target_plugin_chart)
10850 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10851 pi_rule_list);
10852
10853 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10854
10855 // Add the additional info files
10856 wxString AddFiles, filenameOK;
10857 int filecount = 0;
10858 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10859 // plugin
10860
10861 AddFiles = wxString::Format(
10862 "<hr noshade><br><b>Additional info files attached to: </b> "
10863 "<font "
10864 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10865 "cellpadding=3>",
10866 file.GetFullName());
10867 file.Normalize();
10868 file.Assign(file.GetPath(), "");
10869 wxDir dir(file.GetFullPath());
10870 wxString filename;
10871 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10872 while (cont) {
10873 file.Assign(dir.GetNameWithSep().append(filename));
10874 wxString FormatString =
10875 "<td valign=top><font size=-2><a "
10876 "href=\"%s\">%s</a></font></td>";
10877 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10878 filenameOK = file.GetFullPath(); // remember last valid name
10879 // we are making a 3 columns table. New row only every third file
10880 if (3 * ((int)filecount / 3) == filecount)
10881 FormatString.Prepend("<tr>"); // new row
10882 else
10883 FormatString.Prepend(
10884 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10885 // spacer column
10886
10887 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10888 file.GetFullName());
10889 filecount++;
10890 }
10891 cont = dir.GetNext(&filename);
10892 }
10893 objText << AddFiles << "</table>";
10894 }
10895 objText << "</font>";
10896 objText << "</body></html>";
10897
10898 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10899 g_pObjectQueryDialog->SetHTMLPage(objText);
10900 g_pObjectQueryDialog->Show();
10901 }
10902 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10903 // generate an event to avoid double code
10904 wxHtmlLinkInfo hli(filenameOK);
10905 wxHtmlLinkEvent hle(1, hli);
10906 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10907 }
10908
10909 if (rule_list) rule_list->Clear();
10910 delete rule_list;
10911
10912 if (overlay_rule_list) overlay_rule_list->Clear();
10913 delete overlay_rule_list;
10914
10915 if (pi_rule_list) pi_rule_list->Clear();
10916 delete pi_rule_list;
10917
10918 SetCursor(wxCURSOR_ARROW);
10919 }
10920}
10921
10922void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10923 bool bNew = false;
10924 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10925 // Dialog
10926 g_pMarkInfoDialog = new MarkInfoDlg(this);
10927 bNew = true;
10928 }
10929
10930 if (1 /*g_bresponsive*/) {
10931 wxSize canvas_size = GetSize();
10932
10933 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10934 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10935
10936 g_pMarkInfoDialog->Layout();
10937
10938 wxPoint canvas_pos = GetPosition();
10939 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10940
10941 bool newFit = false;
10942 if (canvas_size.x < fitted_size.x) {
10943 fitted_size.x = canvas_size.x - 40;
10944 if (canvas_size.y < fitted_size.y)
10945 fitted_size.y -= 40; // scrollbar added
10946 }
10947 if (canvas_size.y < fitted_size.y) {
10948 fitted_size.y = canvas_size.y - 40;
10949 if (canvas_size.x < fitted_size.x)
10950 fitted_size.x -= 40; // scrollbar added
10951 }
10952
10953 if (newFit) {
10954 g_pMarkInfoDialog->SetSize(fitted_size);
10955 g_pMarkInfoDialog->Centre();
10956 }
10957 }
10958
10959 markPoint->m_bRPIsBeingEdited = false;
10960
10961 wxString title_base = _("Mark Properties");
10962 if (markPoint->m_bIsInRoute) {
10963 title_base = _("Waypoint Properties");
10964 }
10965 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10966 g_pMarkInfoDialog->UpdateProperties();
10967 if (markPoint->m_bIsInLayer) {
10968 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10969 GetLayerName(markPoint->m_LayerID)));
10970 g_pMarkInfoDialog->SetDialogTitle(caption);
10971 } else
10972 g_pMarkInfoDialog->SetDialogTitle(title_base);
10973
10974 g_pMarkInfoDialog->Show();
10975 g_pMarkInfoDialog->Raise();
10976 g_pMarkInfoDialog->InitialFocus();
10977 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10978}
10979
10980void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10981 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10982 pRoutePropDialog->SetRouteAndUpdate(selected);
10983 // pNew->UpdateProperties();
10984 pRoutePropDialog->Show();
10985 pRoutePropDialog->Raise();
10986 return;
10987 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10988 this); // There is one global instance of the RouteProp Dialog
10989
10990 if (g_bresponsive) {
10991 wxSize canvas_size = GetSize();
10992 wxPoint canvas_pos = GetPosition();
10993 wxSize fitted_size = pRoutePropDialog->GetSize();
10994 ;
10995
10996 if (canvas_size.x < fitted_size.x) {
10997 fitted_size.x = canvas_size.x;
10998 if (canvas_size.y < fitted_size.y)
10999 fitted_size.y -= 20; // scrollbar added
11000 }
11001 if (canvas_size.y < fitted_size.y) {
11002 fitted_size.y = canvas_size.y;
11003 if (canvas_size.x < fitted_size.x)
11004 fitted_size.x -= 20; // scrollbar added
11005 }
11006
11007 pRoutePropDialog->SetSize(fitted_size);
11008 pRoutePropDialog->Centre();
11009
11010 // int xp = (canvas_size.x - fitted_size.x)/2;
11011 // int yp = (canvas_size.y - fitted_size.y)/2;
11012
11013 wxPoint xxp = ClientToScreen(canvas_pos);
11014 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
11015 }
11016
11017 pRoutePropDialog->SetRouteAndUpdate(selected);
11018
11019 pRoutePropDialog->Show();
11020
11021 Refresh(false);
11022}
11023
11024void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
11025 pTrackPropDialog = TrackPropDlg::getInstance(
11026 this); // There is one global instance of the RouteProp Dialog
11027
11028 pTrackPropDialog->SetTrackAndUpdate(selected);
11030
11031 pTrackPropDialog->Show();
11032
11033 Refresh(false);
11034}
11035
11036void pupHandler_PasteWaypoint() {
11037 Kml kml;
11038
11039 int pasteBuffer = kml.ParsePasteBuffer();
11040 RoutePoint *pasted = kml.GetParsedRoutePoint();
11041 if (!pasted) return;
11042
11043 double nearby_radius_meters =
11044 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
11045
11046 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
11047 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
11048
11049 int answer = wxID_NO;
11050 if (nearPoint && !nearPoint->m_bIsInLayer) {
11051 wxString msg;
11052 msg << _(
11053 "There is an existing waypoint at the same location as the one you are "
11054 "pasting. Would you like to merge the pasted data with it?\n\n");
11055 msg << _("Answering 'No' will create a new waypoint at the same location.");
11056 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
11057 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
11058 }
11059
11060 if (answer == wxID_YES) {
11061 nearPoint->SetName(pasted->GetName());
11062 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
11063 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11064 pRouteManagerDialog->UpdateWptListCtrl();
11065 }
11066
11067 if (answer == wxID_NO) {
11068 RoutePoint *newPoint = new RoutePoint(pasted);
11069 newPoint->m_bIsolatedMark = true;
11070 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11071 newPoint);
11072 // pConfig->AddNewWayPoint(newPoint, -1);
11073 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11074
11075 pWayPointMan->AddRoutePoint(newPoint);
11076 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11077 pRouteManagerDialog->UpdateWptListCtrl();
11078 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11079 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11080 }
11081
11082 top_frame::Get()->InvalidateAllGL();
11083 top_frame::Get()->RefreshAllCanvas(false);
11084}
11085
11086void pupHandler_PasteRoute() {
11087 Kml kml;
11088
11089 int pasteBuffer = kml.ParsePasteBuffer();
11090 Route *pasted = kml.GetParsedRoute();
11091 if (!pasted) return;
11092
11093 double nearby_radius_meters =
11094 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
11095
11096 RoutePoint *curPoint;
11097 RoutePoint *nearPoint;
11098 RoutePoint *prevPoint = NULL;
11099
11100 bool mergepoints = false;
11101 bool createNewRoute = true;
11102 int existingWaypointCounter = 0;
11103
11104 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11105 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
11106 nearPoint = pWayPointMan->GetNearbyWaypoint(
11107 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11108 if (nearPoint) {
11109 mergepoints = true;
11110 existingWaypointCounter++;
11111 // Small hack here to avoid both extending RoutePoint and repeating all
11112 // the GetNearbyWaypoint calculations. Use existin data field in
11113 // RoutePoint as temporary storage.
11114 curPoint->m_bPtIsSelected = true;
11115 }
11116 }
11117
11118 int answer = wxID_NO;
11119 if (mergepoints) {
11120 wxString msg;
11121 msg << _(
11122 "There are existing waypoints at the same location as some of the ones "
11123 "you are pasting. Would you like to just merge the pasted data into "
11124 "them?\n\n");
11125 msg << _("Answering 'No' will create all new waypoints for this route.");
11126 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
11127 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
11128
11129 if (answer == wxID_CANCEL) {
11130 return;
11131 }
11132 }
11133
11134 // If all waypoints exist since before, and a route with the same name, we
11135 // don't create a new route.
11136 if (mergepoints && answer == wxID_YES &&
11137 existingWaypointCounter == pasted->GetnPoints()) {
11138 for (Route *proute : *pRouteList) {
11139 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11140 createNewRoute = false;
11141 break;
11142 }
11143 }
11144 }
11145
11146 Route *newRoute = 0;
11147 RoutePoint *newPoint = 0;
11148
11149 if (createNewRoute) {
11150 newRoute = new Route();
11151 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11152 }
11153
11154 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11155 curPoint = pasted->GetPoint(i);
11156 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11157 curPoint->m_bPtIsSelected = false;
11158 newPoint = pWayPointMan->GetNearbyWaypoint(
11159 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11160 newPoint->SetName(curPoint->GetName());
11161 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11162
11163 if (createNewRoute) newRoute->AddPoint(newPoint);
11164 } else {
11165 curPoint->m_bPtIsSelected = false;
11166
11167 newPoint = new RoutePoint(curPoint);
11168 newPoint->m_bIsolatedMark = false;
11169 newPoint->SetIconName("circle");
11170 newPoint->m_bIsVisible = true;
11171 newPoint->m_bShowName = false;
11172 newPoint->SetShared(false);
11173
11174 newRoute->AddPoint(newPoint);
11175 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11176 newPoint);
11177 // pConfig->AddNewWayPoint(newPoint, -1);
11178 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11179 pWayPointMan->AddRoutePoint(newPoint);
11180 }
11181 if (i > 1 && createNewRoute)
11182 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11183 curPoint->m_lat, curPoint->m_lon,
11184 prevPoint, newPoint, newRoute);
11185 prevPoint = newPoint;
11186 }
11187
11188 if (createNewRoute) {
11189 pRouteList->push_back(newRoute);
11190 // pConfig->AddNewRoute(newRoute); // use auto next num
11191 NavObj_dB::GetInstance().InsertRoute(newRoute);
11192
11193 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11194 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11195 }
11196
11197 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11198 pRouteManagerDialog->UpdateRouteListCtrl();
11199 pRouteManagerDialog->UpdateWptListCtrl();
11200 }
11201 top_frame::Get()->InvalidateAllGL();
11202 top_frame::Get()->RefreshAllCanvas(false);
11203 }
11204 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11205 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11206}
11207
11208void pupHandler_PasteTrack() {
11209 Kml kml;
11210
11211 int pasteBuffer = kml.ParsePasteBuffer();
11212 Track *pasted = kml.GetParsedTrack();
11213 if (!pasted) return;
11214
11215 TrackPoint *curPoint;
11216
11217 Track *newTrack = new Track();
11218 TrackPoint *newPoint;
11219 TrackPoint *prevPoint = NULL;
11220
11221 newTrack->SetName(pasted->GetName());
11222
11223 for (int i = 0; i < pasted->GetnPoints(); i++) {
11224 curPoint = pasted->GetPoint(i);
11225
11226 newPoint = new TrackPoint(curPoint);
11227
11228 wxDateTime now = wxDateTime::Now();
11229 newPoint->SetCreateTime(curPoint->GetCreateTime());
11230
11231 newTrack->AddPoint(newPoint);
11232
11233 if (prevPoint)
11234 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11235 newPoint->m_lat, newPoint->m_lon,
11236 prevPoint, newPoint, newTrack);
11237
11238 prevPoint = newPoint;
11239 }
11240
11241 g_TrackList.push_back(newTrack);
11242 // pConfig->AddNewTrack(newTrack);
11243 NavObj_dB::GetInstance().InsertTrack(newTrack);
11244
11245 top_frame::Get()->InvalidateAllGL();
11246 top_frame::Get()->RefreshAllCanvas(false);
11247}
11248
11249bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11250 nlohmann::json v;
11251 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11252 v["CursorPosition_x"] = x;
11253 v["CursorPosition_y"] = y;
11254 // Send a limited set of selection types depending on what is
11255 // found under the mouse point.
11256 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11257 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11258 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11259
11260 std::string out = v.dump();
11261 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11262
11263 json_msg.Notify(std::make_shared<nlohmann::json>(v), "OCPN_CONTEXT_CLICK");
11264
11265#if 0
11266#define SELTYPE_UNKNOWN 0x0001
11267#define SELTYPE_ROUTEPOINT 0x0002
11268#define SELTYPE_ROUTESEGMENT 0x0004
11269#define SELTYPE_TIDEPOINT 0x0008
11270#define SELTYPE_CURRENTPOINT 0x0010
11271#define SELTYPE_ROUTECREATE 0x0020
11272#define SELTYPE_AISTARGET 0x0040
11273#define SELTYPE_MARKPOINT 0x0080
11274#define SELTYPE_TRACKSEGMENT 0x0100
11275#define SELTYPE_DRAGHANDLE 0x0200
11276#endif
11277
11278 if (g_bhide_context_menus) return true;
11279 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11280 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11281 m_pIDXCandidate, m_nmea_log);
11282
11283 Connect(
11284 wxEVT_COMMAND_MENU_SELECTED,
11285 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11286
11287#ifdef __WXGTK__
11288 // Funny requirement here for gtk, to clear the menu trigger event
11289 // TODO
11290 // Causes a slight "flasH" of the menu,
11291 if (m_inLongPress) {
11292 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11293 m_inLongPress = false;
11294 }
11295#endif
11296
11297 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11298
11299 Disconnect(
11300 wxEVT_COMMAND_MENU_SELECTED,
11301 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11302
11303 delete m_canvasMenu;
11304 m_canvasMenu = NULL;
11305
11306#ifdef __WXQT__
11307 // gFrame->SurfaceToolbar();
11308 // g_MainToolbar->Raise();
11309#endif
11310
11311 return true;
11312}
11313
11314void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11315 // Pass menu events from the canvas to the menu handler
11316 // This is necessarily in ChartCanvas since that is the menu's parent.
11317 if (m_canvasMenu) {
11318 m_canvasMenu->PopupMenuHandler(event);
11319 }
11320 return;
11321}
11322
11323void ChartCanvas::StartRoute() {
11324 // Do not allow more than one canvas to create a route at one time.
11325 if (g_brouteCreating) return;
11326
11327 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11328
11329 g_brouteCreating = true;
11330 m_routeState = 1;
11331 m_bDrawingRoute = false;
11332 SetCursor(*pCursorPencil);
11333 // SetCanvasToolbarItemState(ID_ROUTE, true);
11334 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11335
11336 HideGlobalToolbar();
11337
11338#ifdef __ANDROID__
11339 androidSetRouteAnnunciator(true);
11340#endif
11341}
11342
11343wxString ChartCanvas::FinishRoute() {
11344 m_routeState = 0;
11345 m_prev_pMousePoint = NULL;
11346 m_bDrawingRoute = false;
11347 wxString rv = "";
11348 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11349
11350 // SetCanvasToolbarItemState(ID_ROUTE, false);
11351 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11352#ifdef __ANDROID__
11353 androidSetRouteAnnunciator(false);
11354#endif
11355
11356 SetCursor(*pCursorArrow);
11357
11358 if (m_pMouseRoute) {
11359 if (m_bAppendingRoute) {
11360 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11361 } else {
11362 if (m_pMouseRoute->GetnPoints() > 1) {
11363 // pConfig->AddNewRoute(m_pMouseRoute);
11364 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11365 } else {
11366 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11367 m_pMouseRoute = NULL;
11368 }
11369 }
11370 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11371
11372 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11373 (pRoutePropDialog->IsShown())) {
11374 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11375 }
11376
11377 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11378 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11379 pRouteManagerDialog->UpdateRouteListCtrl();
11380 }
11381 }
11382 m_bAppendingRoute = false;
11383 m_pMouseRoute = NULL;
11384
11385 m_pSelectedRoute = NULL;
11386
11387 undo->InvalidateUndo();
11388 top_frame::Get()->RefreshAllCanvas(true);
11389
11390 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11391
11392 ShowGlobalToolbar();
11393
11394 g_brouteCreating = false;
11395
11396 return rv;
11397}
11398
11399void ChartCanvas::HideGlobalToolbar() {
11400 if (m_canvasIndex == 0) {
11401 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11402 }
11403}
11404
11405void ChartCanvas::ShowGlobalToolbar() {
11406 if (m_canvasIndex == 0) {
11407 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11408 }
11409}
11410
11411void ChartCanvas::ShowAISTargetList() {
11412 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11413 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11414 }
11415
11416 g_pAISTargetList->UpdateAISTargetList();
11417}
11418
11419void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11420 if (!m_bShowOutlines) return;
11421
11422 if (!ChartData) return;
11423
11424 int nEntry = ChartData->GetChartTableEntries();
11425
11426 for (int i = 0; i < nEntry; i++) {
11427 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11428
11429 // Check to see if the candidate chart is in the currently active group
11430 bool b_group_draw = false;
11431 if (m_groupIndex > 0) {
11432 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11433 int index = pt->GetGroupArray()[ig];
11434 if (m_groupIndex == index) {
11435 b_group_draw = true;
11436 break;
11437 }
11438 }
11439 } else
11440 b_group_draw = true;
11441
11442 // Do not render chart outlines if chart identifies as a "basemap"
11443 if (pt->IsBasemap()) continue;
11444
11445 if (b_group_draw) RenderChartOutline(dc, i, vp);
11446 }
11447
11448 // On CM93 Composite Charts, draw the outlines of the next smaller
11449 // scale cell
11450 cm93compchart *pcm93 = NULL;
11451 if (VPoint.b_quilt) {
11452 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11453 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11454 pcm93 = (cm93compchart *)pch;
11455 break;
11456 }
11457 } else if (m_singleChart &&
11458 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11459 pcm93 = (cm93compchart *)m_singleChart;
11460
11461 if (pcm93) {
11462 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11463 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11464
11465 if (zoom_factor > 8.0) {
11466 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11467 dc.SetPen(mPen);
11468 } else {
11469 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11470 dc.SetPen(mPen);
11471 }
11472
11473 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11474 }
11475}
11476
11477void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11478#ifdef ocpnUSE_GL
11479 if (g_bopengl && m_glcc) {
11480 /* opengl version specially optimized */
11481 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11482 return;
11483 }
11484#endif
11485
11486 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11487 if (!ChartData->IsChartAvailable(dbIndex)) return;
11488 }
11489
11490 float plylat, plylon;
11491 float plylat1, plylon1;
11492
11493 int pixx, pixy, pixx1, pixy1;
11494
11495 LLBBox box;
11496 ChartData->GetDBBoundingBox(dbIndex, box);
11497
11498 // Don't draw an outline in the case where the chart covers the entire world
11499 // */
11500 if (box.GetLonRange() == 360) return;
11501
11502 double lon_bias = 0;
11503 // chart is outside of viewport lat/lon bounding box
11504 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11505
11506 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11507
11508 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11509 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11510
11511 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11512 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11513
11514 else
11515 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11516
11517 // Are there any aux ply entries?
11518 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11519 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11520 {
11521 wxPoint r, r1;
11522
11523 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11524 plylon += lon_bias;
11525
11526 GetCanvasPointPix(plylat, plylon, &r);
11527 pixx = r.x;
11528 pixy = r.y;
11529
11530 for (int i = 0; i < nPly - 1; i++) {
11531 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11532 plylon1 += lon_bias;
11533
11534 GetCanvasPointPix(plylat1, plylon1, &r1);
11535 pixx1 = r1.x;
11536 pixy1 = r1.y;
11537
11538 int pixxs1 = pixx1;
11539 int pixys1 = pixy1;
11540
11541 bool b_skip = false;
11542
11543 if (vp.chart_scale > 5e7) {
11544 // calculate projected distance between these two points in meters
11545 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11546 pow((double)(pixy1 - pixy), 2)) /
11547 vp.view_scale_ppm;
11548
11549 if (dist > 0.0) {
11550 // calculate GC distance between these two points in meters
11551 double distgc =
11552 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11553
11554 // If the distances are nonsense, it means that the scale is very
11555 // small and the segment wrapped the world So skip it....
11556 // TODO improve this to draw two segments
11557 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11558 b_skip = true;
11559 } else
11560 b_skip = true;
11561 }
11562
11563 ClipResult res = cohen_sutherland_line_clip_i(
11564 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11565 if (res != Invisible && !b_skip)
11566 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11567
11568 plylat = plylat1;
11569 plylon = plylon1;
11570 pixx = pixxs1;
11571 pixy = pixys1;
11572 }
11573
11574 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11575 plylon1 += lon_bias;
11576
11577 GetCanvasPointPix(plylat1, plylon1, &r1);
11578 pixx1 = r1.x;
11579 pixy1 = r1.y;
11580
11581 ClipResult res = cohen_sutherland_line_clip_i(
11582 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11583 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11584 }
11585
11586 else // Use Aux PlyPoints
11587 {
11588 wxPoint r, r1;
11589
11590 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11591 for (int j = 0; j < nAuxPlyEntries; j++) {
11592 int nAuxPly =
11593 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11594 GetCanvasPointPix(plylat, plylon, &r);
11595 pixx = r.x;
11596 pixy = r.y;
11597
11598 for (int i = 0; i < nAuxPly - 1; i++) {
11599 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11600
11601 GetCanvasPointPix(plylat1, plylon1, &r1);
11602 pixx1 = r1.x;
11603 pixy1 = r1.y;
11604
11605 int pixxs1 = pixx1;
11606 int pixys1 = pixy1;
11607
11608 bool b_skip = false;
11609
11610 if (vp.chart_scale > 5e7) {
11611 // calculate projected distance between these two points in meters
11612 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11613 ((pixy1 - pixy) * (pixy1 - pixy))) /
11614 vp.view_scale_ppm;
11615 if (dist > 0.0) {
11616 // calculate GC distance between these two points in meters
11617 double distgc =
11618 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11619
11620 // If the distances are nonsense, it means that the scale is very
11621 // small and the segment wrapped the world So skip it....
11622 // TODO improve this to draw two segments
11623 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11624 b_skip = true;
11625 } else
11626 b_skip = true;
11627 }
11628
11629 ClipResult res = cohen_sutherland_line_clip_i(
11630 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11631 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11632
11633 plylat = plylat1;
11634 plylon = plylon1;
11635 pixx = pixxs1;
11636 pixy = pixys1;
11637 }
11638
11639 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11640 GetCanvasPointPix(plylat1, plylon1, &r1);
11641 pixx1 = r1.x;
11642 pixy1 = r1.y;
11643
11644 ClipResult res = cohen_sutherland_line_clip_i(
11645 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11646 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11647 }
11648 }
11649}
11650
11651static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11652 const wxArrayString &legend) {
11653 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11654
11655 int pointsize = dFont->GetPointSize();
11656 pointsize /= OCPN_GetWinDIPScaleFactor();
11657
11658 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11659 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11660 false, dFont->GetFaceName());
11661
11662 dc.SetFont(*psRLI_font);
11663
11664 int h = 0;
11665 int w = 0;
11666 int hl, wl;
11667
11668 int xp, yp;
11669 int hilite_offset = 3;
11670
11671 for (wxString line : legend) {
11672#ifdef __WXMAC__
11673 wxScreenDC sdc;
11674 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11675#else
11676 dc.GetTextExtent(line, &wl, &hl);
11677 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11678 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11679#endif
11680 h += hl;
11681 w = wxMax(w, wl);
11682 }
11683 w += (hl / 2); // Add a little right pad
11684
11685 xp = ref_point.x - w;
11686 yp = ref_point.y;
11687 yp += hilite_offset;
11688
11689 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11690
11691 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11692 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11693
11694 for (wxString line : legend) {
11695 dc.DrawText(line, xp, yp);
11696 yp += hl;
11697 }
11698}
11699
11700void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11701 if (!g_bAllowShipToActive) return;
11702
11703 Route *rt = g_pRouteMan->GetpActiveRoute();
11704 if (!rt) return;
11705
11706 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11707 wxPoint2DDouble pa, pb;
11709 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11710
11711 // set pen
11712 int width =
11713 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11714 if (rt->m_width != wxPENSTYLE_INVALID)
11715 width = rt->m_width; // set route pen style if any
11716 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11717 g_shipToActiveStyle, 5)]; // get setting pen style
11718 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11719 wxColour color =
11720 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11721 : // set setting route pen color
11722 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11723 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11724
11725 dc.SetPen(*mypen);
11726 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11727
11728 if (!Use_Opengl)
11729 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11730 (int)pb.m_y, GetVP(), true);
11731
11732#ifdef ocpnUSE_GL
11733 else {
11734#ifdef USE_ANDROID_GLES2
11735 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11736#else
11737 if (style != wxPENSTYLE_SOLID) {
11738 if (glChartCanvas::dash_map.find(style) !=
11739 glChartCanvas::dash_map.end()) {
11740 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11741 dc.SetPen(*mypen);
11742 }
11743 }
11744 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11745#endif
11746
11747 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11748 (int)pb.m_x, (int)pb.m_y, GetVP());
11749 }
11750#endif
11751 }
11752}
11753
11754void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11755 Route *route = 0;
11756 if (m_routeState >= 2) route = m_pMouseRoute;
11757 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11758 route = m_pMeasureRoute;
11759
11760 if (!route) return;
11761
11762 // Validate route pointer
11763 if (!g_pRouteMan->IsRouteValid(route)) return;
11764
11765 double render_lat = m_cursor_lat;
11766 double render_lon = m_cursor_lon;
11767
11768 int np = route->GetnPoints();
11769 if (np) {
11770 if (g_btouch && (np > 1)) np--;
11771 RoutePoint rp = route->GetPoint(np);
11772 render_lat = rp.m_lat;
11773 render_lon = rp.m_lon;
11774 }
11775
11776 double rhumbBearing, rhumbDist;
11777 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11778 &rhumbBearing, &rhumbDist);
11779 double brg = rhumbBearing;
11780 double dist = rhumbDist;
11781
11782 // Skip GreatCircle rubberbanding on touch devices.
11783 if (!g_btouch) {
11784 double gcBearing, gcBearing2, gcDist;
11785 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11786 m_cursor_lat, &gcDist, &gcBearing,
11787 &gcBearing2);
11788 double gcDistm = gcDist / 1852.0;
11789
11790 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11791 rhumbBearing = 90.;
11792
11793 wxPoint destPoint, lastPoint;
11794
11795 route->m_NextLegGreatCircle = false;
11796 int milesDiff = rhumbDist - gcDistm;
11797 if (milesDiff > 1) {
11798 brg = gcBearing;
11799 dist = gcDistm;
11800 route->m_NextLegGreatCircle = true;
11801 }
11802
11803 // FIXME (MacOS, the first segment is rendered wrong)
11804 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11805 &lastPoint);
11806
11807 if (route->m_NextLegGreatCircle) {
11808 for (int i = 1; i <= milesDiff; i++) {
11809 double p = (double)i * (1.0 / (double)milesDiff);
11810 double pLat, pLon;
11811 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11812 &pLon, &pLat, &gcBearing2);
11813 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11814 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11815 false);
11816 lastPoint = destPoint;
11817 }
11818 } else {
11819 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11820 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11821 false);
11822 if (m_bMeasure_DistCircle) {
11823 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11824 powf((float)(r_rband.y - lastPoint.y), 2));
11825
11826 dc.SetPen(*g_pRouteMan->GetRoutePen());
11827 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11828 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11829 }
11830 }
11831 }
11832 }
11833
11834 wxString routeInfo;
11835 wxArrayString infoArray;
11836 double varBrg = 0;
11837 if (g_bShowTrue)
11838 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11839 0x00B0);
11840
11841 if (g_bShowMag) {
11842 double latAverage = (m_cursor_lat + render_lat) / 2;
11843 double lonAverage = (m_cursor_lon + render_lon) / 2;
11844 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11845
11846 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11847 (int)varBrg, 0x00B0);
11848 }
11849 routeInfo << " " << FormatDistanceAdaptive(dist);
11850 infoArray.Add(routeInfo);
11851 routeInfo.Clear();
11852
11853 // To make it easier to use a route as a bearing on a charted object add for
11854 // the first leg also the reverse bearing.
11855 if (np == 1) {
11856 routeInfo << "Reverse: ";
11857 if (g_bShowTrue)
11858 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11859 (int)(brg + 180.) % 360, 0x00B0);
11860 if (g_bShowMag)
11861 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11862 (int)(varBrg + 180.) % 360, 0x00B0);
11863 infoArray.Add(routeInfo);
11864 routeInfo.Clear();
11865 }
11866
11867 wxString s0;
11868 if (!route->m_bIsInLayer)
11869 s0.Append(_("Route") + ": ");
11870 else
11871 s0.Append(_("Layer Route: "));
11872
11873 double disp_length = route->m_route_length;
11874 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11875 s0 += FormatDistanceAdaptive(disp_length);
11876
11877 infoArray.Add(s0);
11878 routeInfo.Clear();
11879
11880 RouteLegInfo(dc, r_rband, infoArray);
11881
11882 m_brepaint_piano = true;
11883}
11884
11885void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11886 if (!m_bShowVisibleSectors) return;
11887
11888 if (g_bDeferredInitDone) {
11889 // need to re-evaluate sectors?
11890 double rhumbBearing, rhumbDist;
11891 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11892 &rhumbBearing, &rhumbDist);
11893
11894 if (rhumbDist > 0.05) // miles
11895 {
11896 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11897 m_sectorlegsVisible);
11898 m_sector_glat = gLat;
11899 m_sector_glon = gLon;
11900 }
11901 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11902 }
11903}
11904
11905void ChartCanvas::WarpPointerDeferred(int x, int y) {
11906 warp_x = x;
11907 warp_y = y;
11908 warp_flag = true;
11909}
11910
11911int s_msg;
11912
11913void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11914 if (!ps52plib) return;
11915
11916 if (VPoint.b_quilt) { // quilted
11917 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11918
11919 if (m_pQuilt->IsQuiltVector()) {
11920 if (ps52plib->GetStateHash() != m_s52StateHash) {
11921 UpdateS52State();
11922 m_s52StateHash = ps52plib->GetStateHash();
11923 }
11924 }
11925 } else {
11926 if (ps52plib->GetStateHash() != m_s52StateHash) {
11927 UpdateS52State();
11928 m_s52StateHash = ps52plib->GetStateHash();
11929 }
11930 }
11931
11932 // Plugin charts
11933 bool bSendPlibState = true;
11934 if (VPoint.b_quilt) { // quilted
11935 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11936 }
11937
11938 if (bSendPlibState) {
11939 nlohmann::json v;
11940 v["OpenCPN Version Major"] = VERSION_MAJOR;
11941 v["OpenCPN Version Minor"] = VERSION_MINOR;
11942 v["OpenCPN Version Patch"] = VERSION_PATCH;
11943 v["OpenCPN Version Date"] = VERSION_DATE;
11944 v["OpenCPN Version Full"] = VERSION_FULL;
11945
11946 // S52PLIB state
11947 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11948 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11949 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11950 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11951 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11952 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11953 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11954
11955 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11956
11957 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11958 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11959
11960 // Global S52 options
11961
11962 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11963 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11964 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11965 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11966 ps52plib->m_bShowS57ImportantTextOnly;
11967 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11968 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11969 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11970 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11971 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11972
11973 // Some global GUI parameters, for completeness
11974 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11975 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11976 v["OpenCPN Scale Factor Exp"] =
11977 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11978 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11979
11980 const std::string out = v.dump();
11981
11982 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11983 SendMessageToAllPlugins("OpenCPN Config", out);
11984 g_lastS52PLIBPluginMessage = out;
11985 }
11986 }
11987}
11988int spaint;
11989int s_in_update;
11990void ChartCanvas::OnPaint(wxPaintEvent &event) {
11991 wxPaintDC dc(this);
11992
11993 // GetToolbar()->Show( m_bToolbarEnable );
11994
11995 // Paint updates may have been externally disabled (temporarily, to avoid
11996 // Yield() recursion performance loss) It is important that the wxPaintDC is
11997 // built, even if we elect to not process this paint message. Otherwise, the
11998 // paint message may not be removed from the message queue, esp on Windows.
11999 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
12000
12001 if (!m_b_paint_enable) {
12002 return;
12003 }
12004
12005 // If necessary, reconfigure the S52 PLIB
12007
12008#ifdef ocpnUSE_GL
12009 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
12010
12011 if (m_glcc && g_bopengl) {
12012 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
12013 s_in_update++;
12014 m_glcc->Update();
12015 s_in_update--;
12016 }
12017
12018 return;
12019 }
12020#endif
12021
12022 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
12023
12024 wxRegion ru = GetUpdateRegion();
12025
12026 int rx, ry, rwidth, rheight;
12027 ru.GetBox(rx, ry, rwidth, rheight);
12028
12029#ifdef ocpnUSE_DIBSECTION
12030 ocpnMemDC temp_dc;
12031#else
12032 wxMemoryDC temp_dc;
12033#endif
12034
12035 long height = GetVP().pix_height;
12036
12037#ifdef __WXMAC__
12038 // On OS X we have to explicitly extend the region for the piano area
12039 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12040 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
12041 height += m_Piano->GetHeight();
12042#endif // __WXMAC__
12043 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
12044
12045 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
12046 if (pthumbwin) {
12047 int thumbx, thumby, thumbsx, thumbsy;
12048 pthumbwin->GetPosition(&thumbx, &thumby);
12049 pthumbwin->GetSize(&thumbsx, &thumbsy);
12050 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
12051
12052 if (pthumbwin->IsShown()) {
12053 rgn_chart.Subtract(rgn_thumbwin);
12054 ru.Subtract(rgn_thumbwin);
12055 }
12056 }
12057
12058 // subtract the chart bar if it isn't transparent, and determine if we need to
12059 // paint it
12060 wxRegion rgn_blit = ru;
12061 if (g_bShowChartBar) {
12062 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
12063 GetClientSize().x, m_Piano->GetHeight());
12064
12065 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12066 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
12067 if (style->chartStatusWindowTransparent)
12068 m_brepaint_piano = true;
12069 else
12070 ru.Subtract(chart_bar_rect);
12071 }
12072 }
12073
12074 if (m_Compass && m_Compass->IsShown()) {
12075 wxRect compassRect = m_Compass->GetRect();
12076 if (ru.Contains(compassRect) != wxOutRegion) {
12077 ru.Subtract(compassRect);
12078 }
12079 }
12080
12081 if (m_notification_button) {
12082 wxRect noteRect = m_notification_button->GetRect();
12083 if (ru.Contains(noteRect) != wxOutRegion) {
12084 ru.Subtract(noteRect);
12085 }
12086 }
12087
12088 // Is this viewpoint the same as the previously painted one?
12089 bool b_newview = true;
12090
12091 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12092 (m_cache_vp.rotation == VPoint.rotation) &&
12093 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
12094 m_cache_vp.IsValid()) {
12095 b_newview = false;
12096 }
12097
12098 // If the ViewPort is skewed or rotated, we may be able to use the cached
12099 // rotated bitmap.
12100 bool b_rcache_ok = false;
12101 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
12102 b_rcache_ok = !b_newview;
12103
12104 // Make a special VP
12105 if (VPoint.b_MercatorProjectionOverride)
12106 VPoint.SetProjectionType(PROJECTION_MERCATOR);
12107 ViewPort svp = VPoint;
12108
12109 svp.pix_width = svp.rv_rect.width;
12110 svp.pix_height = svp.rv_rect.height;
12111
12112 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
12113 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
12114 // VPoint.rv_rect.height);
12115
12116 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
12117
12118 // If we are going to use the cached rotated image, there is no need to fetch
12119 // any chart data and this will do it...
12120 if (b_rcache_ok) chart_get_region.Clear();
12121
12122 // Blit pan acceleration
12123 if (VPoint.b_quilt) // quilted
12124 {
12125 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12126
12127 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12128
12129 bool busy = false;
12130 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12131 m_cache_vp.rotation != VPoint.rotation)) {
12132 AbstractPlatform::ShowBusySpinner();
12133 busy = true;
12134 }
12135
12136 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12137 (m_working_bm.GetHeight() != svp.pix_height))
12138 m_working_bm.Create(svp.pix_width, svp.pix_height,
12139 -1); // make sure the target is big enoug
12140
12141 if (fabs(VPoint.rotation) < 0.01) {
12142 bool b_save = true;
12143
12144 if (g_SencThreadManager) {
12145 if (g_SencThreadManager->GetJobCount()) {
12146 b_save = false;
12147 m_cache_vp.Invalidate();
12148 }
12149 }
12150
12151 // If the saved wxBitmap from last OnPaint is useable
12152 // calculate the blit parameters
12153
12154 // We can only do screen blit painting if subsequent ViewPorts differ by
12155 // whole pixels So, in small scale bFollow mode, force the full screen
12156 // render. This seems a hack....There may be better logic here.....
12157
12158 // if(m_bFollow)
12159 // b_save = false;
12160
12161 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12162 if (b_newview) {
12163 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12164 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12165
12166 int dy = c_new.y - c_old.y;
12167 int dx = c_new.x - c_old.x;
12168
12169 // printf("In OnPaint Trying Blit dx: %d
12170 // dy:%d\n\n", dx, dy);
12171
12172 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12173 if (dx || dy) {
12174 // Blit the reuseable portion of the cached wxBitmap to a working
12175 // bitmap
12176 temp_dc.SelectObject(m_working_bm);
12177
12178 wxMemoryDC cache_dc;
12179 cache_dc.SelectObject(m_cached_chart_bm);
12180
12181 if (dy > 0) {
12182 if (dx > 0) {
12183 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12184 VPoint.pix_height - dy, &cache_dc, dx, dy);
12185 } else {
12186 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12187 VPoint.pix_height - dy, &cache_dc, 0, dy);
12188 }
12189
12190 } else {
12191 if (dx > 0) {
12192 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12193 VPoint.pix_height + dy, &cache_dc, dx, 0);
12194 } else {
12195 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12196 VPoint.pix_height + dy, &cache_dc, 0, 0);
12197 }
12198 }
12199
12200 OCPNRegion update_region;
12201 if (dy) {
12202 if (dy > 0)
12203 update_region.Union(
12204 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12205 else
12206 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12207 }
12208
12209 if (dx) {
12210 if (dx > 0)
12211 update_region.Union(
12212 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12213 else
12214 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12215 }
12216
12217 // Render the new region
12218 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12219 update_region);
12220 cache_dc.SelectObject(wxNullBitmap);
12221 } else {
12222 // No sensible (dx, dy) change in the view, so use the cached
12223 // member bitmap
12224 temp_dc.SelectObject(m_cached_chart_bm);
12225 b_save = false;
12226 }
12227 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12228
12229 } else // not blitable
12230 {
12231 temp_dc.SelectObject(m_working_bm);
12232 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12233 chart_get_region);
12234 }
12235 } else {
12236 // No change in the view, so use the cached member bitmap2
12237 temp_dc.SelectObject(m_cached_chart_bm);
12238 b_save = false;
12239 }
12240 } else // cached bitmap is not yet valid
12241 {
12242 temp_dc.SelectObject(m_working_bm);
12243 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12244 chart_get_region);
12245 }
12246
12247 // Save the fully rendered quilt image as a wxBitmap member of this class
12248 if (b_save) {
12249 // if((m_cached_chart_bm.GetWidth() !=
12250 // svp.pix_width) ||
12251 // (m_cached_chart_bm.GetHeight() !=
12252 // svp.pix_height))
12253 // m_cached_chart_bm.Create(svp.pix_width,
12254 // svp.pix_height, -1); // target wxBitmap
12255 // is big enough
12256 wxMemoryDC scratch_dc_0;
12257 scratch_dc_0.SelectObject(m_cached_chart_bm);
12258 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12259
12260 scratch_dc_0.SelectObject(wxNullBitmap);
12261
12262 m_bm_cache_vp =
12263 VPoint; // save the ViewPort associated with the cached wxBitmap
12264 }
12265 }
12266
12267 else // quilted, rotated
12268 {
12269 temp_dc.SelectObject(m_working_bm);
12270 OCPNRegion chart_get_all_region(
12271 wxRect(0, 0, svp.pix_width, svp.pix_height));
12272 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12273 chart_get_all_region);
12274 }
12275
12276 AbstractPlatform::HideBusySpinner();
12277
12278 }
12279
12280 else // not quilted
12281 {
12282 if (!m_singleChart) {
12283 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12284 dc.Clear();
12285 return;
12286 }
12287
12288 if (!chart_get_region.IsEmpty()) {
12289 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12290 }
12291 }
12292
12293 if (temp_dc.IsOk()) {
12294 // Arrange to render the World Chart vector data behind the rendered
12295 // current chart so that uncovered canvas areas show at least the world
12296 // chart.
12297 OCPNRegion chartValidRegion;
12298 if (!VPoint.b_quilt) {
12299 // Make a region covering the current chart on the canvas
12300
12301 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12302 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12303 else {
12304 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12305 // require that the viewport passed here have pix_width and pix_height
12306 // set to the actual display, not the virtual (rv_rect) sizes
12307 // (the vector calculations require the virtual sizes in svp)
12308
12309 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12310 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12311 }
12312 } else
12313 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12314
12315 temp_dc.DestroyClippingRegion();
12316
12317 // Copy current chart region
12318 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12319
12320 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12321
12322 if (!backgroundRegion.IsEmpty()) {
12323 // Draw the Background Chart only in the areas NOT covered by the
12324 // current chart view
12325
12326 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12327 clipping regions with more than 1 rectangle so... */
12328 wxColour water = pWorldBackgroundChart->water;
12329 if (water.IsOk()) {
12330 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12331 temp_dc.SetBrush(wxBrush(water));
12332 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12333 while (upd.HaveRects()) {
12334 wxRect rect = upd.GetRect();
12335 temp_dc.DrawRectangle(rect);
12336 upd.NextRect();
12337 }
12338 }
12339 // Associate with temp_dc
12340 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12341 temp_dc.SetDeviceClippingRegion(*clip_region);
12342 delete clip_region;
12343
12344 ocpnDC bgdc(temp_dc);
12345 double r = VPoint.rotation;
12346 SetVPRotation(VPoint.skew);
12347
12348 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12349 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12350
12351 SetVPRotation(r);
12352 }
12353 } // temp_dc.IsOk();
12354
12355 wxMemoryDC *pChartDC = &temp_dc;
12356 wxMemoryDC rotd_dc;
12357
12358 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12359 // Can we use the current rotated image cache?
12360 if (!b_rcache_ok) {
12361#ifdef __WXMSW__
12362 wxMemoryDC tbase_dc;
12363 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12364 tbase_dc.SelectObject(bm_base);
12365 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12366 tbase_dc.SelectObject(wxNullBitmap);
12367#else
12368 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12369#endif
12370
12371 wxImage base_image;
12372 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12373
12374 // Use a local static image rotator to improve wxWidgets code profile
12375 // Especially, on GTK the wxRound and wxRealPoint functions are very
12376 // expensive.....
12377
12378 double angle = GetVP().skew - GetVP().rotation;
12379 wxImage ri;
12380 bool b_rot_ok = false;
12381 if (base_image.IsOk()) {
12382 ViewPort rot_vp = GetVP();
12383
12384 m_b_rot_hidef = false;
12385
12386 ri = Image_Rotate(
12387 base_image, angle,
12388 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12389 m_b_rot_hidef, &m_roffset);
12390
12391 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12392 (rot_vp.rotation == VPoint.rotation) &&
12393 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12394 rot_vp.IsValid() && (ri.IsOk())) {
12395 b_rot_ok = true;
12396 }
12397 }
12398
12399 if (b_rot_ok) {
12400 delete m_prot_bm;
12401 m_prot_bm = new wxBitmap(ri);
12402 }
12403
12404 m_roffset.x += VPoint.rv_rect.x;
12405 m_roffset.y += VPoint.rv_rect.y;
12406 }
12407
12408 if (m_prot_bm && m_prot_bm->IsOk()) {
12409 rotd_dc.SelectObject(*m_prot_bm);
12410 pChartDC = &rotd_dc;
12411 } else {
12412 pChartDC = &temp_dc;
12413 m_roffset = wxPoint(0, 0);
12414 }
12415 } else { // unrotated
12416 pChartDC = &temp_dc;
12417 m_roffset = wxPoint(0, 0);
12418 }
12419
12420 wxPoint offset = m_roffset;
12421
12422 // Save the PixelCache viewpoint for next time
12423 m_cache_vp = VPoint;
12424
12425 // Set up a scratch DC for overlay objects
12426 wxMemoryDC mscratch_dc;
12427 mscratch_dc.SelectObject(*pscratch_bm);
12428
12429 mscratch_dc.ResetBoundingBox();
12430 mscratch_dc.DestroyClippingRegion();
12431 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12432
12433 // Blit the externally invalidated areas of the chart onto the scratch dc
12434 wxRegionIterator upd(rgn_blit); // get the update rect list
12435 while (upd) {
12436 wxRect rect = upd.GetRect();
12437
12438 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12439 rect.x - offset.x, rect.y - offset.y);
12440 upd++;
12441 }
12442
12443 // If multi-canvas, indicate which canvas has keyboard focus
12444 // by drawing a simple blue bar at the top.
12445 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12446 if (this == wxWindow::FindFocus()) {
12447 g_focusCanvas = this;
12448
12449 wxColour colour = GetGlobalColor("BLUE4");
12450 mscratch_dc.SetPen(wxPen(colour));
12451 mscratch_dc.SetBrush(wxBrush(colour));
12452
12453 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12454 mscratch_dc.DrawRectangle(activeRect);
12455 }
12456 }
12457
12458 // Any MBtiles?
12459 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12460 unsigned int im = stackIndexArray.size();
12461 if (VPoint.b_quilt && im > 0) {
12462 std::vector<int> tiles_to_show;
12463 for (unsigned int is = 0; is < im; is++) {
12464 const ChartTableEntry &cte =
12465 ChartData->GetChartTableEntry(stackIndexArray[is]);
12466 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12467 continue;
12468 }
12469 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12470 tiles_to_show.push_back(stackIndexArray[is]);
12471 }
12472 }
12473
12474 if (tiles_to_show.size())
12475 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12476 }
12477
12478 // May get an unexpected OnPaint call while switching display modes
12479 // Guard for that.
12480 if (!g_bopengl) {
12481 ocpnDC scratch_dc(mscratch_dc);
12482 RenderAlertMessage(mscratch_dc, GetVP());
12483 }
12484
12485#if 0
12486 // quiting?
12487 if (g_bquiting) {
12488#ifdef ocpnUSE_DIBSECTION
12489 ocpnMemDC q_dc;
12490#else
12491 wxMemoryDC q_dc;
12492#endif
12493 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12494 q_dc.SelectObject(qbm);
12495
12496 // Get a copy of the screen
12497 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12498
12499 // Draw a rectangle over the screen with a stipple brush
12500 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12501 q_dc.SetBrush(qbr);
12502 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12503
12504 // Blit back into source
12505 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12506 wxCOPY);
12507
12508 q_dc.SelectObject(wxNullBitmap);
12509 }
12510#endif
12511
12512#if 0
12513 // It is possible that this two-step method may be reuired for some platforms.
12514 // So, retain in the code base to aid recovery if necessary
12515
12516 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12517 if( VPoint.b_quilt ) {
12518 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12519 ChartBase *chart = m_pQuilt->GetRefChart();
12520 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12521
12522 // Clear the text Global declutter list
12523 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12524 if(ChPI)
12525 ChPI->ClearPLIBTextList();
12526 else{
12527 if(ps52plib)
12528 ps52plib->ClearTextList();
12529 }
12530
12531 wxMemoryDC t_dc;
12532 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12533
12534 wxColor maskBackground = wxColour(1,0,0);
12535 t_dc.SelectObject( qbm );
12536 t_dc.SetBackground(wxBrush(maskBackground));
12537 t_dc.Clear();
12538
12539 // Copy the scratch DC into the new bitmap
12540 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12541
12542 // Render the text to the new bitmap
12543 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12544 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12545
12546 // Copy the new bitmap back to the scratch dc
12547 wxRegionIterator upd_final( ru );
12548 while( upd_final ) {
12549 wxRect rect = upd_final.GetRect();
12550 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12551 upd_final++;
12552 }
12553
12554 t_dc.SelectObject( wxNullBitmap );
12555 }
12556 }
12557 }
12558#endif
12559 // Direct rendering model...
12560 if (VPoint.b_quilt) {
12561 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12562 ChartBase *chart = m_pQuilt->GetRefChart();
12563 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12564 // Clear the text Global declutter list
12565 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12566 if (ChPI)
12567 ChPI->ClearPLIBTextList();
12568 else {
12569 if (ps52plib) ps52plib->ClearTextList();
12570 }
12571
12572 // Render the text directly to the scratch bitmap
12573 OCPNRegion chart_all_text_region(
12574 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12575
12576 if (g_bShowChartBar && m_Piano) {
12577 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12578 GetVP().pix_width, m_Piano->GetHeight());
12579
12580 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12581 if (!style->chartStatusWindowTransparent)
12582 chart_all_text_region.Subtract(chart_bar_rect);
12583 }
12584
12585 if (m_Compass && m_Compass->IsShown()) {
12586 wxRect compassRect = m_Compass->GetRect();
12587 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12588 chart_all_text_region.Subtract(compassRect);
12589 }
12590 }
12591
12592 mscratch_dc.DestroyClippingRegion();
12593
12594 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12595 chart_all_text_region);
12596 }
12597 }
12598 }
12599
12600 // Now that charts are fully rendered, apply the overlay objects as decals.
12601 ocpnDC scratch_dc(mscratch_dc);
12602 DrawOverlayObjects(scratch_dc, ru);
12603
12604 // And finally, blit the scratch dc onto the physical dc
12605 wxRegionIterator upd_final(rgn_blit);
12606 while (upd_final) {
12607 wxRect rect = upd_final.GetRect();
12608 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12609 rect.y);
12610 upd_final++;
12611 }
12612
12613 // Deselect the chart bitmap from the temp_dc, so that it will not be
12614 // destroyed in the temp_dc dtor
12615 temp_dc.SelectObject(wxNullBitmap);
12616 // And for the scratch bitmap
12617 mscratch_dc.SelectObject(wxNullBitmap);
12618
12619 dc.DestroyClippingRegion();
12620
12621 PaintCleanup();
12622}
12623
12624void ChartCanvas::PaintCleanup() {
12625 // Handle the current graphic window, if present
12626 if (m_inPinch) return;
12627
12628 if (pCwin) {
12629 pCwin->Show();
12630 if (m_bTCupdate) {
12631 pCwin->Refresh();
12632 pCwin->Update();
12633 }
12634 }
12635
12636 // And set flags for next time
12637 m_bTCupdate = false;
12638
12639 // Handle deferred WarpPointer
12640 if (warp_flag) {
12641 WarpPointer(warp_x, warp_y);
12642 warp_flag = false;
12643 }
12644
12645 // Start movement timers, this runs nearly immediately.
12646 // the reason we cannot simply call it directly is the
12647 // refresh events it emits may be blocked from this paint event
12648 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12649 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12650}
12651
12652#if 0
12653wxColour GetErrorGraphicColor(double val)
12654{
12655 /*
12656 double valm = wxMin(val_max, val);
12657
12658 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12659 unsigned char red = (unsigned char)(255 * (valm/val_max));
12660
12661 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12662
12663 hv.saturation = 1.0;
12664 hv.value = 1.0;
12665
12666 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12667 return wxColour(rv.red, rv.green, rv.blue);
12668 */
12669
12670 // HTML colors taken from NOAA WW3 Web representation
12671 wxColour c;
12672 if((val > 0) && (val < 1)) c.Set("#002ad9");
12673 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12674 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12675 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12676 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12677 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12678 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12679 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12680 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12681 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12682 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12683 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12684 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12685 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12686 else if((val >= 30) && (val < 36)) c.Set("#870000");
12687 else if((val >= 36) && (val < 42)) c.Set("#690000");
12688 else if((val >= 42) && (val < 48)) c.Set("#550000");
12689 else if( val >= 48) c.Set("#410000");
12690
12691 return c;
12692}
12693
12694void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12695{
12696 wxImage gr_image(vp->pix_width, vp->pix_height);
12697 gr_image.InitAlpha();
12698
12699 double maxval = -10000;
12700 double minval = 10000;
12701
12702 double rlat, rlon;
12703 double glat, glon;
12704
12705 GetCanvasPixPoint(0, 0, rlat, rlon);
12706
12707 for(int i=1; i < vp->pix_height-1; i++)
12708 {
12709 for(int j=0; j < vp->pix_width; j++)
12710 {
12711 // Reference mercator value
12712// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12713
12714 // Georef value
12715 GetCanvasPixPoint(j, i, glat, glon);
12716
12717 maxval = wxMax(maxval, (glat - rlat));
12718 minval = wxMin(minval, (glat - rlat));
12719
12720 }
12721 rlat = glat;
12722 }
12723
12724 GetCanvasPixPoint(0, 0, rlat, rlon);
12725 for(int i=1; i < vp->pix_height-1; i++)
12726 {
12727 for(int j=0; j < vp->pix_width; j++)
12728 {
12729 // Reference mercator value
12730// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12731
12732 // Georef value
12733 GetCanvasPixPoint(j, i, glat, glon);
12734
12735 double f = ((glat - rlat)-minval)/(maxval - minval);
12736
12737 double dy = (f * 40);
12738
12739 wxColour c = GetErrorGraphicColor(dy);
12740 unsigned char r = c.Red();
12741 unsigned char g = c.Green();
12742 unsigned char b = c.Blue();
12743
12744 gr_image.SetRGB(j, i, r,g,b);
12745 if((glat - rlat )!= 0)
12746 gr_image.SetAlpha(j, i, 128);
12747 else
12748 gr_image.SetAlpha(j, i, 255);
12749
12750 }
12751 rlat = glat;
12752 }
12753
12754 // Create a Bitmap
12755 wxBitmap *pbm = new wxBitmap(gr_image);
12756 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12757 pbm->SetMask(gr_mask);
12758
12759 pmdc->DrawBitmap(*pbm, 0,0);
12760
12761 delete pbm;
12762
12763}
12764
12765#endif
12766
12767void ChartCanvas::CancelMouseRoute() {
12768 m_routeState = 0;
12769 m_pMouseRoute = NULL;
12770 m_bDrawingRoute = false;
12771}
12772
12773int ChartCanvas::GetNextContextMenuId() {
12774 return CanvasMenuHandler::GetNextContextMenuId();
12775}
12776
12777bool ChartCanvas::SetCursor(const wxCursor &c) {
12778#ifdef ocpnUSE_GL
12779 if (g_bopengl && m_glcc)
12780 return m_glcc->SetCursor(c);
12781 else
12782#endif
12783 return wxWindow::SetCursor(c);
12784}
12785
12786void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12787 if (g_bquiting) return;
12788 // Keep the mouse position members up to date
12789 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12790
12791 // Retrigger the route leg popup timer
12792 // This handles the case when the chart is moving in auto-follow mode,
12793 // but no user mouse input is made. The timer handler may Hide() the
12794 // popup if the chart moved enough n.b. We use slightly longer oneshot
12795 // value to allow this method's Refresh() to complete before potentially
12796 // getting another Refresh() in the popup timer handler.
12797 if (!m_RolloverPopupTimer.IsRunning() &&
12798 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12799 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12800 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12801 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12802
12803#ifdef ocpnUSE_GL
12804 if (m_glcc && g_bopengl) {
12805 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12806 // overlay objects.
12807 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12808
12809 m_glcc->Refresh(eraseBackground,
12810 NULL); // We always are going to render the entire screen
12811 // anyway, so make
12812 // sure that the window managers understand the invalid area
12813 // is actually the entire client area.
12814
12815 // We need to selectively Refresh some child windows, if they are visible.
12816 // Note that some children are refreshed elsewhere on timer ticks, so don't
12817 // need attention here.
12818
12819 // Thumbnail chart
12820 if (pthumbwin && pthumbwin->IsShown()) {
12821 pthumbwin->Raise();
12822 pthumbwin->Refresh(false);
12823 }
12824
12825 // ChartInfo window
12826 if (m_pCIWin && m_pCIWin->IsShown()) {
12827 m_pCIWin->Raise();
12828 m_pCIWin->Refresh(false);
12829 }
12830
12831 // if(g_MainToolbar)
12832 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12833
12834 } else
12835#endif
12836 wxWindow::Refresh(eraseBackground, rect);
12837}
12838
12839void ChartCanvas::Update() {
12840 if (m_glcc && g_bopengl) {
12841#ifdef ocpnUSE_GL
12842 m_glcc->Update();
12843#endif
12844 } else
12845 wxWindow::Update();
12846}
12847
12848void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12849 if (!pemboss) return;
12850 int x = pemboss->x, y = pemboss->y;
12851 const double factor = 200;
12852
12853 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12854 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12855 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12856
12857 // Grab a snipped image out of the chart
12858 wxMemoryDC snip_dc;
12859 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12860 snip_dc.SelectObject(snip_bmp);
12861
12862 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12863 snip_dc.SelectObject(wxNullBitmap);
12864
12865 wxImage snip_img = snip_bmp.ConvertToImage();
12866
12867 // Apply Emboss map to the snip image
12868 unsigned char *pdata = snip_img.GetData();
12869 if (pdata) {
12870 for (int y = 0; y < pemboss->height; y++) {
12871 int map_index = (y * pemboss->width);
12872 for (int x = 0; x < pemboss->width; x++) {
12873 double val = (pemboss->pmap[map_index] * factor) / 256.;
12874
12875 int nred = (int)((*pdata) + val);
12876 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12877 *pdata++ = (unsigned char)nred;
12878
12879 int ngreen = (int)((*pdata) + val);
12880 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12881 *pdata++ = (unsigned char)ngreen;
12882
12883 int nblue = (int)((*pdata) + val);
12884 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12885 *pdata++ = (unsigned char)nblue;
12886
12887 map_index++;
12888 }
12889 }
12890 }
12891
12892 // Convert embossed snip to a bitmap
12893 wxBitmap emb_bmp(snip_img);
12894
12895 // Map to another memoryDC
12896 wxMemoryDC result_dc;
12897 result_dc.SelectObject(emb_bmp);
12898
12899 // Blit to target
12900 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12901
12902 result_dc.SelectObject(wxNullBitmap);
12903}
12904
12905emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12906 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12907
12908 if (GetQuiltMode()) {
12909 // disable Overzoom indicator for MBTiles
12910 int refIndex = GetQuiltRefChartdbIndex();
12911 if (refIndex >= 0) {
12912 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12913 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12914 if (current_type == CHART_TYPE_MBTILES) {
12915 ChartBase *pChart = m_pQuilt->GetRefChart();
12916 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12917 if (ptc) {
12918 zoom_factor = ptc->GetZoomFactor();
12919 }
12920 }
12921 }
12922
12923 if (zoom_factor <= 3.9) return NULL;
12924 } else {
12925 if (m_singleChart) {
12926 if (zoom_factor <= 3.9) return NULL;
12927 } else
12928 return NULL;
12929 }
12930
12931 if (m_pEM_OverZoom) {
12932 m_pEM_OverZoom->x = 4;
12933 m_pEM_OverZoom->y = 0;
12934 if (g_MainToolbar && IsPrimaryCanvas()) {
12935 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12936 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12937 }
12938 }
12939 return m_pEM_OverZoom;
12940}
12941
12942void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12943 GridDraw(dc);
12944
12945 // bool pluginOverlayRender = true;
12946 //
12947 // if(g_canvasConfig > 0){ // Multi canvas
12948 // if(IsPrimaryCanvas())
12949 // pluginOverlayRender = false;
12950 // }
12951
12952 g_overlayCanvas = this;
12953
12954 if (g_pi_manager) {
12955 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12956 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12958 }
12959
12960 AISDrawAreaNotices(dc, GetVP(), this);
12961
12962 wxDC *pdc = dc.GetDC();
12963 if (pdc) {
12964 pdc->DestroyClippingRegion();
12965 wxDCClipper(*pdc, ru);
12966 }
12967
12968 if (m_bShowNavobjects) {
12969 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12970 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12971 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12972 DrawAnchorWatchPoints(dc);
12973 } else {
12974 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12975 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12976 }
12977
12978 AISDraw(dc, GetVP(), this);
12979 ShipDraw(dc);
12980 AlertDraw(dc);
12981
12982 RenderVisibleSectorLights(dc);
12983
12984 RenderAllChartOutlines(dc, GetVP());
12985 RenderRouteLegs(dc);
12986 RenderShipToActive(dc, false);
12987 ScaleBarDraw(dc);
12988 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12989 if (g_pi_manager) {
12990 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12992 }
12993
12994 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12995 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12996
12997 if (g_pi_manager) {
12998 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
13000 }
13001
13002 if (m_bShowTide) {
13003 RebuildTideSelectList(GetVP().GetBBox());
13004 DrawAllTidesInBBox(dc, GetVP().GetBBox());
13005 }
13006
13007 if (m_bShowCurrent) {
13008 RebuildCurrentSelectList(GetVP().GetBBox());
13009 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
13010 }
13011
13012 if (!g_PrintingInProgress) {
13013 if (IsPrimaryCanvas()) {
13014 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
13015 }
13016
13017 if (IsPrimaryCanvas()) {
13018 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
13019 }
13020
13021 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
13022
13023 if (m_pTrackRolloverWin) {
13024 m_pTrackRolloverWin->Draw(dc);
13025 m_brepaint_piano = true;
13026 }
13027
13028 if (m_pRouteRolloverWin) {
13029 m_pRouteRolloverWin->Draw(dc);
13030 m_brepaint_piano = true;
13031 }
13032
13033 if (m_pAISRolloverWin) {
13034 m_pAISRolloverWin->Draw(dc);
13035 m_brepaint_piano = true;
13036 }
13037 if (m_brepaint_piano && g_bShowChartBar) {
13038 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
13039 }
13040
13041 if (m_Compass) m_Compass->Paint(dc);
13042
13043 if (!g_CanvasHideNotificationIcon) {
13044 if (IsPrimaryCanvas()) {
13045 auto &noteman = NotificationManager::GetInstance();
13046 if (m_notification_button) {
13047 if (noteman.GetNotificationCount()) {
13048 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
13049 if (m_notification_button->UpdateStatus()) Refresh();
13050 m_notification_button->Show(true);
13051 m_notification_button->Paint(dc);
13052 } else {
13053 m_notification_button->Show(false);
13054 }
13055 }
13056 }
13057 }
13058 }
13059 if (g_pi_manager) {
13060 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
13062 }
13063}
13064
13065emboss_data *ChartCanvas::EmbossDepthScale() {
13066 if (!m_bShowDepthUnits) return NULL;
13067
13068 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
13069
13070 if (GetQuiltMode()) {
13071 wxString s = m_pQuilt->GetQuiltDepthUnit();
13072 s.MakeUpper();
13073 if (s == "FEET")
13074 depth_unit_type = DEPTH_UNIT_FEET;
13075 else if (s.StartsWith("FATHOMS"))
13076 depth_unit_type = DEPTH_UNIT_FATHOMS;
13077 else if (s.StartsWith("METERS"))
13078 depth_unit_type = DEPTH_UNIT_METERS;
13079 else if (s.StartsWith("METRES"))
13080 depth_unit_type = DEPTH_UNIT_METERS;
13081 else if (s.StartsWith("METRIC"))
13082 depth_unit_type = DEPTH_UNIT_METERS;
13083 else if (s.StartsWith("METER"))
13084 depth_unit_type = DEPTH_UNIT_METERS;
13085
13086 } else {
13087 if (m_singleChart) {
13088 depth_unit_type = m_singleChart->GetDepthUnitType();
13089 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
13090 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
13091 }
13092 }
13093
13094 emboss_data *ped = NULL;
13095 switch (depth_unit_type) {
13096 case DEPTH_UNIT_FEET:
13097 ped = m_pEM_Feet;
13098 break;
13099 case DEPTH_UNIT_METERS:
13100 ped = m_pEM_Meters;
13101 break;
13102 case DEPTH_UNIT_FATHOMS:
13103 ped = m_pEM_Fathoms;
13104 break;
13105 default:
13106 return NULL;
13107 }
13108
13109 ped->x = (GetVP().pix_width - ped->width);
13110
13111 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
13112 wxRect r = m_Compass->GetRect();
13113 ped->y = r.y + r.height;
13114 } else {
13115 ped->y = 40;
13116 }
13117 return ped;
13118}
13119
13120void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
13121 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13122 wxFont font;
13123 if (style->embossFont == wxEmptyString) {
13124 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13125 font = *dFont;
13126 font.SetPointSize(60);
13127 font.SetWeight(wxFONTWEIGHT_BOLD);
13128 } else
13129 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13130 wxFONTWEIGHT_BOLD, false, style->embossFont);
13131
13132 int emboss_width = 500;
13133 int emboss_height = 200;
13134
13135 // Free any existing emboss maps
13136 delete m_pEM_Feet;
13137 delete m_pEM_Meters;
13138 delete m_pEM_Fathoms;
13139
13140 // Create the 3 DepthUnit emboss map structures
13141 m_pEM_Feet =
13142 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13143 m_pEM_Meters =
13144 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13145 m_pEM_Fathoms =
13146 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13147}
13148
13149#define OVERZOOM_TEXT _("OverZoom")
13150
13151void ChartCanvas::SetOverzoomFont() {
13152 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13153 int w, h;
13154
13155 wxFont font;
13156 if (style->embossFont == wxEmptyString) {
13157 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13158 font = *dFont;
13159 font.SetPointSize(40);
13160 font.SetWeight(wxFONTWEIGHT_BOLD);
13161 } else
13162 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13163 wxFONTWEIGHT_BOLD, false, style->embossFont);
13164
13165 wxClientDC dc(this);
13166 dc.SetFont(font);
13167 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13168
13169 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13170 font.SetPointSize(font.GetPointSize() - 1);
13171 dc.SetFont(font);
13172 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13173 }
13174 m_overzoomFont = font;
13175 m_overzoomTextWidth = w;
13176 m_overzoomTextHeight = h;
13177}
13178
13179void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13180 delete m_pEM_OverZoom;
13181
13182 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13183 m_pEM_OverZoom =
13184 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13185 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13186}
13187
13188emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13189 int height, const wxString &str,
13190 ColorScheme cs) {
13191 int *pmap;
13192
13193 // Create a temporary bitmap
13194 wxBitmap bmp(width, height, -1);
13195
13196 // Create a memory DC
13197 wxMemoryDC temp_dc;
13198 temp_dc.SelectObject(bmp);
13199
13200 // Paint on it
13201 temp_dc.SetBackground(*wxWHITE_BRUSH);
13202 temp_dc.SetTextBackground(*wxWHITE);
13203 temp_dc.SetTextForeground(*wxBLACK);
13204
13205 temp_dc.Clear();
13206
13207 temp_dc.SetFont(font);
13208
13209 int str_w, str_h;
13210 temp_dc.GetTextExtent(str, &str_w, &str_h);
13211 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13212 temp_dc.DrawText(str, 1, 1);
13213
13214 // Deselect the bitmap
13215 temp_dc.SelectObject(wxNullBitmap);
13216
13217 // Convert bitmap the wxImage for manipulation
13218 wxImage img = bmp.ConvertToImage();
13219
13220 int image_width = str_w * 105 / 100;
13221 int image_height = str_h * 105 / 100;
13222 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13223 wxMin(image_height, img.GetHeight()));
13224 wxImage imgs = img.GetSubImage(r);
13225
13226 double val_factor;
13227 switch (cs) {
13228 case GLOBAL_COLOR_SCHEME_DAY:
13229 default:
13230 val_factor = 1;
13231 break;
13232 case GLOBAL_COLOR_SCHEME_DUSK:
13233 val_factor = .5;
13234 break;
13235 case GLOBAL_COLOR_SCHEME_NIGHT:
13236 val_factor = .25;
13237 break;
13238 }
13239
13240 int val;
13241 int index;
13242 const int w = imgs.GetWidth();
13243 const int h = imgs.GetHeight();
13244 pmap = (int *)calloc(w * h * sizeof(int), 1);
13245 // Create emboss map by differentiating the emboss image
13246 // and storing integer results in pmap
13247 // n.b. since the image is B/W, it is sufficient to check
13248 // one channel (i.e. red) only
13249 for (int y = 1; y < h - 1; y++) {
13250 for (int x = 1; x < w - 1; x++) {
13251 val =
13252 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13253 val = (int)(val * val_factor);
13254 index = (y * w) + x;
13255 pmap[index] = val;
13256 }
13257 }
13258
13259 emboss_data *pret = new emboss_data;
13260 pret->pmap = pmap;
13261 pret->width = w;
13262 pret->height = h;
13263
13264 return pret;
13265}
13266
13267void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13268 Track *active_track = NULL;
13269 for (Track *pTrackDraw : g_TrackList) {
13270 if (g_pActiveTrack == pTrackDraw) {
13271 active_track = pTrackDraw;
13272 continue;
13273 }
13274
13275 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13276 }
13277
13278 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13279}
13280
13281void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13282 Track *active_track = NULL;
13283 for (Track *pTrackDraw : g_TrackList) {
13284 if (g_pActiveTrack == pTrackDraw) {
13285 active_track = pTrackDraw;
13286 break;
13287 }
13288 }
13289 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13290}
13291
13292void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13293 Route *active_route = NULL;
13294 for (Route *pRouteDraw : *pRouteList) {
13295 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13296 active_route = pRouteDraw;
13297 continue;
13298 }
13299
13300 // if(m_canvasIndex == 1)
13301 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13302 }
13303
13304 // Draw any active or selected route (or track) last, so that is is always on
13305 // top
13306 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13307}
13308
13309void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13310 Route *active_route = NULL;
13311
13312 for (Route *pRouteDraw : *pRouteList) {
13313 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13314 active_route = pRouteDraw;
13315 break;
13316 }
13317 }
13318 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13319}
13320
13321void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13322 if (!pWayPointMan) return;
13323
13324 auto node = pWayPointMan->GetWaypointList()->begin();
13325
13326 while (node != pWayPointMan->GetWaypointList()->end()) {
13327 RoutePoint *pWP = *node;
13328 if (pWP) {
13329 if (pWP->m_bIsInRoute) {
13330 ++node;
13331 continue;
13332 }
13333
13334 /* technically incorrect... waypoint has bounding box */
13335 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13336 RoutePointGui(*pWP).Draw(dc, this, NULL);
13337 else {
13338 // Are Range Rings enabled?
13339 if (pWP->GetShowWaypointRangeRings() &&
13340 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13341 double factor = 1.00;
13342 if (pWP->GetWaypointRangeRingsStepUnits() ==
13343 1) // convert kilometers to NMi
13344 factor = 1 / 1.852;
13345
13346 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13347 pWP->GetWaypointRangeRingsStep() / 60.;
13348 radius *= 2; // Fudge factor
13349
13350 LLBBox radar_box;
13351 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13352 pWP->m_lat + radius, pWP->m_lon + radius);
13353 if (!BltBBox.IntersectOut(radar_box)) {
13354 RoutePointGui(*pWP).Draw(dc, this, NULL);
13355 }
13356 }
13357 }
13358 }
13359
13360 ++node;
13361 }
13362}
13363
13364void ChartCanvas::DrawBlinkObjects() {
13365 // All RoutePoints
13366 wxRect update_rect;
13367
13368 if (!pWayPointMan) return;
13369
13370 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13371 if (pWP) {
13372 if (pWP->m_bBlink) {
13373 update_rect.Union(pWP->CurrentRect_in_DC);
13374 }
13375 }
13376 }
13377 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13378}
13379
13380void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13381 // draw anchor watch rings, if activated
13382
13384 wxPoint r1, r2;
13385 wxPoint lAnchorPoint1, lAnchorPoint2;
13386 double lpp1 = 0.0;
13387 double lpp2 = 0.0;
13388 if (pAnchorWatchPoint1) {
13389 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13391 &lAnchorPoint1);
13392 }
13393 if (pAnchorWatchPoint2) {
13394 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13396 &lAnchorPoint2);
13397 }
13398
13399 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13400 wxPen ppPenr(GetGlobalColor("URED"), 2);
13401
13402 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13403 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13404 dc.SetBrush(*ppBrush);
13405
13406 if (lpp1 > 0) {
13407 dc.SetPen(ppPeng);
13408 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13409 }
13410
13411 if (lpp2 > 0) {
13412 dc.SetPen(ppPeng);
13413 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13414 }
13415
13416 if (lpp1 < 0) {
13417 dc.SetPen(ppPenr);
13418 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13419 }
13420
13421 if (lpp2 < 0) {
13422 dc.SetPen(ppPenr);
13423 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13424 }
13425 }
13426}
13427
13428double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13429 double lpp = 0.;
13430 wxPoint r1;
13431 wxPoint lAnchorPoint;
13432 double d1 = 0.0;
13433 double dabs;
13434 double tlat1, tlon1;
13435
13436 if (pAnchorWatchPoint) {
13437 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13438 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13439 dabs = fabs(d1 / 1852.);
13440 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13441 &tlat1, &tlon1);
13442 GetCanvasPointPix(tlat1, tlon1, &r1);
13443 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13444 &lAnchorPoint);
13445 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13446 pow((double)(lAnchorPoint.y - r1.y), 2));
13447
13448 // This is an entry watch
13449 if (d1 < 0) lpp = -lpp;
13450 }
13451 return lpp;
13452}
13453
13454//------------------------------------------------------------------------------------------
13455// Tides Support
13456//------------------------------------------------------------------------------------------
13457void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13458 if (!ptcmgr) return;
13459 if (this != wxWindow::FindFocus()) return;
13460
13461 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13462
13463 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13464 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13465 double lon = pIDX->IDX_lon;
13466 double lat = pIDX->IDX_lat;
13467
13468 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13469 if ((type == 't') || (type == 'T')) {
13470 if (BBox.Contains(lat, lon)) {
13471 // Manage the point selection list
13472 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13473 }
13474 }
13475 }
13476}
13477
13478void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13479 if (!ptcmgr) return;
13480
13481 wxDateTime this_now = gTimeSource;
13482 bool cur_time = !gTimeSource.IsValid();
13483 if (cur_time) this_now = wxDateTime::Now();
13484 time_t t_this_now = this_now.GetTicks();
13485
13486 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13487 wxPENSTYLE_SOLID);
13488 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13489 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13490 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13491 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13492
13493 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13494 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13495 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13496 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13497 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13498 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13499
13500 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13501 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13502 int font_size = wxMax(10, dFont->GetPointSize());
13503 font_size /= g_Platform->GetDisplayDIPMult(this);
13504 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13505 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13506 false, dFont->GetFaceName());
13507
13508 dc.SetPen(*pblack_pen);
13509 dc.SetBrush(*pgreen_brush);
13510
13511 wxBitmap bm;
13512 switch (m_cs) {
13513 case GLOBAL_COLOR_SCHEME_DAY:
13514 bm = m_bmTideDay;
13515 break;
13516 case GLOBAL_COLOR_SCHEME_DUSK:
13517 bm = m_bmTideDusk;
13518 break;
13519 case GLOBAL_COLOR_SCHEME_NIGHT:
13520 bm = m_bmTideNight;
13521 break;
13522 default:
13523 bm = m_bmTideDay;
13524 break;
13525 }
13526
13527 int bmw = bm.GetWidth();
13528 int bmh = bm.GetHeight();
13529
13530 float scale_factor = 1.0;
13531
13532 // Set the onscreen size of the symbol
13533 // Compensate for various display resolutions
13534 float icon_pixelRefDim = 45;
13535
13536 // Tidal report graphic is scaled by the text size of the label in use
13537 wxScreenDC sdc;
13538 int height;
13539 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13540 height *= g_Platform->GetDisplayDIPMult(this);
13541 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13542
13543 scale_factor *= pix_factor;
13544
13545 float user_scale_factor = g_ChartScaleFactorExp;
13546 if (g_ChartScaleFactorExp > 1.0)
13547 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13548 1.2; // soften the scale factor a bit
13549
13550 scale_factor *= user_scale_factor;
13551 scale_factor *= GetContentScaleFactor();
13552
13553 {
13554 double marge = 0.05;
13555 std::vector<LLBBox> drawn_boxes;
13556 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13557 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13558
13559 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13560 if ((type == 't') || (type == 'T')) // only Tides
13561 {
13562 double lon = pIDX->IDX_lon;
13563 double lat = pIDX->IDX_lat;
13564
13565 if (BBox.ContainsMarge(lat, lon, marge)) {
13566 // Avoid drawing detailed graphic for duplicate tide stations
13567 if (GetVP().chart_scale < 500000) {
13568 bool bdrawn = false;
13569 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13570 if (drawn_boxes[i].Contains(lat, lon)) {
13571 bdrawn = true;
13572 break;
13573 }
13574 }
13575 if (bdrawn) continue; // the station loop
13576
13577 LLBBox this_box;
13578 this_box.Set(lat, lon, lat, lon);
13579 this_box.EnLarge(.005);
13580 drawn_boxes.push_back(this_box);
13581 }
13582
13583 wxPoint r;
13584 GetCanvasPointPix(lat, lon, &r);
13585 // draw standard icons
13586 if (GetVP().chart_scale > 500000) {
13587 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13588 }
13589 // draw "extended" icons
13590 else {
13591 dc.SetFont(*plabelFont);
13592 {
13593 {
13594 float val, nowlev;
13595 float ltleve = 0.;
13596 float htleve = 0.;
13597 time_t tctime;
13598 time_t lttime = 0;
13599 time_t httime = 0;
13600 bool wt;
13601 // define if flood or ebb in the last ten minutes and verify if
13602 // data are useable
13603 if (ptcmgr->GetTideFlowSens(
13604 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13605 pIDX->IDX_rec_num, nowlev, val, wt)) {
13606 // search forward the first HW or LW near "now" ( starting at
13607 // "now" - ten minutes )
13608 ptcmgr->GetHightOrLowTide(
13609 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13610 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13611 wt, pIDX->IDX_rec_num, val, tctime);
13612 if (wt) {
13613 httime = tctime;
13614 htleve = val;
13615 } else {
13616 lttime = tctime;
13617 ltleve = val;
13618 }
13619 wt = !wt;
13620
13621 // then search opposite tide near "now"
13622 if (tctime > t_this_now) // search backward
13623 ptcmgr->GetHightOrLowTide(
13624 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13625 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13626 pIDX->IDX_rec_num, val, tctime);
13627 else
13628 // or search forward
13629 ptcmgr->GetHightOrLowTide(
13630 t_this_now, FORWARD_TEN_MINUTES_STEP,
13631 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13632 val, tctime);
13633 if (wt) {
13634 httime = tctime;
13635 htleve = val;
13636 } else {
13637 lttime = tctime;
13638 ltleve = val;
13639 }
13640
13641 // draw the tide rectangle:
13642
13643 // tide icon rectangle has default pre-scaled width = 12 ,
13644 // height = 45
13645 int width = (int)(12 * scale_factor + 0.5);
13646 int height = (int)(45 * scale_factor + 0.5);
13647 int linew = wxMax(1, (int)(scale_factor));
13648 int xDraw = r.x - (width / 2);
13649 int yDraw = r.y - (height / 2);
13650
13651 // process tide state ( %height and flow sens )
13652 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13653 int hs = (httime > lttime) ? -4 : 4;
13654 hs *= (int)(scale_factor + 0.5);
13655 if (ts > 0.995 || ts < 0.005) hs = 0;
13656 int ht_y = (int)(height * ts);
13657
13658 // draw yellow tide rectangle outlined in black
13659 pblack_pen->SetWidth(linew);
13660 dc.SetPen(*pblack_pen);
13661 dc.SetBrush(*pyelo_brush);
13662 dc.DrawRectangle(xDraw, yDraw, width, height);
13663
13664 // draw blue rectangle as water height, smaller in width than
13665 // yellow rectangle
13666 dc.SetPen(*pblue_pen);
13667 dc.SetBrush(*pblue_brush);
13668 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13669 (width - (4 * linew)), height - ht_y);
13670
13671 // draw sens arrows (ensure they are not "under-drawn" by top
13672 // line of blue rectangle )
13673 int hl;
13674 wxPoint arrow[3];
13675 arrow[0].x = xDraw + 2 * linew;
13676 arrow[1].x = xDraw + width / 2;
13677 arrow[2].x = xDraw + width - 2 * linew;
13678 pyelo_pen->SetWidth(linew);
13679 pblue_pen->SetWidth(linew);
13680 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13681 {
13682 hl = (int)(height * 0.25) + yDraw;
13683 arrow[0].y = hl;
13684 arrow[1].y = hl + hs;
13685 arrow[2].y = hl;
13686 if (ts < 0.15)
13687 dc.SetPen(*pyelo_pen);
13688 else
13689 dc.SetPen(*pblue_pen);
13690 dc.DrawLines(3, arrow);
13691 }
13692 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13693 {
13694 hl = (int)(height * 0.5) + yDraw;
13695 arrow[0].y = hl;
13696 arrow[1].y = hl + hs;
13697 arrow[2].y = hl;
13698 if (ts < 0.40)
13699 dc.SetPen(*pyelo_pen);
13700 else
13701 dc.SetPen(*pblue_pen);
13702 dc.DrawLines(3, arrow);
13703 }
13704 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13705 {
13706 hl = (int)(height * 0.75) + yDraw;
13707 arrow[0].y = hl;
13708 arrow[1].y = hl + hs;
13709 arrow[2].y = hl;
13710 if (ts < 0.65)
13711 dc.SetPen(*pyelo_pen);
13712 else
13713 dc.SetPen(*pblue_pen);
13714 dc.DrawLines(3, arrow);
13715 }
13716 // Convert nowlev to preferred height units (it comes from
13717 // GetHightOrLowTide in station units)
13718 double nowlev_converted = nowlev;
13719 Station_Data *pmsd = pIDX->pref_sta_data;
13720 if (pmsd) {
13721 // Convert from station units to meters first
13722 int unit_c = TCDataFactory::findunit(pmsd->unit);
13723 if (unit_c >= 0) {
13724 nowlev_converted =
13725 nowlev_converted *
13726 TCDataFactory::known_units[unit_c].conv_factor;
13727 }
13728 // Now convert from meters to preferred height units
13729 nowlev_converted = toUsrHeight(nowlev_converted);
13730 }
13731 // draw tide level text
13732 wxString s;
13733 s.Printf("%3.1f", nowlev_converted);
13734 // Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13735 if (pmsd) s.Append(getUsrHeightUnit());
13736 int wx1;
13737 dc.GetTextExtent(s, &wx1, NULL);
13738 wx1 *= g_Platform->GetDisplayDIPMult(this);
13739 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13740 }
13741 }
13742 }
13743 }
13744 }
13745 }
13746 }
13747 }
13748}
13749
13750//------------------------------------------------------------------------------------------
13751// Currents Support
13752//------------------------------------------------------------------------------------------
13753
13754void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13755 if (!ptcmgr) return;
13756
13757 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13758
13759 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13760 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13761 double lon = pIDX->IDX_lon;
13762 double lat = pIDX->IDX_lat;
13763
13764 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13765 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13766 if ((BBox.Contains(lat, lon))) {
13767 // Manage the point selection list
13768 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13769 }
13770 }
13771 }
13772}
13773
13774void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13775 if (!ptcmgr) return;
13776
13777 float tcvalue, dir;
13778 bool bnew_val;
13779 char sbuf[20];
13780 wxFont *pTCFont;
13781 double lon_last = 0.;
13782 double lat_last = 0.;
13783 // arrow size for Raz Blanchard : 12 knots north
13784 double marge = 0.2;
13785 bool cur_time = !gTimeSource.IsValid();
13786
13787 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13788 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13789
13790 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13791 wxPENSTYLE_SOLID);
13792 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13793 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13794 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13795 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13796 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13797 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13798 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13799 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13800
13801 double skew_angle = GetVPRotation();
13802
13803 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13804 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13805 int font_size = wxMax(10, dFont->GetPointSize());
13806 font_size /= g_Platform->GetDisplayDIPMult(this);
13807 pTCFont = FontMgr::Get().FindOrCreateFont(
13808 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13809 false, dFont->GetFaceName());
13810
13811 float scale_factor = 1.0;
13812
13813 // Set the onscreen size of the symbol
13814 // Current report graphic is scaled by the text size of the label in use
13815 wxScreenDC sdc;
13816 int height;
13817 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13818 height *= g_Platform->GetDisplayDIPMult(this);
13819 float nominal_icon_size_pixels = 15;
13820 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13821
13822 scale_factor *= pix_factor;
13823
13824 float user_scale_factor = g_ChartScaleFactorExp;
13825 if (g_ChartScaleFactorExp > 1.0)
13826 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13827 1.2; // soften the scale factor a bit
13828
13829 scale_factor *= user_scale_factor;
13830
13831 scale_factor *= GetContentScaleFactor();
13832
13833 {
13834 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13835 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13836 double lon = pIDX->IDX_lon;
13837 double lat = pIDX->IDX_lat;
13838
13839 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13840 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13841 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13842 wxPoint r;
13843 GetCanvasPointPix(lat, lon, &r);
13844
13845 wxPoint d[4]; // points of a diamond at the current station location
13846 int dd = (int)(5.0 * scale_factor + 0.5);
13847 d[0].x = r.x;
13848 d[0].y = r.y + dd;
13849 d[1].x = r.x + dd;
13850 d[1].y = r.y;
13851 d[2].x = r.x;
13852 d[2].y = r.y - dd;
13853 d[3].x = r.x - dd;
13854 d[3].y = r.y;
13855
13856 if (1) {
13857 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13858 dc.SetPen(*pblack_pen);
13859 dc.SetBrush(*porange_brush);
13860 dc.DrawPolygon(4, d);
13861
13862 if (type == 'C') {
13863 dc.SetBrush(*pblack_brush);
13864 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13865 }
13866
13867 if (GetVP().chart_scale < 1000000) {
13868 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13869 continue;
13870 } else
13871 continue;
13872
13873 if (1 /*type == 'c'*/) {
13874 {
13875 // Get the display pixel location of the current station
13876 int pixxc, pixyc;
13877 pixxc = r.x;
13878 pixyc = r.y;
13879
13880 // Adjust drawing size using logarithmic scale. tcvalue is
13881 // current in knots
13882 double a1 = fabs(tcvalue) * 10.;
13883 // Current values <= 0.1 knot will have no arrow
13884 a1 = wxMax(1.0, a1);
13885 double a2 = log10(a1);
13886
13887 float cscale = scale_factor * a2 * 0.3;
13888
13889 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13890 dc.SetPen(*porange_pen);
13891 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13892 cscale);
13893 // Draw text, if enabled
13894
13895 if (bDrawCurrentValues) {
13896 dc.SetFont(*pTCFont);
13897 tcvalue = toUsrSpeed(tcvalue);
13898 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13899 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13900 }
13901 }
13902 } // scale
13903 }
13904 /* This is useful for debugging the TC database
13905 else
13906 {
13907 dc.SetPen ( *porange_pen );
13908 dc.SetBrush ( *pgray_brush );
13909 dc.DrawPolygon ( 4, d );
13910 }
13911 */
13912 }
13913 lon_last = lon;
13914 lat_last = lat;
13915 }
13916 }
13917 }
13918}
13919
13920void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13921 ShowSingleTideDialog(x, y, pvIDX);
13922}
13923
13924void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13925 if (!pvIDX) return; // Validate input
13926
13927 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13928
13929 // Check if a tide dialog is already open and visible
13930 if (pCwin && pCwin->IsShown()) {
13931 // Same tide station: bring existing dialog to front (preserves user
13932 // context)
13933 if (pCwin->GetCurrentIDX() == pNewIDX) {
13934 pCwin->Raise();
13935 pCwin->SetFocus();
13936
13937 // Provide subtle visual feedback that dialog is already open
13938 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13939 return;
13940 }
13941
13942 // Different tide station: close current dialog before opening new one
13943 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13944 }
13945
13946 if (pCwin) {
13947 // This shouldn't happen but ensures clean state
13948 pCwin->Destroy();
13949 pCwin = NULL;
13950 }
13951
13952 // Create and display new tide dialog
13953 pCwin = new TCWin(this, x, y, pvIDX);
13954
13955 // Ensure the dialog is properly shown and focused
13956 if (pCwin) {
13957 pCwin->Show();
13958 pCwin->Raise();
13959 pCwin->SetFocus();
13960 }
13961}
13962
13963bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13964
13966 if (pCwin) {
13967 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13968 }
13969}
13970
13971#define NUM_CURRENT_ARROW_POINTS 9
13972static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13973 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13974 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13975 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13976
13977void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13978 double scale) {
13979 if (scale > 1e-2) {
13980 float sin_rot = sin(rot_angle * PI / 180.);
13981 float cos_rot = cos(rot_angle * PI / 180.);
13982
13983 // Move to the first point
13984
13985 float xt = CurrentArrowArray[0].x;
13986 float yt = CurrentArrowArray[0].y;
13987
13988 float xp = (xt * cos_rot) - (yt * sin_rot);
13989 float yp = (xt * sin_rot) + (yt * cos_rot);
13990 int x1 = (int)(xp * scale);
13991 int y1 = (int)(yp * scale);
13992
13993 // Walk thru the point list
13994 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13995 xt = CurrentArrowArray[ip].x;
13996 yt = CurrentArrowArray[ip].y;
13997
13998 float xp = (xt * cos_rot) - (yt * sin_rot);
13999 float yp = (xt * sin_rot) + (yt * cos_rot);
14000 int x2 = (int)(xp * scale);
14001 int y2 = (int)(yp * scale);
14002
14003 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
14004
14005 x1 = x2;
14006 y1 = y2;
14007 }
14008 }
14009}
14010
14011wxString ChartCanvas::FindValidUploadPort() {
14012 wxString port;
14013 // Try to use the saved persistent upload port first
14014 if (!g_uploadConnection.IsEmpty() &&
14015 g_uploadConnection.StartsWith("Serial")) {
14016 port = g_uploadConnection;
14017 }
14018
14019 else {
14020 // If there is no persistent upload port recorded (yet)
14021 // then use the first available serial connection which has output defined.
14022 for (auto *cp : TheConnectionParams()) {
14023 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
14024 port << "Serial:" << cp->Port;
14025 }
14026 }
14027 return port;
14028}
14029
14030void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
14031 if (!win) return;
14032
14033 if (NULL == g_pais_query_dialog_active) {
14034 int pos_x = g_ais_query_dialog_x;
14035 int pos_y = g_ais_query_dialog_y;
14036
14037 if (g_pais_query_dialog_active) {
14038 g_pais_query_dialog_active->Destroy();
14039 g_pais_query_dialog_active = new AISTargetQueryDialog();
14040 } else {
14041 g_pais_query_dialog_active = new AISTargetQueryDialog();
14042 }
14043
14044 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
14045 wxPoint(pos_x, pos_y));
14046
14047 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
14048 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
14049 g_pais_query_dialog_active->SetMMSI(mmsi);
14050 g_pais_query_dialog_active->UpdateText();
14051 wxSize sz = g_pais_query_dialog_active->GetSize();
14052
14053 bool b_reset_pos = false;
14054#ifdef __WXMSW__
14055 // Support MultiMonitor setups which an allow negative window positions.
14056 // If the requested window title bar does not intersect any installed
14057 // monitor, then default to simple primary monitor positioning.
14058 RECT frame_title_rect;
14059 frame_title_rect.left = pos_x;
14060 frame_title_rect.top = pos_y;
14061 frame_title_rect.right = pos_x + sz.x;
14062 frame_title_rect.bottom = pos_y + 30;
14063
14064 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
14065 b_reset_pos = true;
14066#else
14067
14068 // Make sure drag bar (title bar) of window intersects wxClient Area of
14069 // screen, with a little slop...
14070 wxRect window_title_rect; // conservative estimate
14071 window_title_rect.x = pos_x;
14072 window_title_rect.y = pos_y;
14073 window_title_rect.width = sz.x;
14074 window_title_rect.height = 30;
14075
14076 wxRect ClientRect = wxGetClientDisplayRect();
14077 ClientRect.Deflate(
14078 60, 60); // Prevent the new window from being too close to the edge
14079 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
14080
14081#endif
14082
14083 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
14084
14085 } else {
14086 g_pais_query_dialog_active->SetMMSI(mmsi);
14087 g_pais_query_dialog_active->UpdateText();
14088 }
14089
14090 g_pais_query_dialog_active->Show();
14091}
14092
14093void ChartCanvas::ToggleCanvasQuiltMode() {
14094 bool cur_mode = GetQuiltMode();
14095
14096 if (!GetQuiltMode())
14097 SetQuiltMode(true);
14098 else if (GetQuiltMode()) {
14099 SetQuiltMode(false);
14100 g_sticky_chart = GetQuiltReferenceChartIndex();
14101 }
14102
14103 if (cur_mode != GetQuiltMode()) {
14104 SetupCanvasQuiltMode();
14105 DoCanvasUpdate();
14106 InvalidateGL();
14107 Refresh();
14108 }
14109 // TODO What to do about this?
14110 // g_bQuiltEnable = GetQuiltMode();
14111
14112 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
14113 if (ps52plib) ps52plib->GenerateStateHash();
14114
14115 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14116 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14117}
14118
14119void ChartCanvas::DoCanvasStackDelta(int direction) {
14120 if (!GetQuiltMode()) {
14121 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
14122 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
14123 if ((current_stack_index + direction) < 0) return;
14124
14125 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
14126 int new_dbIndex =
14127 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
14128
14129 if (IsChartQuiltableRef(new_dbIndex)) {
14130 ToggleCanvasQuiltMode();
14131 SelectQuiltRefdbChart(new_dbIndex);
14132 m_bpersistent_quilt = false;
14133 }
14134 } else {
14135 SelectChartFromStack(current_stack_index + direction);
14136 }
14137 } else {
14138 std::vector<int> piano_chart_index_array =
14139 GetQuiltExtendedStackdbIndexArray();
14140 int refdb = GetQuiltRefChartdbIndex();
14141
14142 // Find the ref chart in the stack
14143 int current_index = -1;
14144 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14145 if (refdb == piano_chart_index_array[i]) {
14146 current_index = i;
14147 break;
14148 }
14149 }
14150 if (current_index == -1) return;
14151
14152 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14153 int target_family = ctet.GetChartFamily();
14154
14155 int new_index = -1;
14156 int check_index = current_index + direction;
14157 bool found = false;
14158 int check_dbIndex = -1;
14159 int new_dbIndex = -1;
14160
14161 // When quilted. switch within the same chart family
14162 while (!found &&
14163 (unsigned int)check_index < piano_chart_index_array.size() &&
14164 (check_index >= 0)) {
14165 check_dbIndex = piano_chart_index_array[check_index];
14166 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14167 if (target_family == cte.GetChartFamily()) {
14168 found = true;
14169 new_index = check_index;
14170 new_dbIndex = check_dbIndex;
14171 break;
14172 }
14173
14174 check_index += direction;
14175 }
14176
14177 if (!found) return;
14178
14179 if (!IsChartQuiltableRef(new_dbIndex)) {
14180 ToggleCanvasQuiltMode();
14181 SelectdbChart(new_dbIndex);
14182 m_bpersistent_quilt = true;
14183 } else {
14184 SelectQuiltRefChart(new_index);
14185 }
14186 }
14187
14188 // update the state of the menu items (checkmarks etc)
14189 top_frame::Get()->UpdateGlobalMenuItems();
14190 SetQuiltChartHiLiteIndex(-1);
14191
14192 ReloadVP();
14193}
14194
14195//--------------------------------------------------------------------------------------------------------
14196//
14197// Toolbar support
14198//
14199//--------------------------------------------------------------------------------------------------------
14200
14201void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14202 // Handle the per-canvas toolbar clicks here
14203
14204 switch (event.GetId()) {
14205 case ID_ZOOMIN: {
14206 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14207 break;
14208 }
14209
14210 case ID_ZOOMOUT: {
14211 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14212 break;
14213 }
14214
14215 case ID_STKUP:
14216 DoCanvasStackDelta(1);
14217 DoCanvasUpdate();
14218 break;
14219
14220 case ID_STKDN:
14221 DoCanvasStackDelta(-1);
14222 DoCanvasUpdate();
14223 break;
14224
14225 case ID_FOLLOW: {
14226 TogglebFollow();
14227 break;
14228 }
14229
14230 case ID_CURRENT: {
14231 ShowCurrents(!GetbShowCurrent());
14232 ReloadVP();
14233 Refresh(false);
14234 break;
14235 }
14236
14237 case ID_TIDE: {
14238 ShowTides(!GetbShowTide());
14239 ReloadVP();
14240 Refresh(false);
14241 break;
14242 }
14243
14244 case ID_ROUTE: {
14245 if (0 == m_routeState) {
14246 StartRoute();
14247 } else {
14248 FinishRoute();
14249 }
14250
14251#ifdef __ANDROID__
14252 androidSetRouteAnnunciator(m_routeState == 1);
14253#endif
14254 break;
14255 }
14256
14257 case ID_AIS: {
14258 SetAISCanvasDisplayStyle(-1);
14259 break;
14260 }
14261
14262 default:
14263 break;
14264 }
14265
14266 // And then let gFrame handle the rest....
14267 event.Skip();
14268}
14269
14270void ChartCanvas::SetShowAIS(bool show) {
14271 m_bShowAIS = show;
14272 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14273 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14274}
14275
14276void ChartCanvas::SetAttenAIS(bool show) {
14277 m_bShowAISScaled = show;
14278 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14279 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14280}
14281
14282void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14283 // make some arrays to hold the dfferences between cycle steps
14284 // show all, scaled, hide all
14285 bool bShowAIS_Array[3] = {true, true, false};
14286 bool bShowScaled_Array[3] = {false, true, true};
14287 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14288 _("Attenuate less critical AIS targets"),
14289 _("Hide AIS Targets")};
14290 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14291 int ArraySize = 3;
14292 int AIS_Toolbar_Switch = 0;
14293 if (StyleIndx == -1) { // -1 means coming from toolbar button
14294 // find current state of switch
14295 for (int i = 1; i < ArraySize; i++) {
14296 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14297 (bShowScaled_Array[i] == m_bShowAISScaled))
14298 AIS_Toolbar_Switch = i;
14299 }
14300 AIS_Toolbar_Switch++; // we did click so continu with next item
14301 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14302 AIS_Toolbar_Switch++;
14303
14304 } else { // coming from menu bar.
14305 AIS_Toolbar_Switch = StyleIndx;
14306 }
14307 // make sure we are not above array
14308 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14309
14310 int AIS_Toolbar_Switch_Next =
14311 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14312 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14313 AIS_Toolbar_Switch_Next++;
14314 if (AIS_Toolbar_Switch_Next >= ArraySize)
14315 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14316
14317 // Set found values to global and member variables
14318 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14319 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14320}
14321
14322void ChartCanvas::TouchAISToolActive() {}
14323
14324void ChartCanvas::UpdateAISTBTool() {}
14325
14326//---------------------------------------------------------------------------------
14327//
14328// Compass/GPS status icon support
14329//
14330//---------------------------------------------------------------------------------
14331
14332void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14333 // Look for change in overlap or positions
14334 bool b_update = false;
14335 int cc1_edge_comp = 2;
14336 wxRect rect = m_Compass->GetRect();
14337 wxSize parent_size = GetSize();
14338
14339 parent_size *= m_displayScale;
14340
14341 // check to see if it would overlap if it was in its home position (upper
14342 // right)
14343 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14344 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14345 wxRect compass_rect(compass_pt, rect.GetSize());
14346
14347 m_Compass->Move(compass_pt);
14348
14349 if (m_Compass && m_Compass->IsShown())
14350 m_Compass->UpdateStatus(b_force_new | b_update);
14351
14352 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14353 scaler = wxMax(scaler, 1.0);
14354 wxPoint note_point = wxPoint(
14355 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14356 if (m_notification_button) {
14357 m_notification_button->Move(note_point);
14358 m_notification_button->UpdateStatus();
14359 }
14360
14361 if (b_force_new | b_update) Refresh();
14362}
14363
14364void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14365 ChartTypeEnum New_Type,
14366 ChartFamilyEnum New_Family) {
14367 if (!GetpCurrentStack()) return;
14368 if (!ChartData) return;
14369
14370 if (index < GetpCurrentStack()->nEntry) {
14371 // Open the new chart
14372 ChartBase *pTentative_Chart;
14373 pTentative_Chart = ChartData->OpenStackChartConditional(
14374 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14375
14376 if (pTentative_Chart) {
14377 if (m_singleChart) m_singleChart->Deactivate();
14378
14379 m_singleChart = pTentative_Chart;
14380 m_singleChart->Activate();
14381
14382 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14383 GetpCurrentStack(), m_singleChart->GetFullPath());
14384 }
14385
14386 // Setup the view
14387 double zLat, zLon;
14388 if (m_bFollow) {
14389 zLat = gLat;
14390 zLon = gLon;
14391 } else {
14392 zLat = m_vLat;
14393 zLon = m_vLon;
14394 }
14395
14396 double best_scale_ppm = GetBestVPScale(m_singleChart);
14397 double rotation = GetVPRotation();
14398 double oldskew = GetVPSkew();
14399 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14400
14401 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14402 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14403 if (fabs(newskew) > 0.0001) rotation = newskew;
14404 }
14405
14406 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14407
14408 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14409 }
14410
14411 // refresh Piano
14412 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14413 if (idx < 0) return;
14414
14415 std::vector<int> piano_active_chart_index_array;
14416 piano_active_chart_index_array.push_back(
14417 GetpCurrentStack()->GetCurrentEntrydbIndex());
14418 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14419}
14420
14421void ChartCanvas::SelectdbChart(int dbindex) {
14422 if (!GetpCurrentStack()) return;
14423 if (!ChartData) return;
14424
14425 if (dbindex >= 0) {
14426 // Open the new chart
14427 ChartBase *pTentative_Chart;
14428 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14429
14430 if (pTentative_Chart) {
14431 if (m_singleChart) m_singleChart->Deactivate();
14432
14433 m_singleChart = pTentative_Chart;
14434 m_singleChart->Activate();
14435
14436 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14437 GetpCurrentStack(), m_singleChart->GetFullPath());
14438 }
14439
14440 // Setup the view
14441 double zLat, zLon;
14442 if (m_bFollow) {
14443 zLat = gLat;
14444 zLon = gLon;
14445 } else {
14446 zLat = m_vLat;
14447 zLon = m_vLon;
14448 }
14449
14450 double best_scale_ppm = GetBestVPScale(m_singleChart);
14451
14452 if (m_singleChart)
14453 SetViewPoint(zLat, zLon, best_scale_ppm,
14454 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14455
14456 // SetChartUpdatePeriod( );
14457
14458 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14459 }
14460
14461 // TODO refresh_Piano();
14462}
14463
14464void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14465 double target_scale = GetVP().view_scale_ppm;
14466
14467 if (!GetQuiltMode()) {
14468 if (GetpCurrentStack()) {
14469 int stack_index = -1;
14470 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14471 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14472 if (check_dbIndex < 0) continue;
14473 const ChartTableEntry &cte =
14474 ChartData->GetChartTableEntry(check_dbIndex);
14475 if (type == cte.GetChartType()) {
14476 stack_index = i;
14477 break;
14478 } else if (family == cte.GetChartFamily()) {
14479 stack_index = i;
14480 break;
14481 }
14482 }
14483
14484 if (stack_index >= 0) {
14485 SelectChartFromStack(stack_index);
14486 }
14487 }
14488 } else {
14489 int sel_dbIndex = -1;
14490 std::vector<int> piano_chart_index_array =
14491 GetQuiltExtendedStackdbIndexArray();
14492 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14493 int check_dbIndex = piano_chart_index_array[i];
14494 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14495 if (type == cte.GetChartType()) {
14496 if (IsChartQuiltableRef(check_dbIndex)) {
14497 sel_dbIndex = check_dbIndex;
14498 break;
14499 }
14500 } else if (family == cte.GetChartFamily()) {
14501 if (IsChartQuiltableRef(check_dbIndex)) {
14502 sel_dbIndex = check_dbIndex;
14503 break;
14504 }
14505 }
14506 }
14507
14508 if (sel_dbIndex >= 0) {
14509 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14510 // Re-qualify the quilt reference chart selection
14511 AdjustQuiltRefChart();
14512 }
14513
14514 // Now reset the scale to the target...
14515 SetVPScale(target_scale);
14516 }
14517
14518 SetQuiltChartHiLiteIndex(-1);
14519
14520 ReloadVP();
14521}
14522
14523bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14524 return std::find(m_tile_yesshow_index_array.begin(),
14525 m_tile_yesshow_index_array.end(),
14526 index) != m_tile_yesshow_index_array.end();
14527}
14528
14529bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14530 return std::find(m_tile_noshow_index_array.begin(),
14531 m_tile_noshow_index_array.end(),
14532 index) != m_tile_noshow_index_array.end();
14533}
14534
14535void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14536 if (std::find(m_tile_noshow_index_array.begin(),
14537 m_tile_noshow_index_array.end(),
14538 index) == m_tile_noshow_index_array.end()) {
14539 m_tile_noshow_index_array.push_back(index);
14540 }
14541}
14542
14543//-------------------------------------------------------------------------------------------------------
14544//
14545// Piano support
14546//
14547//-------------------------------------------------------------------------------------------------------
14548
14549void ChartCanvas::HandlePianoClick(
14550 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14551 if (g_options && g_options->IsShown())
14552 return; // Piano might be invalid due to chartset updates.
14553 if (!m_pCurrentStack) return;
14554 if (!ChartData) return;
14555
14556 // stop movement or on slow computer we may get something like :
14557 // zoom out with the wheel (timer is set)
14558 // quickly click and display a chart, which may zoom in
14559 // but the delayed timer fires first and it zooms out again!
14560 StopMovement();
14561
14562 // When switching by piano key click, we may appoint the new target chart to
14563 // be any chart in the composite array.
14564 // As an improvement to UX, find the chart that is "closest" to the current
14565 // vp,
14566 // and select that chart. This will cause a jump to the centroid of that
14567 // chart
14568
14569 double distance = 25000; // RTW
14570 int closest_index = -1;
14571 for (int chart_index : selected_dbIndex_array) {
14572 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14573 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14574 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14575
14576 // measure distance as Manhattan style
14577 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14578 if (test_distance < distance) {
14579 distance = test_distance;
14580 closest_index = chart_index;
14581 }
14582 }
14583
14584 int selected_dbIndex = selected_dbIndex_array[0];
14585 if (closest_index >= 0) selected_dbIndex = closest_index;
14586
14587 if (!GetQuiltMode()) {
14588 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14589 if (IsChartQuiltableRef(selected_dbIndex)) {
14590 ToggleCanvasQuiltMode();
14591 SelectQuiltRefdbChart(selected_dbIndex);
14592 m_bpersistent_quilt = false;
14593 } else {
14594 SelectChartFromStack(selected_index);
14595 }
14596 } else {
14597 SelectChartFromStack(selected_index);
14598 g_sticky_chart = selected_dbIndex;
14599 }
14600
14601 if (m_singleChart)
14602 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14603 } else {
14604 // Handle MBTiles overlays first
14605 // Left click simply toggles the noshow array index entry
14606 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14607 bool bfound = false;
14608 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14609 if (m_tile_noshow_index_array[i] ==
14610 selected_dbIndex) { // chart is in the noshow list
14611 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14612 i); // erase it
14613 bfound = true;
14614 break;
14615 }
14616 }
14617 if (!bfound) {
14618 m_tile_noshow_index_array.push_back(selected_dbIndex);
14619 }
14620
14621 // If not already present, add this tileset to the "yes_show" array.
14622 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14623 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14624 }
14625
14626 else {
14627 if (IsChartQuiltableRef(selected_dbIndex)) {
14628 // if( ChartData ) ChartData->PurgeCache();
14629
14630 // If the chart is a vector chart, and of very large scale,
14631 // then we had better set the new scale directly to avoid excessive
14632 // underzoom on, eg, Inland ENCs
14633 bool set_scale = false;
14634 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14635 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14636 set_scale = true;
14637 }
14638 }
14639
14640 if (!set_scale) {
14641 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14642 } else {
14643 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14644
14645 // Adjust scale so that the selected chart is underzoomed/overzoomed
14646 // by a controlled amount
14647 ChartBase *pc =
14648 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14649 if (pc) {
14650 double proposed_scale_onscreen =
14652
14653 if (g_bPreserveScaleOnX) {
14654 proposed_scale_onscreen =
14655 wxMin(proposed_scale_onscreen,
14656 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14657 GetCanvasWidth()));
14658 } else {
14659 proposed_scale_onscreen =
14660 wxMin(proposed_scale_onscreen,
14661 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14662 GetCanvasWidth()));
14663
14664 proposed_scale_onscreen =
14665 wxMax(proposed_scale_onscreen,
14666 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14668 }
14669
14670 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14671 }
14672 }
14673 } else {
14674 ToggleCanvasQuiltMode();
14675 SelectdbChart(selected_dbIndex);
14676 m_bpersistent_quilt = true;
14677 }
14678 }
14679 }
14680
14681 SetQuiltChartHiLiteIndex(-1);
14682 // update the state of the menu items (checkmarks etc)
14683 top_frame::Get()->UpdateGlobalMenuItems();
14684 HideChartInfoWindow();
14685 DoCanvasUpdate();
14686 ReloadVP(); // Pick up the new selections
14687}
14688
14689void ChartCanvas::HandlePianoRClick(
14690 int x, int y, int selected_index,
14691 const std::vector<int> &selected_dbIndex_array) {
14692 if (g_options && g_options->IsShown())
14693 return; // Piano might be invalid due to chartset updates.
14694 if (!GetpCurrentStack()) return;
14695
14696 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14697 UpdateCanvasControlBar();
14698
14699 SetQuiltChartHiLiteIndex(-1);
14700}
14701
14702void ChartCanvas::HandlePianoRollover(
14703 int selected_index, const std::vector<int> &selected_dbIndex_array,
14704 int n_charts, int scale) {
14705 if (g_options && g_options->IsShown())
14706 return; // Piano might be invalid due to chartset updates.
14707 if (!GetpCurrentStack()) return;
14708 if (!ChartData) return;
14709
14710 if (ChartData->IsBusy()) return;
14711
14712 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14713
14714 if (!GetQuiltMode()) {
14715 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14716 } else {
14717 // Select the correct vector
14718 std::vector<int> piano_chart_index_array;
14719 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14720 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14721 if ((GetpCurrentStack()->nEntry > 1) ||
14722 (piano_chart_index_array.size() >= 1)) {
14723 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14724
14725 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14726 ReloadVP(false); // no VP adjustment allowed
14727 } else if (GetpCurrentStack()->nEntry == 1) {
14728 const ChartTableEntry &cte =
14729 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14730 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14731 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14732 ReloadVP(false);
14733 } else if ((-1 == selected_index) &&
14734 (0 == selected_dbIndex_array.size())) {
14735 ShowChartInfoWindow(key_location.x, -1);
14736 }
14737 }
14738 } else {
14739 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14740
14741 if ((GetpCurrentStack()->nEntry > 1) ||
14742 (piano_chart_index_array.size() >= 1)) {
14743 if (n_charts > 1)
14744 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14745 selected_dbIndex_array);
14746 else if (n_charts == 1)
14747 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14748
14749 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14750 ReloadVP(false); // no VP adjustment allowed
14751 }
14752 }
14753 }
14754}
14755
14756void ChartCanvas::ClearPianoRollover() {
14757 ClearQuiltChartHiLiteIndexArray();
14758 ShowChartInfoWindow(0, -1);
14759 std::vector<int> vec;
14760 ShowCompositeInfoWindow(0, 0, 0, vec);
14761 ReloadVP(false);
14762}
14763
14764void ChartCanvas::UpdateCanvasControlBar() {
14765 if (m_pianoFrozen) return;
14766
14767 if (!GetpCurrentStack()) return;
14768 if (!ChartData) return;
14769 if (!g_bShowChartBar) return;
14770
14771 int sel_type = -1;
14772 int sel_family = -1;
14773
14774 std::vector<int> piano_chart_index_array;
14775 std::vector<int> empty_piano_chart_index_array;
14776
14777 wxString old_hash = m_Piano->GetStoredHash();
14778
14779 if (GetQuiltMode()) {
14780 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14781 GetQuiltFullScreendbIndexArray());
14782
14783 std::vector<int> piano_active_chart_index_array =
14784 GetQuiltCandidatedbIndexArray();
14785 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14786
14787 std::vector<int> piano_eclipsed_chart_index_array =
14788 GetQuiltEclipsedStackdbIndexArray();
14789 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14790
14791 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14792 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14793
14794 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14795 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14796 } else {
14797 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14798 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14799 // TODO refresh_Piano();
14800
14801 if (m_singleChart) {
14802 sel_type = m_singleChart->GetChartType();
14803 sel_family = m_singleChart->GetChartFamily();
14804 }
14805 }
14806
14807 // Set up the TMerc and Skew arrays
14808 std::vector<int> piano_skew_chart_index_array;
14809 std::vector<int> piano_tmerc_chart_index_array;
14810 std::vector<int> piano_poly_chart_index_array;
14811
14812 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14813 const ChartTableEntry &ctei =
14814 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14815 double skew_norm = ctei.GetChartSkew();
14816 if (skew_norm > 180.) skew_norm -= 360.;
14817
14818 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14819 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14820
14821 // Polyconic skewed charts should show as skewed
14822 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14823 if (fabs(skew_norm) > 1.)
14824 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14825 else
14826 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14827 } else if (fabs(skew_norm) > 1.)
14828 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14829 }
14830 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14831 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14832 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14833
14834 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14835 if (new_hash != old_hash) {
14836 m_Piano->FormatKeys();
14837 HideChartInfoWindow();
14838 m_Piano->ResetRollover();
14839 SetQuiltChartHiLiteIndex(-1);
14840 m_brepaint_piano = true;
14841 }
14842
14843 // Create a bitmask int that describes what Family/Type of charts are shown in
14844 // the bar, and notify the platform.
14845 int mask = 0;
14846 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14847 const ChartTableEntry &ctei =
14848 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14849 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14850 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14851 if (e == CHART_FAMILY_RASTER) mask |= 1;
14852 if (e == CHART_FAMILY_VECTOR) {
14853 if (t == CHART_TYPE_CM93COMP)
14854 mask |= 4;
14855 else
14856 mask |= 2;
14857 }
14858 }
14859
14860 wxString s_indicated;
14861 if (sel_type == CHART_TYPE_CM93COMP)
14862 s_indicated = "cm93";
14863 else {
14864 if (sel_family == CHART_FAMILY_RASTER)
14865 s_indicated = "raster";
14866 else if (sel_family == CHART_FAMILY_VECTOR)
14867 s_indicated = "vector";
14868 }
14869
14870 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14871}
14872
14873void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14874
14875void ChartCanvas::PianoPopupMenu(
14876 int x, int y, int selected_index,
14877 const std::vector<int> &selected_dbIndex_array) {
14878 if (!GetpCurrentStack()) return;
14879
14880 // No context menu if quilting is disabled
14881 if (!GetQuiltMode()) return;
14882
14883 m_piano_ctx_menu = new wxMenu();
14884
14885 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14886 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14887 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14888 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14889 } else {
14890 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14891 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14892 // wxEVT_COMMAND_MENU_SELECTED,
14893 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14894
14895 menu_selected_dbIndex = selected_dbIndex_array[0];
14896 menu_selected_index = selected_index;
14897
14898 // Search the no-show array
14899 bool b_is_in_noshow = false;
14900 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14901 if (m_quilt_noshow_index_array[i] ==
14902 menu_selected_dbIndex) // chart is in the noshow list
14903 {
14904 b_is_in_noshow = true;
14905 break;
14906 }
14907 }
14908
14909 if (b_is_in_noshow) {
14910 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14911 _("Show This Chart"));
14912 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14913 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14914 } else if (GetpCurrentStack()->nEntry > 1) {
14915 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14916 _("Hide This Chart"));
14917 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14918 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14919 }
14920 }
14921
14922 wxPoint pos = wxPoint(x, y - 30);
14923
14924 // Invoke the drop-down menu
14925 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14926 PopupMenu(m_piano_ctx_menu, pos);
14927
14928 delete m_piano_ctx_menu;
14929 m_piano_ctx_menu = NULL;
14930
14931 HideChartInfoWindow();
14932 m_Piano->ResetRollover();
14933
14934 SetQuiltChartHiLiteIndex(-1);
14935 ClearQuiltChartHiLiteIndexArray();
14936
14937 ReloadVP();
14938}
14939
14940void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14941 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14942 if (m_quilt_noshow_index_array[i] ==
14943 menu_selected_dbIndex) // chart is in the noshow list
14944 {
14945 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14946 break;
14947 }
14948 }
14949}
14950
14951void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14952 if (!GetpCurrentStack()) return;
14953 if (!ChartData) return;
14954
14955 RemoveChartFromQuilt(menu_selected_dbIndex);
14956
14957 // It could happen that the chart being disabled is the reference
14958 // chart....
14959 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14960 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14961
14962 int i = menu_selected_index + 1; // select next smaller scale chart
14963 bool b_success = false;
14964 while (i < GetpCurrentStack()->nEntry - 1) {
14965 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14966 if (type == ChartData->GetDBChartType(dbIndex)) {
14967 SelectQuiltRefChart(i);
14968 b_success = true;
14969 break;
14970 }
14971 i++;
14972 }
14973
14974 // If that did not work, try to select the next larger scale compatible
14975 // chart
14976 if (!b_success) {
14977 i = menu_selected_index - 1;
14978 while (i > 0) {
14979 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14980 if (type == ChartData->GetDBChartType(dbIndex)) {
14981 SelectQuiltRefChart(i);
14982 b_success = true;
14983 break;
14984 }
14985 i--;
14986 }
14987 }
14988 }
14989}
14990
14991void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14992 // Remove the item from the list (if it appears) to avoid multiple addition
14993 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14994 if (m_quilt_noshow_index_array[i] ==
14995 dbIndex) // chart is already in the noshow list
14996 {
14997 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14998 break;
14999 }
15000 }
15001
15002 m_quilt_noshow_index_array.push_back(dbIndex);
15003}
15004
15005bool ChartCanvas::UpdateS52State() {
15006 bool retval = false;
15007
15008 if (ps52plib) {
15009 ps52plib->SetShowS57Text(m_encShowText);
15010 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
15011 ps52plib->m_bShowSoundg = m_encShowDepth;
15012 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
15013 ps52plib->m_bShowLdisText = m_encShowLightDesc;
15014
15015 // Lights
15016 if (!m_encShowLights) // On, going off
15017 ps52plib->AddObjNoshow("LIGHTS");
15018 else // Off, going on
15019 ps52plib->RemoveObjNoshow("LIGHTS");
15020 ps52plib->SetLightsOff(!m_encShowLights);
15021 ps52plib->m_bExtendLightSectors = true;
15022
15023 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
15024 ps52plib->SetAnchorOn(m_encShowAnchor);
15025 ps52plib->SetQualityOfData(m_encShowDataQual);
15026 }
15027
15028 return retval;
15029}
15030
15031void ChartCanvas::SetShowENCDataQual(bool show) {
15032 m_encShowDataQual = show;
15033 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15034 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15035
15036 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15037}
15038
15039void ChartCanvas::SetShowENCText(bool show) {
15040 m_encShowText = show;
15041 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15042 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15043
15044 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15045}
15046
15047void ChartCanvas::SetENCDisplayCategory(int category) {
15048 m_encDisplayCategory = category;
15049 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15050}
15051
15052void ChartCanvas::SetShowENCDepth(bool show) {
15053 m_encShowDepth = show;
15054 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15055 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15056
15057 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15058}
15059
15060void ChartCanvas::SetShowENCLightDesc(bool show) {
15061 m_encShowLightDesc = show;
15062 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15063 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15064
15065 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15066}
15067
15068void ChartCanvas::SetShowENCBuoyLabels(bool show) {
15069 m_encShowBuoyLabels = show;
15070 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15071}
15072
15073void ChartCanvas::SetShowENCLights(bool show) {
15074 m_encShowLights = show;
15075 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15076 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15077
15078 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15079}
15080
15081void ChartCanvas::SetShowENCAnchor(bool show) {
15082 m_encShowAnchor = show;
15083 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15084 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15085
15086 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15087}
15088
15089wxRect ChartCanvas::GetMUIBarRect() {
15090 wxRect rv;
15091 if (m_muiBar) {
15092 rv = m_muiBar->GetRect();
15093 }
15094
15095 return rv;
15096}
15097
15098void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
15099 if (!GetAlertString().IsEmpty()) {
15100 wxFont *pfont = wxTheFontList->FindOrCreateFont(
15101 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
15102
15103 dc.SetFont(*pfont);
15104 dc.SetPen(*wxTRANSPARENT_PEN);
15105
15106 dc.SetBrush(wxColour(243, 229, 47));
15107 int w, h;
15108 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
15109 h += 2;
15110 // int yp = vp.pix_height - 20 - h;
15111
15112 wxRect sbr = GetScaleBarRect();
15113 int xp = sbr.x + sbr.width + 10;
15114 int yp = (sbr.y + sbr.height) - h;
15115
15116 int wdraw = w + 10;
15117 dc.DrawRectangle(xp, yp, wdraw, h);
15118 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
15119 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
15120 }
15121}
15122
15123//--------------------------------------------------------------------------------------------------------
15124// Screen Brightness Control Support Routines
15125//
15126//--------------------------------------------------------------------------------------------------------
15127
15128#ifdef __UNIX__
15129#define BRIGHT_XCALIB
15130#define __OPCPN_USEICC__
15131#endif
15132
15133#ifdef __OPCPN_USEICC__
15134int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15135 double co_green, double co_blue);
15136
15137wxString temp_file_name;
15138#endif
15139
15140#if 0
15141class ocpnCurtain: public wxDialog
15142{
15143 DECLARE_CLASS( ocpnCurtain )
15144 DECLARE_EVENT_TABLE()
15145
15146public:
15147 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15148 ~ocpnCurtain( );
15149 bool ProcessEvent(wxEvent& event);
15150
15151};
15152
15153IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15154
15155BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15156END_EVENT_TABLE()
15157
15158ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15159{
15160 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15161}
15162
15163ocpnCurtain::~ocpnCurtain()
15164{
15165}
15166
15167bool ocpnCurtain::ProcessEvent(wxEvent& event)
15168{
15169 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15170 return GetParent()->GetEventHandler()->ProcessEvent(event);
15171}
15172#endif
15173
15174#ifdef _WIN32
15175#include <windows.h>
15176
15177HMODULE hGDI32DLL;
15178typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15179typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15180SetDeviceGammaRamp_ptr_type
15181 g_pSetDeviceGammaRamp; // the API entry points in the dll
15182GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15183
15184WORD *g_pSavedGammaMap;
15185
15186#endif
15187
15188int InitScreenBrightness() {
15189#ifdef _WIN32
15190#ifdef ocpnUSE_GL
15191 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15192 HDC hDC;
15193 BOOL bbr;
15194
15195 if (NULL == hGDI32DLL) {
15196 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15197
15198 if (NULL != hGDI32DLL) {
15199 // Get the entry points of the required functions
15200 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15201 hGDI32DLL, "SetDeviceGammaRamp");
15202 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15203 hGDI32DLL, "GetDeviceGammaRamp");
15204
15205 // If the functions are not found, unload the DLL and return false
15206 if ((NULL == g_pSetDeviceGammaRamp) ||
15207 (NULL == g_pGetDeviceGammaRamp)) {
15208 FreeLibrary(hGDI32DLL);
15209 hGDI32DLL = NULL;
15210 return 0;
15211 }
15212 }
15213 }
15214
15215 // Interface is ready, so....
15216 // Get some storage
15217 if (!g_pSavedGammaMap) {
15218 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15219
15220 hDC = GetDC(NULL); // Get the full screen DC
15221 bbr = g_pGetDeviceGammaRamp(
15222 hDC, g_pSavedGammaMap); // Get the existing ramp table
15223 ReleaseDC(NULL, hDC); // Release the DC
15224 }
15225
15226 // On Windows hosts, try to adjust the registry to allow full range
15227 // setting of Gamma table This is an undocumented Windows hack.....
15228 wxRegKey *pRegKey = new wxRegKey(
15229 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15230 "NT\\CurrentVersion\\ICM");
15231 if (!pRegKey->Exists()) pRegKey->Create();
15232 pRegKey->SetValue("GdiIcmGammaRange", 256);
15233
15234 g_brightness_init = true;
15235 return 1;
15236 }
15237#endif
15238
15239 {
15240 if (NULL == g_pcurtain) {
15241 if (top_frame::Get()->CanSetTransparent()) {
15242 // Build the curtain window
15243 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15244 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15245 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15246 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15247
15248 // g_pcurtain = new ocpnCurtain(gFrame,
15249 // wxPoint(0,0),::wxGetDisplaySize(),
15250 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15251 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15252
15253 g_pcurtain->Hide();
15254
15255 HWND hWnd = GetHwndOf(g_pcurtain);
15256 SetWindowLong(hWnd, GWL_EXSTYLE,
15257 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15258 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15259 g_pcurtain->SetTransparent(0);
15260
15261 g_pcurtain->Maximize();
15262 g_pcurtain->Show();
15263
15264 // All of this is obtuse, but necessary for Windows...
15265 g_pcurtain->Enable();
15266 g_pcurtain->Disable();
15267
15268 top_frame::Get()->Disable();
15269 top_frame::Get()->Enable();
15270 // SetFocus();
15271 }
15272 }
15273 g_brightness_init = true;
15274
15275 return 1;
15276 }
15277#else
15278 // Look for "xcalib" application
15279 wxString cmd("xcalib -version");
15280
15281 wxArrayString output;
15282 long r = wxExecute(cmd, output);
15283 if (0 != r)
15284 wxLogMessage(
15285 " External application \"xcalib\" not found. Screen brightness "
15286 "not changed.");
15287
15288 g_brightness_init = true;
15289 return 0;
15290#endif
15291}
15292
15293int RestoreScreenBrightness() {
15294#ifdef _WIN32
15295
15296 if (g_pSavedGammaMap) {
15297 HDC hDC = GetDC(NULL); // Get the full screen DC
15298 g_pSetDeviceGammaRamp(hDC,
15299 g_pSavedGammaMap); // Restore the saved ramp table
15300 ReleaseDC(NULL, hDC); // Release the DC
15301
15302 free(g_pSavedGammaMap);
15303 g_pSavedGammaMap = NULL;
15304 }
15305
15306 if (g_pcurtain) {
15307 g_pcurtain->Close();
15308 g_pcurtain->Destroy();
15309 g_pcurtain = NULL;
15310 }
15311
15312 g_brightness_init = false;
15313 return 1;
15314
15315#endif
15316
15317#ifdef BRIGHT_XCALIB
15318 if (g_brightness_init) {
15319 wxString cmd;
15320 cmd = "xcalib -clear";
15321 wxExecute(cmd, wxEXEC_ASYNC);
15322 g_brightness_init = false;
15323 }
15324
15325 return 1;
15326#endif
15327
15328 return 0;
15329}
15330
15331// Set brightness. [0..100]
15332int SetScreenBrightness(int brightness) {
15333#ifdef _WIN32
15334
15335 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15336 // some (most modern?) versions of gdi32.dll Load the required library dll,
15337 // if not already in place
15338#ifdef ocpnUSE_GL
15339 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15340 if (g_pcurtain) {
15341 g_pcurtain->Close();
15342 g_pcurtain->Destroy();
15343 g_pcurtain = NULL;
15344 }
15345
15346 InitScreenBrightness();
15347
15348 if (NULL == hGDI32DLL) {
15349 // Unicode stuff.....
15350 wchar_t wdll_name[80];
15351 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15352 LPCWSTR cstr = wdll_name;
15353
15354 hGDI32DLL = LoadLibrary(cstr);
15355
15356 if (NULL != hGDI32DLL) {
15357 // Get the entry points of the required functions
15358 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15359 hGDI32DLL, "SetDeviceGammaRamp");
15360 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15361 hGDI32DLL, "GetDeviceGammaRamp");
15362
15363 // If the functions are not found, unload the DLL and return false
15364 if ((NULL == g_pSetDeviceGammaRamp) ||
15365 (NULL == g_pGetDeviceGammaRamp)) {
15366 FreeLibrary(hGDI32DLL);
15367 hGDI32DLL = NULL;
15368 return 0;
15369 }
15370 }
15371 }
15372
15373 HDC hDC = GetDC(NULL); // Get the full screen DC
15374
15375 /*
15376 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15377 if (cmcap != CM_GAMMA_RAMP)
15378 {
15379 wxLogMessage(" Video hardware does not support brightness control by
15380 gamma ramp adjustment."); return false;
15381 }
15382 */
15383
15384 int increment = brightness * 256 / 100;
15385
15386 // Build the Gamma Ramp table
15387 WORD GammaTable[3][256];
15388
15389 int table_val = 0;
15390 for (int i = 0; i < 256; i++) {
15391 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15392 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15393 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15394
15395 table_val += increment;
15396
15397 if (table_val > 65535) table_val = 65535;
15398 }
15399
15400 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15401 ReleaseDC(NULL, hDC); // Release the DC
15402
15403 return 1;
15404 }
15405#endif
15406
15407 {
15408 if (g_pSavedGammaMap) {
15409 HDC hDC = GetDC(NULL); // Get the full screen DC
15410 g_pSetDeviceGammaRamp(hDC,
15411 g_pSavedGammaMap); // Restore the saved ramp table
15412 ReleaseDC(NULL, hDC); // Release the DC
15413 }
15414
15415 if (brightness < 100) {
15416 if (NULL == g_pcurtain) InitScreenBrightness();
15417
15418 if (g_pcurtain) {
15419 int sbrite = wxMax(1, brightness);
15420 sbrite = wxMin(100, sbrite);
15421
15422 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15423 }
15424 } else {
15425 if (g_pcurtain) {
15426 g_pcurtain->Close();
15427 g_pcurtain->Destroy();
15428 g_pcurtain = NULL;
15429 }
15430 }
15431
15432 return 1;
15433 }
15434
15435#endif
15436
15437#ifdef BRIGHT_XCALIB
15438
15439 if (!g_brightness_init) {
15440 last_brightness = 100;
15441 g_brightness_init = true;
15442 temp_file_name = wxFileName::CreateTempFileName("");
15443 InitScreenBrightness();
15444 }
15445
15446#ifdef __OPCPN_USEICC__
15447 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15448 // desired, and then activate this temporary profile using xcalib <filename>
15449 if (!CreateSimpleICCProfileFile(
15450 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15451 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15452 wxString cmd("xcalib ");
15453 cmd += temp_file_name;
15454
15455 wxExecute(cmd, wxEXEC_ASYNC);
15456 }
15457
15458#else
15459 // Or, use "xcalib -co" to set overall contrast value
15460 // This is not as nice, since the -co parameter wants to be a fraction of
15461 // the current contrast, and values greater than 100 are not allowed. As a
15462 // result, increases of contrast must do a "-clear" step first, which
15463 // produces objectionable flashing.
15464 if (brightness > last_brightness) {
15465 wxString cmd;
15466 cmd = "xcalib -clear";
15467 wxExecute(cmd, wxEXEC_ASYNC);
15468
15469 ::wxMilliSleep(10);
15470
15471 int brite_adj = wxMax(1, brightness);
15472 cmd.Printf("xcalib -co %2d -a", brite_adj);
15473 wxExecute(cmd, wxEXEC_ASYNC);
15474 } else {
15475 int brite_adj = wxMax(1, brightness);
15476 int factor = (brite_adj * 100) / last_brightness;
15477 factor = wxMax(1, factor);
15478 wxString cmd;
15479 cmd.Printf("xcalib -co %2d -a", factor);
15480 wxExecute(cmd, wxEXEC_ASYNC);
15481 }
15482
15483#endif
15484
15485 last_brightness = brightness;
15486
15487#endif
15488
15489 return 0;
15490}
15491
15492#ifdef __OPCPN_USEICC__
15493
15494#define MLUT_TAG 0x6d4c5554L
15495#define VCGT_TAG 0x76636774L
15496
15497int GetIntEndian(unsigned char *s) {
15498 int ret;
15499 unsigned char *p;
15500 int i;
15501
15502 p = (unsigned char *)&ret;
15503
15504 if (1)
15505 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15506 else
15507 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15508
15509 return ret;
15510}
15511
15512unsigned short GetShortEndian(unsigned char *s) {
15513 unsigned short ret;
15514 unsigned char *p;
15515 int i;
15516
15517 p = (unsigned char *)&ret;
15518
15519 if (1)
15520 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15521 else
15522 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15523
15524 return ret;
15525}
15526
15527// Create a very simple Gamma correction file readable by xcalib
15528int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15529 double co_green, double co_blue) {
15530 FILE *fp;
15531
15532 if (file_name) {
15533 fp = fopen(file_name, "wb");
15534 if (!fp) return -1; /* file can not be created */
15535 } else
15536 return -1; /* filename char pointer not valid */
15537
15538 // Write header
15539 char header[128];
15540 for (int i = 0; i < 128; i++) header[i] = 0;
15541
15542 fwrite(header, 128, 1, fp);
15543
15544 // Num tags
15545 int numTags0 = 1;
15546 int numTags = GetIntEndian((unsigned char *)&numTags0);
15547 fwrite(&numTags, 1, 4, fp);
15548
15549 int tagName0 = VCGT_TAG;
15550 int tagName = GetIntEndian((unsigned char *)&tagName0);
15551 fwrite(&tagName, 1, 4, fp);
15552
15553 int tagOffset0 = 128 + 4 * sizeof(int);
15554 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15555 fwrite(&tagOffset, 1, 4, fp);
15556
15557 int tagSize0 = 1;
15558 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15559 fwrite(&tagSize, 1, 4, fp);
15560
15561 fwrite(&tagName, 1, 4, fp); // another copy of tag
15562
15563 fwrite(&tagName, 1, 4, fp); // dummy
15564
15565 // Table type
15566
15567 /* VideoCardGammaTable (The simplest type) */
15568 int gammatype0 = 0;
15569 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15570 fwrite(&gammatype, 1, 4, fp);
15571
15572 int numChannels0 = 3;
15573 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15574 fwrite(&numChannels, 1, 2, fp);
15575
15576 int numEntries0 = 256;
15577 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15578 fwrite(&numEntries, 1, 2, fp);
15579
15580 int entrySize0 = 1;
15581 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15582 fwrite(&entrySize, 1, 2, fp);
15583
15584 unsigned char ramp[256];
15585
15586 // Red ramp
15587 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15588 fwrite(ramp, 256, 1, fp);
15589
15590 // Green ramp
15591 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15592 fwrite(ramp, 256, 1, fp);
15593
15594 // Blue ramp
15595 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15596 fwrite(ramp, 256, 1, fp);
15597
15598 fclose(fp);
15599
15600 return 0;
15601}
15602#endif // __OPCPN_USEICC__
Select * pSelectAIS
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
AIS target definitions.
Class AISTargetQueryDialog.
std::unique_ptr< HostApi > GetHostApi()
HostApi factory,.
Definition api_122.cpp:35
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:61
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1316
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1315
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1316
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1315
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:174
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13965
bool GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates rounded to nearest integer using specified vie...
Definition chcanv.cpp:4608
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11990
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4604
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3688
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13924
obs::EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:899
void GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision,...
Definition chcanv.cpp:4554
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:808
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:512
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:543
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2360
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8095
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7901
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5136
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4685
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5414
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:184
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:792
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4629
bool IsTideDialogOpen() const
Definition chcanv.cpp:13963
void ZoomCanvas(double factor, bool can_zoom_to_cursor=true, bool stoptimer=true)
Perform a smooth zoom operation on the chart canvas by the specified factor.
Definition chcanv.cpp:4691
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13920
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4549
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5433
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10401
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:500
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Locked down interface published in 5.14.1.
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
char IDX_station_name[MAXNAMELEN]
Name of the tidal or current station.
Definition idx_entry.h:62
int station_tz_offset
Offset in seconds to convert from harmonic data (epochs) to the station time zone.
Definition idx_entry.h:93
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:60
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:201
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:59
Data for a loaded plugin, including dl-loaded library.
int m_cap_flag
PlugIn Capabilities descriptor.
PluginLoader is a backend module without any direct GUI functionality.
const ArrayOfPlugIns * GetPlugInArray()
Return list of currently loaded plugins.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1794
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:359
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:777
Dialog for displaying query results of S57 objects.
Definition tc_win.h:46
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:77
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:139
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:145
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:36
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Display utilities.
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:76
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
wxString getUsrHeightUnit(int unit)
Get the abbreviation for the preferred height unit.
double toUsrHeight(double m_height, int unit)
Convert height from meters to preferred height units.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Miscellaneous utilities, many of which string related.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:190
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:100
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:97
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.