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 wxJSONValue 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 wxJSONWriter w;
11261 wxString out;
11262 w.Write(v, out);
11263 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11264
11265 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11266
11267#if 0
11268#define SELTYPE_UNKNOWN 0x0001
11269#define SELTYPE_ROUTEPOINT 0x0002
11270#define SELTYPE_ROUTESEGMENT 0x0004
11271#define SELTYPE_TIDEPOINT 0x0008
11272#define SELTYPE_CURRENTPOINT 0x0010
11273#define SELTYPE_ROUTECREATE 0x0020
11274#define SELTYPE_AISTARGET 0x0040
11275#define SELTYPE_MARKPOINT 0x0080
11276#define SELTYPE_TRACKSEGMENT 0x0100
11277#define SELTYPE_DRAGHANDLE 0x0200
11278#endif
11279
11280 if (g_bhide_context_menus) return true;
11281 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11282 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11283 m_pIDXCandidate, m_nmea_log);
11284
11285 Connect(
11286 wxEVT_COMMAND_MENU_SELECTED,
11287 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11288
11289#ifdef __WXGTK__
11290 // Funny requirement here for gtk, to clear the menu trigger event
11291 // TODO
11292 // Causes a slight "flasH" of the menu,
11293 if (m_inLongPress) {
11294 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11295 m_inLongPress = false;
11296 }
11297#endif
11298
11299 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11300
11301 Disconnect(
11302 wxEVT_COMMAND_MENU_SELECTED,
11303 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11304
11305 delete m_canvasMenu;
11306 m_canvasMenu = NULL;
11307
11308#ifdef __WXQT__
11309 // gFrame->SurfaceToolbar();
11310 // g_MainToolbar->Raise();
11311#endif
11312
11313 return true;
11314}
11315
11316void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11317 // Pass menu events from the canvas to the menu handler
11318 // This is necessarily in ChartCanvas since that is the menu's parent.
11319 if (m_canvasMenu) {
11320 m_canvasMenu->PopupMenuHandler(event);
11321 }
11322 return;
11323}
11324
11325void ChartCanvas::StartRoute() {
11326 // Do not allow more than one canvas to create a route at one time.
11327 if (g_brouteCreating) return;
11328
11329 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11330
11331 g_brouteCreating = true;
11332 m_routeState = 1;
11333 m_bDrawingRoute = false;
11334 SetCursor(*pCursorPencil);
11335 // SetCanvasToolbarItemState(ID_ROUTE, true);
11336 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11337
11338 HideGlobalToolbar();
11339
11340#ifdef __ANDROID__
11341 androidSetRouteAnnunciator(true);
11342#endif
11343}
11344
11345wxString ChartCanvas::FinishRoute() {
11346 m_routeState = 0;
11347 m_prev_pMousePoint = NULL;
11348 m_bDrawingRoute = false;
11349 wxString rv = "";
11350 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11351
11352 // SetCanvasToolbarItemState(ID_ROUTE, false);
11353 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11354#ifdef __ANDROID__
11355 androidSetRouteAnnunciator(false);
11356#endif
11357
11358 SetCursor(*pCursorArrow);
11359
11360 if (m_pMouseRoute) {
11361 if (m_bAppendingRoute) {
11362 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11363 } else {
11364 if (m_pMouseRoute->GetnPoints() > 1) {
11365 // pConfig->AddNewRoute(m_pMouseRoute);
11366 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11367 } else {
11368 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11369 m_pMouseRoute = NULL;
11370 }
11371 }
11372 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11373
11374 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11375 (pRoutePropDialog->IsShown())) {
11376 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11377 }
11378
11379 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11380 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11381 pRouteManagerDialog->UpdateRouteListCtrl();
11382 }
11383 }
11384 m_bAppendingRoute = false;
11385 m_pMouseRoute = NULL;
11386
11387 m_pSelectedRoute = NULL;
11388
11389 undo->InvalidateUndo();
11390 top_frame::Get()->RefreshAllCanvas(true);
11391
11392 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11393
11394 ShowGlobalToolbar();
11395
11396 g_brouteCreating = false;
11397
11398 return rv;
11399}
11400
11401void ChartCanvas::HideGlobalToolbar() {
11402 if (m_canvasIndex == 0) {
11403 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11404 }
11405}
11406
11407void ChartCanvas::ShowGlobalToolbar() {
11408 if (m_canvasIndex == 0) {
11409 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11410 }
11411}
11412
11413void ChartCanvas::ShowAISTargetList() {
11414 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11415 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11416 }
11417
11418 g_pAISTargetList->UpdateAISTargetList();
11419}
11420
11421void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11422 if (!m_bShowOutlines) return;
11423
11424 if (!ChartData) return;
11425
11426 int nEntry = ChartData->GetChartTableEntries();
11427
11428 for (int i = 0; i < nEntry; i++) {
11429 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11430
11431 // Check to see if the candidate chart is in the currently active group
11432 bool b_group_draw = false;
11433 if (m_groupIndex > 0) {
11434 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11435 int index = pt->GetGroupArray()[ig];
11436 if (m_groupIndex == index) {
11437 b_group_draw = true;
11438 break;
11439 }
11440 }
11441 } else
11442 b_group_draw = true;
11443
11444 // Do not render chart outlines if chart identifies as a "basemap"
11445 if (pt->IsBasemap()) continue;
11446
11447 if (b_group_draw) RenderChartOutline(dc, i, vp);
11448 }
11449
11450 // On CM93 Composite Charts, draw the outlines of the next smaller
11451 // scale cell
11452 cm93compchart *pcm93 = NULL;
11453 if (VPoint.b_quilt) {
11454 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11455 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11456 pcm93 = (cm93compchart *)pch;
11457 break;
11458 }
11459 } else if (m_singleChart &&
11460 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11461 pcm93 = (cm93compchart *)m_singleChart;
11462
11463 if (pcm93) {
11464 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11465 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11466
11467 if (zoom_factor > 8.0) {
11468 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11469 dc.SetPen(mPen);
11470 } else {
11471 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11472 dc.SetPen(mPen);
11473 }
11474
11475 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11476 }
11477}
11478
11479void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11480#ifdef ocpnUSE_GL
11481 if (g_bopengl && m_glcc) {
11482 /* opengl version specially optimized */
11483 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11484 return;
11485 }
11486#endif
11487
11488 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11489 if (!ChartData->IsChartAvailable(dbIndex)) return;
11490 }
11491
11492 float plylat, plylon;
11493 float plylat1, plylon1;
11494
11495 int pixx, pixy, pixx1, pixy1;
11496
11497 LLBBox box;
11498 ChartData->GetDBBoundingBox(dbIndex, box);
11499
11500 // Don't draw an outline in the case where the chart covers the entire world
11501 // */
11502 if (box.GetLonRange() == 360) return;
11503
11504 double lon_bias = 0;
11505 // chart is outside of viewport lat/lon bounding box
11506 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11507
11508 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11509
11510 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11511 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11512
11513 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11514 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11515
11516 else
11517 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11518
11519 // Are there any aux ply entries?
11520 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11521 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11522 {
11523 wxPoint r, r1;
11524
11525 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11526 plylon += lon_bias;
11527
11528 GetCanvasPointPix(plylat, plylon, &r);
11529 pixx = r.x;
11530 pixy = r.y;
11531
11532 for (int i = 0; i < nPly - 1; i++) {
11533 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11534 plylon1 += lon_bias;
11535
11536 GetCanvasPointPix(plylat1, plylon1, &r1);
11537 pixx1 = r1.x;
11538 pixy1 = r1.y;
11539
11540 int pixxs1 = pixx1;
11541 int pixys1 = pixy1;
11542
11543 bool b_skip = false;
11544
11545 if (vp.chart_scale > 5e7) {
11546 // calculate projected distance between these two points in meters
11547 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11548 pow((double)(pixy1 - pixy), 2)) /
11549 vp.view_scale_ppm;
11550
11551 if (dist > 0.0) {
11552 // calculate GC distance between these two points in meters
11553 double distgc =
11554 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11555
11556 // If the distances are nonsense, it means that the scale is very
11557 // small and the segment wrapped the world So skip it....
11558 // TODO improve this to draw two segments
11559 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11560 b_skip = true;
11561 } else
11562 b_skip = true;
11563 }
11564
11565 ClipResult res = cohen_sutherland_line_clip_i(
11566 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11567 if (res != Invisible && !b_skip)
11568 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11569
11570 plylat = plylat1;
11571 plylon = plylon1;
11572 pixx = pixxs1;
11573 pixy = pixys1;
11574 }
11575
11576 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11577 plylon1 += lon_bias;
11578
11579 GetCanvasPointPix(plylat1, plylon1, &r1);
11580 pixx1 = r1.x;
11581 pixy1 = r1.y;
11582
11583 ClipResult res = cohen_sutherland_line_clip_i(
11584 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11585 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11586 }
11587
11588 else // Use Aux PlyPoints
11589 {
11590 wxPoint r, r1;
11591
11592 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11593 for (int j = 0; j < nAuxPlyEntries; j++) {
11594 int nAuxPly =
11595 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11596 GetCanvasPointPix(plylat, plylon, &r);
11597 pixx = r.x;
11598 pixy = r.y;
11599
11600 for (int i = 0; i < nAuxPly - 1; i++) {
11601 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11602
11603 GetCanvasPointPix(plylat1, plylon1, &r1);
11604 pixx1 = r1.x;
11605 pixy1 = r1.y;
11606
11607 int pixxs1 = pixx1;
11608 int pixys1 = pixy1;
11609
11610 bool b_skip = false;
11611
11612 if (vp.chart_scale > 5e7) {
11613 // calculate projected distance between these two points in meters
11614 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11615 ((pixy1 - pixy) * (pixy1 - pixy))) /
11616 vp.view_scale_ppm;
11617 if (dist > 0.0) {
11618 // calculate GC distance between these two points in meters
11619 double distgc =
11620 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11621
11622 // If the distances are nonsense, it means that the scale is very
11623 // small and the segment wrapped the world So skip it....
11624 // TODO improve this to draw two segments
11625 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11626 b_skip = true;
11627 } else
11628 b_skip = true;
11629 }
11630
11631 ClipResult res = cohen_sutherland_line_clip_i(
11632 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11633 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11634
11635 plylat = plylat1;
11636 plylon = plylon1;
11637 pixx = pixxs1;
11638 pixy = pixys1;
11639 }
11640
11641 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11642 GetCanvasPointPix(plylat1, plylon1, &r1);
11643 pixx1 = r1.x;
11644 pixy1 = r1.y;
11645
11646 ClipResult res = cohen_sutherland_line_clip_i(
11647 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11648 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11649 }
11650 }
11651}
11652
11653static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11654 const wxArrayString &legend) {
11655 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11656
11657 int pointsize = dFont->GetPointSize();
11658 pointsize /= OCPN_GetWinDIPScaleFactor();
11659
11660 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11661 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11662 false, dFont->GetFaceName());
11663
11664 dc.SetFont(*psRLI_font);
11665
11666 int h = 0;
11667 int w = 0;
11668 int hl, wl;
11669
11670 int xp, yp;
11671 int hilite_offset = 3;
11672
11673 for (wxString line : legend) {
11674#ifdef __WXMAC__
11675 wxScreenDC sdc;
11676 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11677#else
11678 dc.GetTextExtent(line, &wl, &hl);
11679 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11680 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11681#endif
11682 h += hl;
11683 w = wxMax(w, wl);
11684 }
11685 w += (hl / 2); // Add a little right pad
11686
11687 xp = ref_point.x - w;
11688 yp = ref_point.y;
11689 yp += hilite_offset;
11690
11691 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11692
11693 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11694 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11695
11696 for (wxString line : legend) {
11697 dc.DrawText(line, xp, yp);
11698 yp += hl;
11699 }
11700}
11701
11702void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11703 if (!g_bAllowShipToActive) return;
11704
11705 Route *rt = g_pRouteMan->GetpActiveRoute();
11706 if (!rt) return;
11707
11708 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11709 wxPoint2DDouble pa, pb;
11711 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11712
11713 // set pen
11714 int width =
11715 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11716 if (rt->m_width != wxPENSTYLE_INVALID)
11717 width = rt->m_width; // set route pen style if any
11718 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11719 g_shipToActiveStyle, 5)]; // get setting pen style
11720 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11721 wxColour color =
11722 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11723 : // set setting route pen color
11724 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11725 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11726
11727 dc.SetPen(*mypen);
11728 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11729
11730 if (!Use_Opengl)
11731 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11732 (int)pb.m_y, GetVP(), true);
11733
11734#ifdef ocpnUSE_GL
11735 else {
11736#ifdef USE_ANDROID_GLES2
11737 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11738#else
11739 if (style != wxPENSTYLE_SOLID) {
11740 if (glChartCanvas::dash_map.find(style) !=
11741 glChartCanvas::dash_map.end()) {
11742 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11743 dc.SetPen(*mypen);
11744 }
11745 }
11746 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11747#endif
11748
11749 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11750 (int)pb.m_x, (int)pb.m_y, GetVP());
11751 }
11752#endif
11753 }
11754}
11755
11756void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11757 Route *route = 0;
11758 if (m_routeState >= 2) route = m_pMouseRoute;
11759 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11760 route = m_pMeasureRoute;
11761
11762 if (!route) return;
11763
11764 // Validate route pointer
11765 if (!g_pRouteMan->IsRouteValid(route)) return;
11766
11767 double render_lat = m_cursor_lat;
11768 double render_lon = m_cursor_lon;
11769
11770 int np = route->GetnPoints();
11771 if (np) {
11772 if (g_btouch && (np > 1)) np--;
11773 RoutePoint rp = route->GetPoint(np);
11774 render_lat = rp.m_lat;
11775 render_lon = rp.m_lon;
11776 }
11777
11778 double rhumbBearing, rhumbDist;
11779 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11780 &rhumbBearing, &rhumbDist);
11781 double brg = rhumbBearing;
11782 double dist = rhumbDist;
11783
11784 // Skip GreatCircle rubberbanding on touch devices.
11785 if (!g_btouch) {
11786 double gcBearing, gcBearing2, gcDist;
11787 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11788 m_cursor_lat, &gcDist, &gcBearing,
11789 &gcBearing2);
11790 double gcDistm = gcDist / 1852.0;
11791
11792 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11793 rhumbBearing = 90.;
11794
11795 wxPoint destPoint, lastPoint;
11796
11797 route->m_NextLegGreatCircle = false;
11798 int milesDiff = rhumbDist - gcDistm;
11799 if (milesDiff > 1) {
11800 brg = gcBearing;
11801 dist = gcDistm;
11802 route->m_NextLegGreatCircle = true;
11803 }
11804
11805 // FIXME (MacOS, the first segment is rendered wrong)
11806 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11807 &lastPoint);
11808
11809 if (route->m_NextLegGreatCircle) {
11810 for (int i = 1; i <= milesDiff; i++) {
11811 double p = (double)i * (1.0 / (double)milesDiff);
11812 double pLat, pLon;
11813 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11814 &pLon, &pLat, &gcBearing2);
11815 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11816 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11817 false);
11818 lastPoint = destPoint;
11819 }
11820 } else {
11821 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11822 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11823 false);
11824 if (m_bMeasure_DistCircle) {
11825 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11826 powf((float)(r_rband.y - lastPoint.y), 2));
11827
11828 dc.SetPen(*g_pRouteMan->GetRoutePen());
11829 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11830 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11831 }
11832 }
11833 }
11834 }
11835
11836 wxString routeInfo;
11837 wxArrayString infoArray;
11838 double varBrg = 0;
11839 if (g_bShowTrue)
11840 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11841 0x00B0);
11842
11843 if (g_bShowMag) {
11844 double latAverage = (m_cursor_lat + render_lat) / 2;
11845 double lonAverage = (m_cursor_lon + render_lon) / 2;
11846 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11847
11848 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11849 (int)varBrg, 0x00B0);
11850 }
11851 routeInfo << " " << FormatDistanceAdaptive(dist);
11852 infoArray.Add(routeInfo);
11853 routeInfo.Clear();
11854
11855 // To make it easier to use a route as a bearing on a charted object add for
11856 // the first leg also the reverse bearing.
11857 if (np == 1) {
11858 routeInfo << "Reverse: ";
11859 if (g_bShowTrue)
11860 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11861 (int)(brg + 180.) % 360, 0x00B0);
11862 if (g_bShowMag)
11863 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11864 (int)(varBrg + 180.) % 360, 0x00B0);
11865 infoArray.Add(routeInfo);
11866 routeInfo.Clear();
11867 }
11868
11869 wxString s0;
11870 if (!route->m_bIsInLayer)
11871 s0.Append(_("Route") + ": ");
11872 else
11873 s0.Append(_("Layer Route: "));
11874
11875 double disp_length = route->m_route_length;
11876 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11877 s0 += FormatDistanceAdaptive(disp_length);
11878
11879 infoArray.Add(s0);
11880 routeInfo.Clear();
11881
11882 RouteLegInfo(dc, r_rband, infoArray);
11883
11884 m_brepaint_piano = true;
11885}
11886
11887void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11888 if (!m_bShowVisibleSectors) return;
11889
11890 if (g_bDeferredInitDone) {
11891 // need to re-evaluate sectors?
11892 double rhumbBearing, rhumbDist;
11893 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11894 &rhumbBearing, &rhumbDist);
11895
11896 if (rhumbDist > 0.05) // miles
11897 {
11898 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11899 m_sectorlegsVisible);
11900 m_sector_glat = gLat;
11901 m_sector_glon = gLon;
11902 }
11903 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11904 }
11905}
11906
11907void ChartCanvas::WarpPointerDeferred(int x, int y) {
11908 warp_x = x;
11909 warp_y = y;
11910 warp_flag = true;
11911}
11912
11913int s_msg;
11914
11915void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11916 if (!ps52plib) return;
11917
11918 if (VPoint.b_quilt) { // quilted
11919 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11920
11921 if (m_pQuilt->IsQuiltVector()) {
11922 if (ps52plib->GetStateHash() != m_s52StateHash) {
11923 UpdateS52State();
11924 m_s52StateHash = ps52plib->GetStateHash();
11925 }
11926 }
11927 } else {
11928 if (ps52plib->GetStateHash() != m_s52StateHash) {
11929 UpdateS52State();
11930 m_s52StateHash = ps52plib->GetStateHash();
11931 }
11932 }
11933
11934 // Plugin charts
11935 bool bSendPlibState = true;
11936 if (VPoint.b_quilt) { // quilted
11937 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11938 }
11939
11940 if (bSendPlibState) {
11941 wxJSONValue v;
11942 v["OpenCPN Version Major"] = VERSION_MAJOR;
11943 v["OpenCPN Version Minor"] = VERSION_MINOR;
11944 v["OpenCPN Version Patch"] = VERSION_PATCH;
11945 v["OpenCPN Version Date"] = VERSION_DATE;
11946 v["OpenCPN Version Full"] = VERSION_FULL;
11947
11948 // S52PLIB state
11949 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11950 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11951 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11952 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11953 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11954 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11955 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11956
11957 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11958
11959 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11960 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11961
11962 // Global S52 options
11963
11964 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11965 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11966 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11967 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11968 ps52plib->m_bShowS57ImportantTextOnly;
11969 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11970 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11971 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11972 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11973 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11974
11975 // Some global GUI parameters, for completeness
11976 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11977 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11978 v["OpenCPN Scale Factor Exp"] =
11979 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11980 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11981
11982 wxJSONWriter w;
11983 wxString out;
11984 w.Write(v, out);
11985
11986 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11987 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11988 g_lastS52PLIBPluginMessage = out;
11989 }
11990 }
11991}
11992int spaint;
11993int s_in_update;
11994void ChartCanvas::OnPaint(wxPaintEvent &event) {
11995 wxPaintDC dc(this);
11996
11997 // GetToolbar()->Show( m_bToolbarEnable );
11998
11999 // Paint updates may have been externally disabled (temporarily, to avoid
12000 // Yield() recursion performance loss) It is important that the wxPaintDC is
12001 // built, even if we elect to not process this paint message. Otherwise, the
12002 // paint message may not be removed from the message queue, esp on Windows.
12003 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
12004
12005 if (!m_b_paint_enable) {
12006 return;
12007 }
12008
12009 // If necessary, reconfigure the S52 PLIB
12011
12012#ifdef ocpnUSE_GL
12013 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
12014
12015 if (m_glcc && g_bopengl) {
12016 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
12017 s_in_update++;
12018 m_glcc->Update();
12019 s_in_update--;
12020 }
12021
12022 return;
12023 }
12024#endif
12025
12026 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
12027
12028 wxRegion ru = GetUpdateRegion();
12029
12030 int rx, ry, rwidth, rheight;
12031 ru.GetBox(rx, ry, rwidth, rheight);
12032
12033#ifdef ocpnUSE_DIBSECTION
12034 ocpnMemDC temp_dc;
12035#else
12036 wxMemoryDC temp_dc;
12037#endif
12038
12039 long height = GetVP().pix_height;
12040
12041#ifdef __WXMAC__
12042 // On OS X we have to explicitly extend the region for the piano area
12043 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12044 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
12045 height += m_Piano->GetHeight();
12046#endif // __WXMAC__
12047 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
12048
12049 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
12050 if (pthumbwin) {
12051 int thumbx, thumby, thumbsx, thumbsy;
12052 pthumbwin->GetPosition(&thumbx, &thumby);
12053 pthumbwin->GetSize(&thumbsx, &thumbsy);
12054 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
12055
12056 if (pthumbwin->IsShown()) {
12057 rgn_chart.Subtract(rgn_thumbwin);
12058 ru.Subtract(rgn_thumbwin);
12059 }
12060 }
12061
12062 // subtract the chart bar if it isn't transparent, and determine if we need to
12063 // paint it
12064 wxRegion rgn_blit = ru;
12065 if (g_bShowChartBar) {
12066 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
12067 GetClientSize().x, m_Piano->GetHeight());
12068
12069 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12070 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
12071 if (style->chartStatusWindowTransparent)
12072 m_brepaint_piano = true;
12073 else
12074 ru.Subtract(chart_bar_rect);
12075 }
12076 }
12077
12078 if (m_Compass && m_Compass->IsShown()) {
12079 wxRect compassRect = m_Compass->GetRect();
12080 if (ru.Contains(compassRect) != wxOutRegion) {
12081 ru.Subtract(compassRect);
12082 }
12083 }
12084
12085 if (m_notification_button) {
12086 wxRect noteRect = m_notification_button->GetRect();
12087 if (ru.Contains(noteRect) != wxOutRegion) {
12088 ru.Subtract(noteRect);
12089 }
12090 }
12091
12092 // Is this viewpoint the same as the previously painted one?
12093 bool b_newview = true;
12094
12095 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12096 (m_cache_vp.rotation == VPoint.rotation) &&
12097 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
12098 m_cache_vp.IsValid()) {
12099 b_newview = false;
12100 }
12101
12102 // If the ViewPort is skewed or rotated, we may be able to use the cached
12103 // rotated bitmap.
12104 bool b_rcache_ok = false;
12105 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
12106 b_rcache_ok = !b_newview;
12107
12108 // Make a special VP
12109 if (VPoint.b_MercatorProjectionOverride)
12110 VPoint.SetProjectionType(PROJECTION_MERCATOR);
12111 ViewPort svp = VPoint;
12112
12113 svp.pix_width = svp.rv_rect.width;
12114 svp.pix_height = svp.rv_rect.height;
12115
12116 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
12117 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
12118 // VPoint.rv_rect.height);
12119
12120 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
12121
12122 // If we are going to use the cached rotated image, there is no need to fetch
12123 // any chart data and this will do it...
12124 if (b_rcache_ok) chart_get_region.Clear();
12125
12126 // Blit pan acceleration
12127 if (VPoint.b_quilt) // quilted
12128 {
12129 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12130
12131 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12132
12133 bool busy = false;
12134 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12135 m_cache_vp.rotation != VPoint.rotation)) {
12136 AbstractPlatform::ShowBusySpinner();
12137 busy = true;
12138 }
12139
12140 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12141 (m_working_bm.GetHeight() != svp.pix_height))
12142 m_working_bm.Create(svp.pix_width, svp.pix_height,
12143 -1); // make sure the target is big enoug
12144
12145 if (fabs(VPoint.rotation) < 0.01) {
12146 bool b_save = true;
12147
12148 if (g_SencThreadManager) {
12149 if (g_SencThreadManager->GetJobCount()) {
12150 b_save = false;
12151 m_cache_vp.Invalidate();
12152 }
12153 }
12154
12155 // If the saved wxBitmap from last OnPaint is useable
12156 // calculate the blit parameters
12157
12158 // We can only do screen blit painting if subsequent ViewPorts differ by
12159 // whole pixels So, in small scale bFollow mode, force the full screen
12160 // render. This seems a hack....There may be better logic here.....
12161
12162 // if(m_bFollow)
12163 // b_save = false;
12164
12165 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12166 if (b_newview) {
12167 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12168 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12169
12170 int dy = c_new.y - c_old.y;
12171 int dx = c_new.x - c_old.x;
12172
12173 // printf("In OnPaint Trying Blit dx: %d
12174 // dy:%d\n\n", dx, dy);
12175
12176 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12177 if (dx || dy) {
12178 // Blit the reuseable portion of the cached wxBitmap to a working
12179 // bitmap
12180 temp_dc.SelectObject(m_working_bm);
12181
12182 wxMemoryDC cache_dc;
12183 cache_dc.SelectObject(m_cached_chart_bm);
12184
12185 if (dy > 0) {
12186 if (dx > 0) {
12187 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12188 VPoint.pix_height - dy, &cache_dc, dx, dy);
12189 } else {
12190 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12191 VPoint.pix_height - dy, &cache_dc, 0, dy);
12192 }
12193
12194 } else {
12195 if (dx > 0) {
12196 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12197 VPoint.pix_height + dy, &cache_dc, dx, 0);
12198 } else {
12199 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12200 VPoint.pix_height + dy, &cache_dc, 0, 0);
12201 }
12202 }
12203
12204 OCPNRegion update_region;
12205 if (dy) {
12206 if (dy > 0)
12207 update_region.Union(
12208 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12209 else
12210 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12211 }
12212
12213 if (dx) {
12214 if (dx > 0)
12215 update_region.Union(
12216 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12217 else
12218 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12219 }
12220
12221 // Render the new region
12222 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12223 update_region);
12224 cache_dc.SelectObject(wxNullBitmap);
12225 } else {
12226 // No sensible (dx, dy) change in the view, so use the cached
12227 // member bitmap
12228 temp_dc.SelectObject(m_cached_chart_bm);
12229 b_save = false;
12230 }
12231 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12232
12233 } else // not blitable
12234 {
12235 temp_dc.SelectObject(m_working_bm);
12236 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12237 chart_get_region);
12238 }
12239 } else {
12240 // No change in the view, so use the cached member bitmap2
12241 temp_dc.SelectObject(m_cached_chart_bm);
12242 b_save = false;
12243 }
12244 } else // cached bitmap is not yet valid
12245 {
12246 temp_dc.SelectObject(m_working_bm);
12247 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12248 chart_get_region);
12249 }
12250
12251 // Save the fully rendered quilt image as a wxBitmap member of this class
12252 if (b_save) {
12253 // if((m_cached_chart_bm.GetWidth() !=
12254 // svp.pix_width) ||
12255 // (m_cached_chart_bm.GetHeight() !=
12256 // svp.pix_height))
12257 // m_cached_chart_bm.Create(svp.pix_width,
12258 // svp.pix_height, -1); // target wxBitmap
12259 // is big enough
12260 wxMemoryDC scratch_dc_0;
12261 scratch_dc_0.SelectObject(m_cached_chart_bm);
12262 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12263
12264 scratch_dc_0.SelectObject(wxNullBitmap);
12265
12266 m_bm_cache_vp =
12267 VPoint; // save the ViewPort associated with the cached wxBitmap
12268 }
12269 }
12270
12271 else // quilted, rotated
12272 {
12273 temp_dc.SelectObject(m_working_bm);
12274 OCPNRegion chart_get_all_region(
12275 wxRect(0, 0, svp.pix_width, svp.pix_height));
12276 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12277 chart_get_all_region);
12278 }
12279
12280 AbstractPlatform::HideBusySpinner();
12281
12282 }
12283
12284 else // not quilted
12285 {
12286 if (!m_singleChart) {
12287 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12288 dc.Clear();
12289 return;
12290 }
12291
12292 if (!chart_get_region.IsEmpty()) {
12293 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12294 }
12295 }
12296
12297 if (temp_dc.IsOk()) {
12298 // Arrange to render the World Chart vector data behind the rendered
12299 // current chart so that uncovered canvas areas show at least the world
12300 // chart.
12301 OCPNRegion chartValidRegion;
12302 if (!VPoint.b_quilt) {
12303 // Make a region covering the current chart on the canvas
12304
12305 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12306 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12307 else {
12308 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12309 // require that the viewport passed here have pix_width and pix_height
12310 // set to the actual display, not the virtual (rv_rect) sizes
12311 // (the vector calculations require the virtual sizes in svp)
12312
12313 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12314 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12315 }
12316 } else
12317 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12318
12319 temp_dc.DestroyClippingRegion();
12320
12321 // Copy current chart region
12322 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12323
12324 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12325
12326 if (!backgroundRegion.IsEmpty()) {
12327 // Draw the Background Chart only in the areas NOT covered by the
12328 // current chart view
12329
12330 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12331 clipping regions with more than 1 rectangle so... */
12332 wxColour water = pWorldBackgroundChart->water;
12333 if (water.IsOk()) {
12334 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12335 temp_dc.SetBrush(wxBrush(water));
12336 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12337 while (upd.HaveRects()) {
12338 wxRect rect = upd.GetRect();
12339 temp_dc.DrawRectangle(rect);
12340 upd.NextRect();
12341 }
12342 }
12343 // Associate with temp_dc
12344 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12345 temp_dc.SetDeviceClippingRegion(*clip_region);
12346 delete clip_region;
12347
12348 ocpnDC bgdc(temp_dc);
12349 double r = VPoint.rotation;
12350 SetVPRotation(VPoint.skew);
12351
12352 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12353 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12354
12355 SetVPRotation(r);
12356 }
12357 } // temp_dc.IsOk();
12358
12359 wxMemoryDC *pChartDC = &temp_dc;
12360 wxMemoryDC rotd_dc;
12361
12362 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12363 // Can we use the current rotated image cache?
12364 if (!b_rcache_ok) {
12365#ifdef __WXMSW__
12366 wxMemoryDC tbase_dc;
12367 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12368 tbase_dc.SelectObject(bm_base);
12369 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12370 tbase_dc.SelectObject(wxNullBitmap);
12371#else
12372 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12373#endif
12374
12375 wxImage base_image;
12376 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12377
12378 // Use a local static image rotator to improve wxWidgets code profile
12379 // Especially, on GTK the wxRound and wxRealPoint functions are very
12380 // expensive.....
12381
12382 double angle = GetVP().skew - GetVP().rotation;
12383 wxImage ri;
12384 bool b_rot_ok = false;
12385 if (base_image.IsOk()) {
12386 ViewPort rot_vp = GetVP();
12387
12388 m_b_rot_hidef = false;
12389
12390 ri = Image_Rotate(
12391 base_image, angle,
12392 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12393 m_b_rot_hidef, &m_roffset);
12394
12395 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12396 (rot_vp.rotation == VPoint.rotation) &&
12397 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12398 rot_vp.IsValid() && (ri.IsOk())) {
12399 b_rot_ok = true;
12400 }
12401 }
12402
12403 if (b_rot_ok) {
12404 delete m_prot_bm;
12405 m_prot_bm = new wxBitmap(ri);
12406 }
12407
12408 m_roffset.x += VPoint.rv_rect.x;
12409 m_roffset.y += VPoint.rv_rect.y;
12410 }
12411
12412 if (m_prot_bm && m_prot_bm->IsOk()) {
12413 rotd_dc.SelectObject(*m_prot_bm);
12414 pChartDC = &rotd_dc;
12415 } else {
12416 pChartDC = &temp_dc;
12417 m_roffset = wxPoint(0, 0);
12418 }
12419 } else { // unrotated
12420 pChartDC = &temp_dc;
12421 m_roffset = wxPoint(0, 0);
12422 }
12423
12424 wxPoint offset = m_roffset;
12425
12426 // Save the PixelCache viewpoint for next time
12427 m_cache_vp = VPoint;
12428
12429 // Set up a scratch DC for overlay objects
12430 wxMemoryDC mscratch_dc;
12431 mscratch_dc.SelectObject(*pscratch_bm);
12432
12433 mscratch_dc.ResetBoundingBox();
12434 mscratch_dc.DestroyClippingRegion();
12435 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12436
12437 // Blit the externally invalidated areas of the chart onto the scratch dc
12438 wxRegionIterator upd(rgn_blit); // get the update rect list
12439 while (upd) {
12440 wxRect rect = upd.GetRect();
12441
12442 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12443 rect.x - offset.x, rect.y - offset.y);
12444 upd++;
12445 }
12446
12447 // If multi-canvas, indicate which canvas has keyboard focus
12448 // by drawing a simple blue bar at the top.
12449 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12450 if (this == wxWindow::FindFocus()) {
12451 g_focusCanvas = this;
12452
12453 wxColour colour = GetGlobalColor("BLUE4");
12454 mscratch_dc.SetPen(wxPen(colour));
12455 mscratch_dc.SetBrush(wxBrush(colour));
12456
12457 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12458 mscratch_dc.DrawRectangle(activeRect);
12459 }
12460 }
12461
12462 // Any MBtiles?
12463 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12464 unsigned int im = stackIndexArray.size();
12465 if (VPoint.b_quilt && im > 0) {
12466 std::vector<int> tiles_to_show;
12467 for (unsigned int is = 0; is < im; is++) {
12468 const ChartTableEntry &cte =
12469 ChartData->GetChartTableEntry(stackIndexArray[is]);
12470 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12471 continue;
12472 }
12473 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12474 tiles_to_show.push_back(stackIndexArray[is]);
12475 }
12476 }
12477
12478 if (tiles_to_show.size())
12479 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12480 }
12481
12482 // May get an unexpected OnPaint call while switching display modes
12483 // Guard for that.
12484 if (!g_bopengl) {
12485 ocpnDC scratch_dc(mscratch_dc);
12486 RenderAlertMessage(mscratch_dc, GetVP());
12487 }
12488
12489#if 0
12490 // quiting?
12491 if (g_bquiting) {
12492#ifdef ocpnUSE_DIBSECTION
12493 ocpnMemDC q_dc;
12494#else
12495 wxMemoryDC q_dc;
12496#endif
12497 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12498 q_dc.SelectObject(qbm);
12499
12500 // Get a copy of the screen
12501 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12502
12503 // Draw a rectangle over the screen with a stipple brush
12504 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12505 q_dc.SetBrush(qbr);
12506 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12507
12508 // Blit back into source
12509 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12510 wxCOPY);
12511
12512 q_dc.SelectObject(wxNullBitmap);
12513 }
12514#endif
12515
12516#if 0
12517 // It is possible that this two-step method may be reuired for some platforms.
12518 // So, retain in the code base to aid recovery if necessary
12519
12520 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12521 if( VPoint.b_quilt ) {
12522 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12523 ChartBase *chart = m_pQuilt->GetRefChart();
12524 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12525
12526 // Clear the text Global declutter list
12527 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12528 if(ChPI)
12529 ChPI->ClearPLIBTextList();
12530 else{
12531 if(ps52plib)
12532 ps52plib->ClearTextList();
12533 }
12534
12535 wxMemoryDC t_dc;
12536 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12537
12538 wxColor maskBackground = wxColour(1,0,0);
12539 t_dc.SelectObject( qbm );
12540 t_dc.SetBackground(wxBrush(maskBackground));
12541 t_dc.Clear();
12542
12543 // Copy the scratch DC into the new bitmap
12544 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12545
12546 // Render the text to the new bitmap
12547 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12548 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12549
12550 // Copy the new bitmap back to the scratch dc
12551 wxRegionIterator upd_final( ru );
12552 while( upd_final ) {
12553 wxRect rect = upd_final.GetRect();
12554 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12555 upd_final++;
12556 }
12557
12558 t_dc.SelectObject( wxNullBitmap );
12559 }
12560 }
12561 }
12562#endif
12563 // Direct rendering model...
12564 if (VPoint.b_quilt) {
12565 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12566 ChartBase *chart = m_pQuilt->GetRefChart();
12567 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12568 // Clear the text Global declutter list
12569 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12570 if (ChPI)
12571 ChPI->ClearPLIBTextList();
12572 else {
12573 if (ps52plib) ps52plib->ClearTextList();
12574 }
12575
12576 // Render the text directly to the scratch bitmap
12577 OCPNRegion chart_all_text_region(
12578 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12579
12580 if (g_bShowChartBar && m_Piano) {
12581 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12582 GetVP().pix_width, m_Piano->GetHeight());
12583
12584 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12585 if (!style->chartStatusWindowTransparent)
12586 chart_all_text_region.Subtract(chart_bar_rect);
12587 }
12588
12589 if (m_Compass && m_Compass->IsShown()) {
12590 wxRect compassRect = m_Compass->GetRect();
12591 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12592 chart_all_text_region.Subtract(compassRect);
12593 }
12594 }
12595
12596 mscratch_dc.DestroyClippingRegion();
12597
12598 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12599 chart_all_text_region);
12600 }
12601 }
12602 }
12603
12604 // Now that charts are fully rendered, apply the overlay objects as decals.
12605 ocpnDC scratch_dc(mscratch_dc);
12606 DrawOverlayObjects(scratch_dc, ru);
12607
12608 // And finally, blit the scratch dc onto the physical dc
12609 wxRegionIterator upd_final(rgn_blit);
12610 while (upd_final) {
12611 wxRect rect = upd_final.GetRect();
12612 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12613 rect.y);
12614 upd_final++;
12615 }
12616
12617 // Deselect the chart bitmap from the temp_dc, so that it will not be
12618 // destroyed in the temp_dc dtor
12619 temp_dc.SelectObject(wxNullBitmap);
12620 // And for the scratch bitmap
12621 mscratch_dc.SelectObject(wxNullBitmap);
12622
12623 dc.DestroyClippingRegion();
12624
12625 PaintCleanup();
12626}
12627
12628void ChartCanvas::PaintCleanup() {
12629 // Handle the current graphic window, if present
12630 if (m_inPinch) return;
12631
12632 if (pCwin) {
12633 pCwin->Show();
12634 if (m_bTCupdate) {
12635 pCwin->Refresh();
12636 pCwin->Update();
12637 }
12638 }
12639
12640 // And set flags for next time
12641 m_bTCupdate = false;
12642
12643 // Handle deferred WarpPointer
12644 if (warp_flag) {
12645 WarpPointer(warp_x, warp_y);
12646 warp_flag = false;
12647 }
12648
12649 // Start movement timers, this runs nearly immediately.
12650 // the reason we cannot simply call it directly is the
12651 // refresh events it emits may be blocked from this paint event
12652 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12653 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12654}
12655
12656#if 0
12657wxColour GetErrorGraphicColor(double val)
12658{
12659 /*
12660 double valm = wxMin(val_max, val);
12661
12662 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12663 unsigned char red = (unsigned char)(255 * (valm/val_max));
12664
12665 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12666
12667 hv.saturation = 1.0;
12668 hv.value = 1.0;
12669
12670 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12671 return wxColour(rv.red, rv.green, rv.blue);
12672 */
12673
12674 // HTML colors taken from NOAA WW3 Web representation
12675 wxColour c;
12676 if((val > 0) && (val < 1)) c.Set("#002ad9");
12677 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12678 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12679 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12680 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12681 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12682 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12683 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12684 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12685 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12686 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12687 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12688 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12689 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12690 else if((val >= 30) && (val < 36)) c.Set("#870000");
12691 else if((val >= 36) && (val < 42)) c.Set("#690000");
12692 else if((val >= 42) && (val < 48)) c.Set("#550000");
12693 else if( val >= 48) c.Set("#410000");
12694
12695 return c;
12696}
12697
12698void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12699{
12700 wxImage gr_image(vp->pix_width, vp->pix_height);
12701 gr_image.InitAlpha();
12702
12703 double maxval = -10000;
12704 double minval = 10000;
12705
12706 double rlat, rlon;
12707 double glat, glon;
12708
12709 GetCanvasPixPoint(0, 0, rlat, rlon);
12710
12711 for(int i=1; i < vp->pix_height-1; i++)
12712 {
12713 for(int j=0; j < vp->pix_width; j++)
12714 {
12715 // Reference mercator value
12716// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12717
12718 // Georef value
12719 GetCanvasPixPoint(j, i, glat, glon);
12720
12721 maxval = wxMax(maxval, (glat - rlat));
12722 minval = wxMin(minval, (glat - rlat));
12723
12724 }
12725 rlat = glat;
12726 }
12727
12728 GetCanvasPixPoint(0, 0, rlat, rlon);
12729 for(int i=1; i < vp->pix_height-1; i++)
12730 {
12731 for(int j=0; j < vp->pix_width; j++)
12732 {
12733 // Reference mercator value
12734// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12735
12736 // Georef value
12737 GetCanvasPixPoint(j, i, glat, glon);
12738
12739 double f = ((glat - rlat)-minval)/(maxval - minval);
12740
12741 double dy = (f * 40);
12742
12743 wxColour c = GetErrorGraphicColor(dy);
12744 unsigned char r = c.Red();
12745 unsigned char g = c.Green();
12746 unsigned char b = c.Blue();
12747
12748 gr_image.SetRGB(j, i, r,g,b);
12749 if((glat - rlat )!= 0)
12750 gr_image.SetAlpha(j, i, 128);
12751 else
12752 gr_image.SetAlpha(j, i, 255);
12753
12754 }
12755 rlat = glat;
12756 }
12757
12758 // Create a Bitmap
12759 wxBitmap *pbm = new wxBitmap(gr_image);
12760 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12761 pbm->SetMask(gr_mask);
12762
12763 pmdc->DrawBitmap(*pbm, 0,0);
12764
12765 delete pbm;
12766
12767}
12768
12769#endif
12770
12771void ChartCanvas::CancelMouseRoute() {
12772 m_routeState = 0;
12773 m_pMouseRoute = NULL;
12774 m_bDrawingRoute = false;
12775}
12776
12777int ChartCanvas::GetNextContextMenuId() {
12778 return CanvasMenuHandler::GetNextContextMenuId();
12779}
12780
12781bool ChartCanvas::SetCursor(const wxCursor &c) {
12782#ifdef ocpnUSE_GL
12783 if (g_bopengl && m_glcc)
12784 return m_glcc->SetCursor(c);
12785 else
12786#endif
12787 return wxWindow::SetCursor(c);
12788}
12789
12790void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12791 if (g_bquiting) return;
12792 // Keep the mouse position members up to date
12793 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12794
12795 // Retrigger the route leg popup timer
12796 // This handles the case when the chart is moving in auto-follow mode,
12797 // but no user mouse input is made. The timer handler may Hide() the
12798 // popup if the chart moved enough n.b. We use slightly longer oneshot
12799 // value to allow this method's Refresh() to complete before potentially
12800 // getting another Refresh() in the popup timer handler.
12801 if (!m_RolloverPopupTimer.IsRunning() &&
12802 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12803 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12804 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12805 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12806
12807#ifdef ocpnUSE_GL
12808 if (m_glcc && g_bopengl) {
12809 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12810 // overlay objects.
12811 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12812
12813 m_glcc->Refresh(eraseBackground,
12814 NULL); // We always are going to render the entire screen
12815 // anyway, so make
12816 // sure that the window managers understand the invalid area
12817 // is actually the entire client area.
12818
12819 // We need to selectively Refresh some child windows, if they are visible.
12820 // Note that some children are refreshed elsewhere on timer ticks, so don't
12821 // need attention here.
12822
12823 // Thumbnail chart
12824 if (pthumbwin && pthumbwin->IsShown()) {
12825 pthumbwin->Raise();
12826 pthumbwin->Refresh(false);
12827 }
12828
12829 // ChartInfo window
12830 if (m_pCIWin && m_pCIWin->IsShown()) {
12831 m_pCIWin->Raise();
12832 m_pCIWin->Refresh(false);
12833 }
12834
12835 // if(g_MainToolbar)
12836 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12837
12838 } else
12839#endif
12840 wxWindow::Refresh(eraseBackground, rect);
12841}
12842
12843void ChartCanvas::Update() {
12844 if (m_glcc && g_bopengl) {
12845#ifdef ocpnUSE_GL
12846 m_glcc->Update();
12847#endif
12848 } else
12849 wxWindow::Update();
12850}
12851
12852void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12853 if (!pemboss) return;
12854 int x = pemboss->x, y = pemboss->y;
12855 const double factor = 200;
12856
12857 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12858 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12859 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12860
12861 // Grab a snipped image out of the chart
12862 wxMemoryDC snip_dc;
12863 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12864 snip_dc.SelectObject(snip_bmp);
12865
12866 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12867 snip_dc.SelectObject(wxNullBitmap);
12868
12869 wxImage snip_img = snip_bmp.ConvertToImage();
12870
12871 // Apply Emboss map to the snip image
12872 unsigned char *pdata = snip_img.GetData();
12873 if (pdata) {
12874 for (int y = 0; y < pemboss->height; y++) {
12875 int map_index = (y * pemboss->width);
12876 for (int x = 0; x < pemboss->width; x++) {
12877 double val = (pemboss->pmap[map_index] * factor) / 256.;
12878
12879 int nred = (int)((*pdata) + val);
12880 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12881 *pdata++ = (unsigned char)nred;
12882
12883 int ngreen = (int)((*pdata) + val);
12884 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12885 *pdata++ = (unsigned char)ngreen;
12886
12887 int nblue = (int)((*pdata) + val);
12888 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12889 *pdata++ = (unsigned char)nblue;
12890
12891 map_index++;
12892 }
12893 }
12894 }
12895
12896 // Convert embossed snip to a bitmap
12897 wxBitmap emb_bmp(snip_img);
12898
12899 // Map to another memoryDC
12900 wxMemoryDC result_dc;
12901 result_dc.SelectObject(emb_bmp);
12902
12903 // Blit to target
12904 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12905
12906 result_dc.SelectObject(wxNullBitmap);
12907}
12908
12909emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12910 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12911
12912 if (GetQuiltMode()) {
12913 // disable Overzoom indicator for MBTiles
12914 int refIndex = GetQuiltRefChartdbIndex();
12915 if (refIndex >= 0) {
12916 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12917 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12918 if (current_type == CHART_TYPE_MBTILES) {
12919 ChartBase *pChart = m_pQuilt->GetRefChart();
12920 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12921 if (ptc) {
12922 zoom_factor = ptc->GetZoomFactor();
12923 }
12924 }
12925 }
12926
12927 if (zoom_factor <= 3.9) return NULL;
12928 } else {
12929 if (m_singleChart) {
12930 if (zoom_factor <= 3.9) return NULL;
12931 } else
12932 return NULL;
12933 }
12934
12935 if (m_pEM_OverZoom) {
12936 m_pEM_OverZoom->x = 4;
12937 m_pEM_OverZoom->y = 0;
12938 if (g_MainToolbar && IsPrimaryCanvas()) {
12939 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12940 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12941 }
12942 }
12943 return m_pEM_OverZoom;
12944}
12945
12946void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12947 GridDraw(dc);
12948
12949 // bool pluginOverlayRender = true;
12950 //
12951 // if(g_canvasConfig > 0){ // Multi canvas
12952 // if(IsPrimaryCanvas())
12953 // pluginOverlayRender = false;
12954 // }
12955
12956 g_overlayCanvas = this;
12957
12958 if (g_pi_manager) {
12959 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12960 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12962 }
12963
12964 AISDrawAreaNotices(dc, GetVP(), this);
12965
12966 wxDC *pdc = dc.GetDC();
12967 if (pdc) {
12968 pdc->DestroyClippingRegion();
12969 wxDCClipper(*pdc, ru);
12970 }
12971
12972 if (m_bShowNavobjects) {
12973 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12974 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12975 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12976 DrawAnchorWatchPoints(dc);
12977 } else {
12978 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12979 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12980 }
12981
12982 AISDraw(dc, GetVP(), this);
12983 ShipDraw(dc);
12984 AlertDraw(dc);
12985
12986 RenderVisibleSectorLights(dc);
12987
12988 RenderAllChartOutlines(dc, GetVP());
12989 RenderRouteLegs(dc);
12990 RenderShipToActive(dc, false);
12991 ScaleBarDraw(dc);
12992 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12993 if (g_pi_manager) {
12994 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12996 }
12997
12998 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12999 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
13000
13001 if (g_pi_manager) {
13002 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
13004 }
13005
13006 if (m_bShowTide) {
13007 RebuildTideSelectList(GetVP().GetBBox());
13008 DrawAllTidesInBBox(dc, GetVP().GetBBox());
13009 }
13010
13011 if (m_bShowCurrent) {
13012 RebuildCurrentSelectList(GetVP().GetBBox());
13013 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
13014 }
13015
13016 if (!g_PrintingInProgress) {
13017 if (IsPrimaryCanvas()) {
13018 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
13019 }
13020
13021 if (IsPrimaryCanvas()) {
13022 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
13023 }
13024
13025 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
13026
13027 if (m_pTrackRolloverWin) {
13028 m_pTrackRolloverWin->Draw(dc);
13029 m_brepaint_piano = true;
13030 }
13031
13032 if (m_pRouteRolloverWin) {
13033 m_pRouteRolloverWin->Draw(dc);
13034 m_brepaint_piano = true;
13035 }
13036
13037 if (m_pAISRolloverWin) {
13038 m_pAISRolloverWin->Draw(dc);
13039 m_brepaint_piano = true;
13040 }
13041 if (m_brepaint_piano && g_bShowChartBar) {
13042 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
13043 }
13044
13045 if (m_Compass) m_Compass->Paint(dc);
13046
13047 if (!g_CanvasHideNotificationIcon) {
13048 if (IsPrimaryCanvas()) {
13049 auto &noteman = NotificationManager::GetInstance();
13050 if (m_notification_button) {
13051 if (noteman.GetNotificationCount()) {
13052 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
13053 if (m_notification_button->UpdateStatus()) Refresh();
13054 m_notification_button->Show(true);
13055 m_notification_button->Paint(dc);
13056 } else {
13057 m_notification_button->Show(false);
13058 }
13059 }
13060 }
13061 }
13062 }
13063 if (g_pi_manager) {
13064 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
13066 }
13067}
13068
13069emboss_data *ChartCanvas::EmbossDepthScale() {
13070 if (!m_bShowDepthUnits) return NULL;
13071
13072 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
13073
13074 if (GetQuiltMode()) {
13075 wxString s = m_pQuilt->GetQuiltDepthUnit();
13076 s.MakeUpper();
13077 if (s == "FEET")
13078 depth_unit_type = DEPTH_UNIT_FEET;
13079 else if (s.StartsWith("FATHOMS"))
13080 depth_unit_type = DEPTH_UNIT_FATHOMS;
13081 else if (s.StartsWith("METERS"))
13082 depth_unit_type = DEPTH_UNIT_METERS;
13083 else if (s.StartsWith("METRES"))
13084 depth_unit_type = DEPTH_UNIT_METERS;
13085 else if (s.StartsWith("METRIC"))
13086 depth_unit_type = DEPTH_UNIT_METERS;
13087 else if (s.StartsWith("METER"))
13088 depth_unit_type = DEPTH_UNIT_METERS;
13089
13090 } else {
13091 if (m_singleChart) {
13092 depth_unit_type = m_singleChart->GetDepthUnitType();
13093 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
13094 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
13095 }
13096 }
13097
13098 emboss_data *ped = NULL;
13099 switch (depth_unit_type) {
13100 case DEPTH_UNIT_FEET:
13101 ped = m_pEM_Feet;
13102 break;
13103 case DEPTH_UNIT_METERS:
13104 ped = m_pEM_Meters;
13105 break;
13106 case DEPTH_UNIT_FATHOMS:
13107 ped = m_pEM_Fathoms;
13108 break;
13109 default:
13110 return NULL;
13111 }
13112
13113 ped->x = (GetVP().pix_width - ped->width);
13114
13115 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
13116 wxRect r = m_Compass->GetRect();
13117 ped->y = r.y + r.height;
13118 } else {
13119 ped->y = 40;
13120 }
13121 return ped;
13122}
13123
13124void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
13125 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13126 wxFont font;
13127 if (style->embossFont == wxEmptyString) {
13128 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13129 font = *dFont;
13130 font.SetPointSize(60);
13131 font.SetWeight(wxFONTWEIGHT_BOLD);
13132 } else
13133 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13134 wxFONTWEIGHT_BOLD, false, style->embossFont);
13135
13136 int emboss_width = 500;
13137 int emboss_height = 200;
13138
13139 // Free any existing emboss maps
13140 delete m_pEM_Feet;
13141 delete m_pEM_Meters;
13142 delete m_pEM_Fathoms;
13143
13144 // Create the 3 DepthUnit emboss map structures
13145 m_pEM_Feet =
13146 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13147 m_pEM_Meters =
13148 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13149 m_pEM_Fathoms =
13150 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13151}
13152
13153#define OVERZOOM_TEXT _("OverZoom")
13154
13155void ChartCanvas::SetOverzoomFont() {
13156 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13157 int w, h;
13158
13159 wxFont font;
13160 if (style->embossFont == wxEmptyString) {
13161 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13162 font = *dFont;
13163 font.SetPointSize(40);
13164 font.SetWeight(wxFONTWEIGHT_BOLD);
13165 } else
13166 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13167 wxFONTWEIGHT_BOLD, false, style->embossFont);
13168
13169 wxClientDC dc(this);
13170 dc.SetFont(font);
13171 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13172
13173 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13174 font.SetPointSize(font.GetPointSize() - 1);
13175 dc.SetFont(font);
13176 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13177 }
13178 m_overzoomFont = font;
13179 m_overzoomTextWidth = w;
13180 m_overzoomTextHeight = h;
13181}
13182
13183void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13184 delete m_pEM_OverZoom;
13185
13186 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13187 m_pEM_OverZoom =
13188 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13189 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13190}
13191
13192emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13193 int height, const wxString &str,
13194 ColorScheme cs) {
13195 int *pmap;
13196
13197 // Create a temporary bitmap
13198 wxBitmap bmp(width, height, -1);
13199
13200 // Create a memory DC
13201 wxMemoryDC temp_dc;
13202 temp_dc.SelectObject(bmp);
13203
13204 // Paint on it
13205 temp_dc.SetBackground(*wxWHITE_BRUSH);
13206 temp_dc.SetTextBackground(*wxWHITE);
13207 temp_dc.SetTextForeground(*wxBLACK);
13208
13209 temp_dc.Clear();
13210
13211 temp_dc.SetFont(font);
13212
13213 int str_w, str_h;
13214 temp_dc.GetTextExtent(str, &str_w, &str_h);
13215 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13216 temp_dc.DrawText(str, 1, 1);
13217
13218 // Deselect the bitmap
13219 temp_dc.SelectObject(wxNullBitmap);
13220
13221 // Convert bitmap the wxImage for manipulation
13222 wxImage img = bmp.ConvertToImage();
13223
13224 int image_width = str_w * 105 / 100;
13225 int image_height = str_h * 105 / 100;
13226 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13227 wxMin(image_height, img.GetHeight()));
13228 wxImage imgs = img.GetSubImage(r);
13229
13230 double val_factor;
13231 switch (cs) {
13232 case GLOBAL_COLOR_SCHEME_DAY:
13233 default:
13234 val_factor = 1;
13235 break;
13236 case GLOBAL_COLOR_SCHEME_DUSK:
13237 val_factor = .5;
13238 break;
13239 case GLOBAL_COLOR_SCHEME_NIGHT:
13240 val_factor = .25;
13241 break;
13242 }
13243
13244 int val;
13245 int index;
13246 const int w = imgs.GetWidth();
13247 const int h = imgs.GetHeight();
13248 pmap = (int *)calloc(w * h * sizeof(int), 1);
13249 // Create emboss map by differentiating the emboss image
13250 // and storing integer results in pmap
13251 // n.b. since the image is B/W, it is sufficient to check
13252 // one channel (i.e. red) only
13253 for (int y = 1; y < h - 1; y++) {
13254 for (int x = 1; x < w - 1; x++) {
13255 val =
13256 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13257 val = (int)(val * val_factor);
13258 index = (y * w) + x;
13259 pmap[index] = val;
13260 }
13261 }
13262
13263 emboss_data *pret = new emboss_data;
13264 pret->pmap = pmap;
13265 pret->width = w;
13266 pret->height = h;
13267
13268 return pret;
13269}
13270
13271void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13272 Track *active_track = NULL;
13273 for (Track *pTrackDraw : g_TrackList) {
13274 if (g_pActiveTrack == pTrackDraw) {
13275 active_track = pTrackDraw;
13276 continue;
13277 }
13278
13279 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13280 }
13281
13282 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13283}
13284
13285void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13286 Track *active_track = NULL;
13287 for (Track *pTrackDraw : g_TrackList) {
13288 if (g_pActiveTrack == pTrackDraw) {
13289 active_track = pTrackDraw;
13290 break;
13291 }
13292 }
13293 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13294}
13295
13296void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13297 Route *active_route = NULL;
13298 for (Route *pRouteDraw : *pRouteList) {
13299 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13300 active_route = pRouteDraw;
13301 continue;
13302 }
13303
13304 // if(m_canvasIndex == 1)
13305 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13306 }
13307
13308 // Draw any active or selected route (or track) last, so that is is always on
13309 // top
13310 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13311}
13312
13313void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13314 Route *active_route = NULL;
13315
13316 for (Route *pRouteDraw : *pRouteList) {
13317 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13318 active_route = pRouteDraw;
13319 break;
13320 }
13321 }
13322 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13323}
13324
13325void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13326 if (!pWayPointMan) return;
13327
13328 auto node = pWayPointMan->GetWaypointList()->begin();
13329
13330 while (node != pWayPointMan->GetWaypointList()->end()) {
13331 RoutePoint *pWP = *node;
13332 if (pWP) {
13333 if (pWP->m_bIsInRoute) {
13334 ++node;
13335 continue;
13336 }
13337
13338 /* technically incorrect... waypoint has bounding box */
13339 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13340 RoutePointGui(*pWP).Draw(dc, this, NULL);
13341 else {
13342 // Are Range Rings enabled?
13343 if (pWP->GetShowWaypointRangeRings() &&
13344 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13345 double factor = 1.00;
13346 if (pWP->GetWaypointRangeRingsStepUnits() ==
13347 1) // convert kilometers to NMi
13348 factor = 1 / 1.852;
13349
13350 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13351 pWP->GetWaypointRangeRingsStep() / 60.;
13352 radius *= 2; // Fudge factor
13353
13354 LLBBox radar_box;
13355 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13356 pWP->m_lat + radius, pWP->m_lon + radius);
13357 if (!BltBBox.IntersectOut(radar_box)) {
13358 RoutePointGui(*pWP).Draw(dc, this, NULL);
13359 }
13360 }
13361 }
13362 }
13363
13364 ++node;
13365 }
13366}
13367
13368void ChartCanvas::DrawBlinkObjects() {
13369 // All RoutePoints
13370 wxRect update_rect;
13371
13372 if (!pWayPointMan) return;
13373
13374 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13375 if (pWP) {
13376 if (pWP->m_bBlink) {
13377 update_rect.Union(pWP->CurrentRect_in_DC);
13378 }
13379 }
13380 }
13381 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13382}
13383
13384void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13385 // draw anchor watch rings, if activated
13386
13388 wxPoint r1, r2;
13389 wxPoint lAnchorPoint1, lAnchorPoint2;
13390 double lpp1 = 0.0;
13391 double lpp2 = 0.0;
13392 if (pAnchorWatchPoint1) {
13393 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13395 &lAnchorPoint1);
13396 }
13397 if (pAnchorWatchPoint2) {
13398 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13400 &lAnchorPoint2);
13401 }
13402
13403 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13404 wxPen ppPenr(GetGlobalColor("URED"), 2);
13405
13406 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13407 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13408 dc.SetBrush(*ppBrush);
13409
13410 if (lpp1 > 0) {
13411 dc.SetPen(ppPeng);
13412 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13413 }
13414
13415 if (lpp2 > 0) {
13416 dc.SetPen(ppPeng);
13417 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13418 }
13419
13420 if (lpp1 < 0) {
13421 dc.SetPen(ppPenr);
13422 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13423 }
13424
13425 if (lpp2 < 0) {
13426 dc.SetPen(ppPenr);
13427 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13428 }
13429 }
13430}
13431
13432double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13433 double lpp = 0.;
13434 wxPoint r1;
13435 wxPoint lAnchorPoint;
13436 double d1 = 0.0;
13437 double dabs;
13438 double tlat1, tlon1;
13439
13440 if (pAnchorWatchPoint) {
13441 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13442 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13443 dabs = fabs(d1 / 1852.);
13444 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13445 &tlat1, &tlon1);
13446 GetCanvasPointPix(tlat1, tlon1, &r1);
13447 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13448 &lAnchorPoint);
13449 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13450 pow((double)(lAnchorPoint.y - r1.y), 2));
13451
13452 // This is an entry watch
13453 if (d1 < 0) lpp = -lpp;
13454 }
13455 return lpp;
13456}
13457
13458//------------------------------------------------------------------------------------------
13459// Tides Support
13460//------------------------------------------------------------------------------------------
13461void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13462 if (!ptcmgr) return;
13463 if (this != wxWindow::FindFocus()) return;
13464
13465 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13466
13467 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13468 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13469 double lon = pIDX->IDX_lon;
13470 double lat = pIDX->IDX_lat;
13471
13472 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13473 if ((type == 't') || (type == 'T')) {
13474 if (BBox.Contains(lat, lon)) {
13475 // Manage the point selection list
13476 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13477 }
13478 }
13479 }
13480}
13481
13482void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13483 if (!ptcmgr) return;
13484
13485 wxDateTime this_now = gTimeSource;
13486 bool cur_time = !gTimeSource.IsValid();
13487 if (cur_time) this_now = wxDateTime::Now();
13488 time_t t_this_now = this_now.GetTicks();
13489
13490 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13491 wxPENSTYLE_SOLID);
13492 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13493 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13494 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13495 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13496
13497 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13498 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13499 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13500 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13501 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13502 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13503
13504 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13505 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13506 int font_size = wxMax(10, dFont->GetPointSize());
13507 font_size /= g_Platform->GetDisplayDIPMult(this);
13508 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13509 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13510 false, dFont->GetFaceName());
13511
13512 dc.SetPen(*pblack_pen);
13513 dc.SetBrush(*pgreen_brush);
13514
13515 wxBitmap bm;
13516 switch (m_cs) {
13517 case GLOBAL_COLOR_SCHEME_DAY:
13518 bm = m_bmTideDay;
13519 break;
13520 case GLOBAL_COLOR_SCHEME_DUSK:
13521 bm = m_bmTideDusk;
13522 break;
13523 case GLOBAL_COLOR_SCHEME_NIGHT:
13524 bm = m_bmTideNight;
13525 break;
13526 default:
13527 bm = m_bmTideDay;
13528 break;
13529 }
13530
13531 int bmw = bm.GetWidth();
13532 int bmh = bm.GetHeight();
13533
13534 float scale_factor = 1.0;
13535
13536 // Set the onscreen size of the symbol
13537 // Compensate for various display resolutions
13538 float icon_pixelRefDim = 45;
13539
13540 // Tidal report graphic is scaled by the text size of the label in use
13541 wxScreenDC sdc;
13542 int height;
13543 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13544 height *= g_Platform->GetDisplayDIPMult(this);
13545 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13546
13547 scale_factor *= pix_factor;
13548
13549 float user_scale_factor = g_ChartScaleFactorExp;
13550 if (g_ChartScaleFactorExp > 1.0)
13551 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13552 1.2; // soften the scale factor a bit
13553
13554 scale_factor *= user_scale_factor;
13555 scale_factor *= GetContentScaleFactor();
13556
13557 {
13558 double marge = 0.05;
13559 std::vector<LLBBox> drawn_boxes;
13560 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13561 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13562
13563 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13564 if ((type == 't') || (type == 'T')) // only Tides
13565 {
13566 double lon = pIDX->IDX_lon;
13567 double lat = pIDX->IDX_lat;
13568
13569 if (BBox.ContainsMarge(lat, lon, marge)) {
13570 // Avoid drawing detailed graphic for duplicate tide stations
13571 if (GetVP().chart_scale < 500000) {
13572 bool bdrawn = false;
13573 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13574 if (drawn_boxes[i].Contains(lat, lon)) {
13575 bdrawn = true;
13576 break;
13577 }
13578 }
13579 if (bdrawn) continue; // the station loop
13580
13581 LLBBox this_box;
13582 this_box.Set(lat, lon, lat, lon);
13583 this_box.EnLarge(.005);
13584 drawn_boxes.push_back(this_box);
13585 }
13586
13587 wxPoint r;
13588 GetCanvasPointPix(lat, lon, &r);
13589 // draw standard icons
13590 if (GetVP().chart_scale > 500000) {
13591 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13592 }
13593 // draw "extended" icons
13594 else {
13595 dc.SetFont(*plabelFont);
13596 {
13597 {
13598 float val, nowlev;
13599 float ltleve = 0.;
13600 float htleve = 0.;
13601 time_t tctime;
13602 time_t lttime = 0;
13603 time_t httime = 0;
13604 bool wt;
13605 // define if flood or ebb in the last ten minutes and verify if
13606 // data are useable
13607 if (ptcmgr->GetTideFlowSens(
13608 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13609 pIDX->IDX_rec_num, nowlev, val, wt)) {
13610 // search forward the first HW or LW near "now" ( starting at
13611 // "now" - ten minutes )
13612 ptcmgr->GetHightOrLowTide(
13613 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13614 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13615 wt, pIDX->IDX_rec_num, val, tctime);
13616 if (wt) {
13617 httime = tctime;
13618 htleve = val;
13619 } else {
13620 lttime = tctime;
13621 ltleve = val;
13622 }
13623 wt = !wt;
13624
13625 // then search opposite tide near "now"
13626 if (tctime > t_this_now) // search backward
13627 ptcmgr->GetHightOrLowTide(
13628 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13629 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13630 pIDX->IDX_rec_num, val, tctime);
13631 else
13632 // or search forward
13633 ptcmgr->GetHightOrLowTide(
13634 t_this_now, FORWARD_TEN_MINUTES_STEP,
13635 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13636 val, tctime);
13637 if (wt) {
13638 httime = tctime;
13639 htleve = val;
13640 } else {
13641 lttime = tctime;
13642 ltleve = val;
13643 }
13644
13645 // draw the tide rectangle:
13646
13647 // tide icon rectangle has default pre-scaled width = 12 ,
13648 // height = 45
13649 int width = (int)(12 * scale_factor + 0.5);
13650 int height = (int)(45 * scale_factor + 0.5);
13651 int linew = wxMax(1, (int)(scale_factor));
13652 int xDraw = r.x - (width / 2);
13653 int yDraw = r.y - (height / 2);
13654
13655 // process tide state ( %height and flow sens )
13656 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13657 int hs = (httime > lttime) ? -4 : 4;
13658 hs *= (int)(scale_factor + 0.5);
13659 if (ts > 0.995 || ts < 0.005) hs = 0;
13660 int ht_y = (int)(height * ts);
13661
13662 // draw yellow tide rectangle outlined in black
13663 pblack_pen->SetWidth(linew);
13664 dc.SetPen(*pblack_pen);
13665 dc.SetBrush(*pyelo_brush);
13666 dc.DrawRectangle(xDraw, yDraw, width, height);
13667
13668 // draw blue rectangle as water height, smaller in width than
13669 // yellow rectangle
13670 dc.SetPen(*pblue_pen);
13671 dc.SetBrush(*pblue_brush);
13672 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13673 (width - (4 * linew)), height - ht_y);
13674
13675 // draw sens arrows (ensure they are not "under-drawn" by top
13676 // line of blue rectangle )
13677 int hl;
13678 wxPoint arrow[3];
13679 arrow[0].x = xDraw + 2 * linew;
13680 arrow[1].x = xDraw + width / 2;
13681 arrow[2].x = xDraw + width - 2 * linew;
13682 pyelo_pen->SetWidth(linew);
13683 pblue_pen->SetWidth(linew);
13684 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13685 {
13686 hl = (int)(height * 0.25) + yDraw;
13687 arrow[0].y = hl;
13688 arrow[1].y = hl + hs;
13689 arrow[2].y = hl;
13690 if (ts < 0.15)
13691 dc.SetPen(*pyelo_pen);
13692 else
13693 dc.SetPen(*pblue_pen);
13694 dc.DrawLines(3, arrow);
13695 }
13696 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13697 {
13698 hl = (int)(height * 0.5) + yDraw;
13699 arrow[0].y = hl;
13700 arrow[1].y = hl + hs;
13701 arrow[2].y = hl;
13702 if (ts < 0.40)
13703 dc.SetPen(*pyelo_pen);
13704 else
13705 dc.SetPen(*pblue_pen);
13706 dc.DrawLines(3, arrow);
13707 }
13708 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13709 {
13710 hl = (int)(height * 0.75) + yDraw;
13711 arrow[0].y = hl;
13712 arrow[1].y = hl + hs;
13713 arrow[2].y = hl;
13714 if (ts < 0.65)
13715 dc.SetPen(*pyelo_pen);
13716 else
13717 dc.SetPen(*pblue_pen);
13718 dc.DrawLines(3, arrow);
13719 }
13720 // Convert nowlev to preferred height units (it comes from
13721 // GetHightOrLowTide in station units)
13722 double nowlev_converted = nowlev;
13723 Station_Data *pmsd = pIDX->pref_sta_data;
13724 if (pmsd) {
13725 // Convert from station units to meters first
13726 int unit_c = TCDataFactory::findunit(pmsd->unit);
13727 if (unit_c >= 0) {
13728 nowlev_converted =
13729 nowlev_converted *
13730 TCDataFactory::known_units[unit_c].conv_factor;
13731 }
13732 // Now convert from meters to preferred height units
13733 nowlev_converted = toUsrHeight(nowlev_converted);
13734 }
13735 // draw tide level text
13736 wxString s;
13737 s.Printf("%3.1f", nowlev_converted);
13738 // Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13739 if (pmsd) s.Append(getUsrHeightUnit());
13740 int wx1;
13741 dc.GetTextExtent(s, &wx1, NULL);
13742 wx1 *= g_Platform->GetDisplayDIPMult(this);
13743 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13744 }
13745 }
13746 }
13747 }
13748 }
13749 }
13750 }
13751 }
13752}
13753
13754//------------------------------------------------------------------------------------------
13755// Currents Support
13756//------------------------------------------------------------------------------------------
13757
13758void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13759 if (!ptcmgr) return;
13760
13761 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13762
13763 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13764 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13765 double lon = pIDX->IDX_lon;
13766 double lat = pIDX->IDX_lat;
13767
13768 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13769 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13770 if ((BBox.Contains(lat, lon))) {
13771 // Manage the point selection list
13772 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13773 }
13774 }
13775 }
13776}
13777
13778void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13779 if (!ptcmgr) return;
13780
13781 float tcvalue, dir;
13782 bool bnew_val;
13783 char sbuf[20];
13784 wxFont *pTCFont;
13785 double lon_last = 0.;
13786 double lat_last = 0.;
13787 // arrow size for Raz Blanchard : 12 knots north
13788 double marge = 0.2;
13789 bool cur_time = !gTimeSource.IsValid();
13790
13791 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13792 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13793
13794 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13795 wxPENSTYLE_SOLID);
13796 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13797 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13798 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13799 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13800 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13801 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13802 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13803 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13804
13805 double skew_angle = GetVPRotation();
13806
13807 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13808 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13809 int font_size = wxMax(10, dFont->GetPointSize());
13810 font_size /= g_Platform->GetDisplayDIPMult(this);
13811 pTCFont = FontMgr::Get().FindOrCreateFont(
13812 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13813 false, dFont->GetFaceName());
13814
13815 float scale_factor = 1.0;
13816
13817 // Set the onscreen size of the symbol
13818 // Current report graphic is scaled by the text size of the label in use
13819 wxScreenDC sdc;
13820 int height;
13821 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13822 height *= g_Platform->GetDisplayDIPMult(this);
13823 float nominal_icon_size_pixels = 15;
13824 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13825
13826 scale_factor *= pix_factor;
13827
13828 float user_scale_factor = g_ChartScaleFactorExp;
13829 if (g_ChartScaleFactorExp > 1.0)
13830 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13831 1.2; // soften the scale factor a bit
13832
13833 scale_factor *= user_scale_factor;
13834
13835 scale_factor *= GetContentScaleFactor();
13836
13837 {
13838 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13839 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13840 double lon = pIDX->IDX_lon;
13841 double lat = pIDX->IDX_lat;
13842
13843 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13844 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13845 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13846 wxPoint r;
13847 GetCanvasPointPix(lat, lon, &r);
13848
13849 wxPoint d[4]; // points of a diamond at the current station location
13850 int dd = (int)(5.0 * scale_factor + 0.5);
13851 d[0].x = r.x;
13852 d[0].y = r.y + dd;
13853 d[1].x = r.x + dd;
13854 d[1].y = r.y;
13855 d[2].x = r.x;
13856 d[2].y = r.y - dd;
13857 d[3].x = r.x - dd;
13858 d[3].y = r.y;
13859
13860 if (1) {
13861 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13862 dc.SetPen(*pblack_pen);
13863 dc.SetBrush(*porange_brush);
13864 dc.DrawPolygon(4, d);
13865
13866 if (type == 'C') {
13867 dc.SetBrush(*pblack_brush);
13868 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13869 }
13870
13871 if (GetVP().chart_scale < 1000000) {
13872 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13873 continue;
13874 } else
13875 continue;
13876
13877 if (1 /*type == 'c'*/) {
13878 {
13879 // Get the display pixel location of the current station
13880 int pixxc, pixyc;
13881 pixxc = r.x;
13882 pixyc = r.y;
13883
13884 // Adjust drawing size using logarithmic scale. tcvalue is
13885 // current in knots
13886 double a1 = fabs(tcvalue) * 10.;
13887 // Current values <= 0.1 knot will have no arrow
13888 a1 = wxMax(1.0, a1);
13889 double a2 = log10(a1);
13890
13891 float cscale = scale_factor * a2 * 0.3;
13892
13893 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13894 dc.SetPen(*porange_pen);
13895 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13896 cscale);
13897 // Draw text, if enabled
13898
13899 if (bDrawCurrentValues) {
13900 dc.SetFont(*pTCFont);
13901 tcvalue = toUsrSpeed(tcvalue);
13902 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13903 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13904 }
13905 }
13906 } // scale
13907 }
13908 /* This is useful for debugging the TC database
13909 else
13910 {
13911 dc.SetPen ( *porange_pen );
13912 dc.SetBrush ( *pgray_brush );
13913 dc.DrawPolygon ( 4, d );
13914 }
13915 */
13916 }
13917 lon_last = lon;
13918 lat_last = lat;
13919 }
13920 }
13921 }
13922}
13923
13924void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13925 ShowSingleTideDialog(x, y, pvIDX);
13926}
13927
13928void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13929 if (!pvIDX) return; // Validate input
13930
13931 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13932
13933 // Check if a tide dialog is already open and visible
13934 if (pCwin && pCwin->IsShown()) {
13935 // Same tide station: bring existing dialog to front (preserves user
13936 // context)
13937 if (pCwin->GetCurrentIDX() == pNewIDX) {
13938 pCwin->Raise();
13939 pCwin->SetFocus();
13940
13941 // Provide subtle visual feedback that dialog is already open
13942 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13943 return;
13944 }
13945
13946 // Different tide station: close current dialog before opening new one
13947 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13948 }
13949
13950 if (pCwin) {
13951 // This shouldn't happen but ensures clean state
13952 pCwin->Destroy();
13953 pCwin = NULL;
13954 }
13955
13956 // Create and display new tide dialog
13957 pCwin = new TCWin(this, x, y, pvIDX);
13958
13959 // Ensure the dialog is properly shown and focused
13960 if (pCwin) {
13961 pCwin->Show();
13962 pCwin->Raise();
13963 pCwin->SetFocus();
13964 }
13965}
13966
13967bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13968
13970 if (pCwin) {
13971 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13972 }
13973}
13974
13975#define NUM_CURRENT_ARROW_POINTS 9
13976static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13977 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13978 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13979 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13980
13981void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13982 double scale) {
13983 if (scale > 1e-2) {
13984 float sin_rot = sin(rot_angle * PI / 180.);
13985 float cos_rot = cos(rot_angle * PI / 180.);
13986
13987 // Move to the first point
13988
13989 float xt = CurrentArrowArray[0].x;
13990 float yt = CurrentArrowArray[0].y;
13991
13992 float xp = (xt * cos_rot) - (yt * sin_rot);
13993 float yp = (xt * sin_rot) + (yt * cos_rot);
13994 int x1 = (int)(xp * scale);
13995 int y1 = (int)(yp * scale);
13996
13997 // Walk thru the point list
13998 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13999 xt = CurrentArrowArray[ip].x;
14000 yt = CurrentArrowArray[ip].y;
14001
14002 float xp = (xt * cos_rot) - (yt * sin_rot);
14003 float yp = (xt * sin_rot) + (yt * cos_rot);
14004 int x2 = (int)(xp * scale);
14005 int y2 = (int)(yp * scale);
14006
14007 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
14008
14009 x1 = x2;
14010 y1 = y2;
14011 }
14012 }
14013}
14014
14015wxString ChartCanvas::FindValidUploadPort() {
14016 wxString port;
14017 // Try to use the saved persistent upload port first
14018 if (!g_uploadConnection.IsEmpty() &&
14019 g_uploadConnection.StartsWith("Serial")) {
14020 port = g_uploadConnection;
14021 }
14022
14023 else {
14024 // If there is no persistent upload port recorded (yet)
14025 // then use the first available serial connection which has output defined.
14026 for (auto *cp : TheConnectionParams()) {
14027 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
14028 port << "Serial:" << cp->Port;
14029 }
14030 }
14031 return port;
14032}
14033
14034void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
14035 if (!win) return;
14036
14037 if (NULL == g_pais_query_dialog_active) {
14038 int pos_x = g_ais_query_dialog_x;
14039 int pos_y = g_ais_query_dialog_y;
14040
14041 if (g_pais_query_dialog_active) {
14042 g_pais_query_dialog_active->Destroy();
14043 g_pais_query_dialog_active = new AISTargetQueryDialog();
14044 } else {
14045 g_pais_query_dialog_active = new AISTargetQueryDialog();
14046 }
14047
14048 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
14049 wxPoint(pos_x, pos_y));
14050
14051 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
14052 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
14053 g_pais_query_dialog_active->SetMMSI(mmsi);
14054 g_pais_query_dialog_active->UpdateText();
14055 wxSize sz = g_pais_query_dialog_active->GetSize();
14056
14057 bool b_reset_pos = false;
14058#ifdef __WXMSW__
14059 // Support MultiMonitor setups which an allow negative window positions.
14060 // If the requested window title bar does not intersect any installed
14061 // monitor, then default to simple primary monitor positioning.
14062 RECT frame_title_rect;
14063 frame_title_rect.left = pos_x;
14064 frame_title_rect.top = pos_y;
14065 frame_title_rect.right = pos_x + sz.x;
14066 frame_title_rect.bottom = pos_y + 30;
14067
14068 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
14069 b_reset_pos = true;
14070#else
14071
14072 // Make sure drag bar (title bar) of window intersects wxClient Area of
14073 // screen, with a little slop...
14074 wxRect window_title_rect; // conservative estimate
14075 window_title_rect.x = pos_x;
14076 window_title_rect.y = pos_y;
14077 window_title_rect.width = sz.x;
14078 window_title_rect.height = 30;
14079
14080 wxRect ClientRect = wxGetClientDisplayRect();
14081 ClientRect.Deflate(
14082 60, 60); // Prevent the new window from being too close to the edge
14083 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
14084
14085#endif
14086
14087 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
14088
14089 } else {
14090 g_pais_query_dialog_active->SetMMSI(mmsi);
14091 g_pais_query_dialog_active->UpdateText();
14092 }
14093
14094 g_pais_query_dialog_active->Show();
14095}
14096
14097void ChartCanvas::ToggleCanvasQuiltMode() {
14098 bool cur_mode = GetQuiltMode();
14099
14100 if (!GetQuiltMode())
14101 SetQuiltMode(true);
14102 else if (GetQuiltMode()) {
14103 SetQuiltMode(false);
14104 g_sticky_chart = GetQuiltReferenceChartIndex();
14105 }
14106
14107 if (cur_mode != GetQuiltMode()) {
14108 SetupCanvasQuiltMode();
14109 DoCanvasUpdate();
14110 InvalidateGL();
14111 Refresh();
14112 }
14113 // TODO What to do about this?
14114 // g_bQuiltEnable = GetQuiltMode();
14115
14116 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
14117 if (ps52plib) ps52plib->GenerateStateHash();
14118
14119 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14120 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14121}
14122
14123void ChartCanvas::DoCanvasStackDelta(int direction) {
14124 if (!GetQuiltMode()) {
14125 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
14126 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
14127 if ((current_stack_index + direction) < 0) return;
14128
14129 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
14130 int new_dbIndex =
14131 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
14132
14133 if (IsChartQuiltableRef(new_dbIndex)) {
14134 ToggleCanvasQuiltMode();
14135 SelectQuiltRefdbChart(new_dbIndex);
14136 m_bpersistent_quilt = false;
14137 }
14138 } else {
14139 SelectChartFromStack(current_stack_index + direction);
14140 }
14141 } else {
14142 std::vector<int> piano_chart_index_array =
14143 GetQuiltExtendedStackdbIndexArray();
14144 int refdb = GetQuiltRefChartdbIndex();
14145
14146 // Find the ref chart in the stack
14147 int current_index = -1;
14148 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14149 if (refdb == piano_chart_index_array[i]) {
14150 current_index = i;
14151 break;
14152 }
14153 }
14154 if (current_index == -1) return;
14155
14156 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14157 int target_family = ctet.GetChartFamily();
14158
14159 int new_index = -1;
14160 int check_index = current_index + direction;
14161 bool found = false;
14162 int check_dbIndex = -1;
14163 int new_dbIndex = -1;
14164
14165 // When quilted. switch within the same chart family
14166 while (!found &&
14167 (unsigned int)check_index < piano_chart_index_array.size() &&
14168 (check_index >= 0)) {
14169 check_dbIndex = piano_chart_index_array[check_index];
14170 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14171 if (target_family == cte.GetChartFamily()) {
14172 found = true;
14173 new_index = check_index;
14174 new_dbIndex = check_dbIndex;
14175 break;
14176 }
14177
14178 check_index += direction;
14179 }
14180
14181 if (!found) return;
14182
14183 if (!IsChartQuiltableRef(new_dbIndex)) {
14184 ToggleCanvasQuiltMode();
14185 SelectdbChart(new_dbIndex);
14186 m_bpersistent_quilt = true;
14187 } else {
14188 SelectQuiltRefChart(new_index);
14189 }
14190 }
14191
14192 // update the state of the menu items (checkmarks etc)
14193 top_frame::Get()->UpdateGlobalMenuItems();
14194 SetQuiltChartHiLiteIndex(-1);
14195
14196 ReloadVP();
14197}
14198
14199//--------------------------------------------------------------------------------------------------------
14200//
14201// Toolbar support
14202//
14203//--------------------------------------------------------------------------------------------------------
14204
14205void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14206 // Handle the per-canvas toolbar clicks here
14207
14208 switch (event.GetId()) {
14209 case ID_ZOOMIN: {
14210 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14211 break;
14212 }
14213
14214 case ID_ZOOMOUT: {
14215 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14216 break;
14217 }
14218
14219 case ID_STKUP:
14220 DoCanvasStackDelta(1);
14221 DoCanvasUpdate();
14222 break;
14223
14224 case ID_STKDN:
14225 DoCanvasStackDelta(-1);
14226 DoCanvasUpdate();
14227 break;
14228
14229 case ID_FOLLOW: {
14230 TogglebFollow();
14231 break;
14232 }
14233
14234 case ID_CURRENT: {
14235 ShowCurrents(!GetbShowCurrent());
14236 ReloadVP();
14237 Refresh(false);
14238 break;
14239 }
14240
14241 case ID_TIDE: {
14242 ShowTides(!GetbShowTide());
14243 ReloadVP();
14244 Refresh(false);
14245 break;
14246 }
14247
14248 case ID_ROUTE: {
14249 if (0 == m_routeState) {
14250 StartRoute();
14251 } else {
14252 FinishRoute();
14253 }
14254
14255#ifdef __ANDROID__
14256 androidSetRouteAnnunciator(m_routeState == 1);
14257#endif
14258 break;
14259 }
14260
14261 case ID_AIS: {
14262 SetAISCanvasDisplayStyle(-1);
14263 break;
14264 }
14265
14266 default:
14267 break;
14268 }
14269
14270 // And then let gFrame handle the rest....
14271 event.Skip();
14272}
14273
14274void ChartCanvas::SetShowAIS(bool show) {
14275 m_bShowAIS = show;
14276 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14277 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14278}
14279
14280void ChartCanvas::SetAttenAIS(bool show) {
14281 m_bShowAISScaled = show;
14282 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14283 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14284}
14285
14286void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14287 // make some arrays to hold the dfferences between cycle steps
14288 // show all, scaled, hide all
14289 bool bShowAIS_Array[3] = {true, true, false};
14290 bool bShowScaled_Array[3] = {false, true, true};
14291 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14292 _("Attenuate less critical AIS targets"),
14293 _("Hide AIS Targets")};
14294 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14295 int ArraySize = 3;
14296 int AIS_Toolbar_Switch = 0;
14297 if (StyleIndx == -1) { // -1 means coming from toolbar button
14298 // find current state of switch
14299 for (int i = 1; i < ArraySize; i++) {
14300 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14301 (bShowScaled_Array[i] == m_bShowAISScaled))
14302 AIS_Toolbar_Switch = i;
14303 }
14304 AIS_Toolbar_Switch++; // we did click so continu with next item
14305 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14306 AIS_Toolbar_Switch++;
14307
14308 } else { // coming from menu bar.
14309 AIS_Toolbar_Switch = StyleIndx;
14310 }
14311 // make sure we are not above array
14312 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14313
14314 int AIS_Toolbar_Switch_Next =
14315 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14316 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14317 AIS_Toolbar_Switch_Next++;
14318 if (AIS_Toolbar_Switch_Next >= ArraySize)
14319 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14320
14321 // Set found values to global and member variables
14322 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14323 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14324}
14325
14326void ChartCanvas::TouchAISToolActive() {}
14327
14328void ChartCanvas::UpdateAISTBTool() {}
14329
14330//---------------------------------------------------------------------------------
14331//
14332// Compass/GPS status icon support
14333//
14334//---------------------------------------------------------------------------------
14335
14336void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14337 // Look for change in overlap or positions
14338 bool b_update = false;
14339 int cc1_edge_comp = 2;
14340 wxRect rect = m_Compass->GetRect();
14341 wxSize parent_size = GetSize();
14342
14343 parent_size *= m_displayScale;
14344
14345 // check to see if it would overlap if it was in its home position (upper
14346 // right)
14347 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14348 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14349 wxRect compass_rect(compass_pt, rect.GetSize());
14350
14351 m_Compass->Move(compass_pt);
14352
14353 if (m_Compass && m_Compass->IsShown())
14354 m_Compass->UpdateStatus(b_force_new | b_update);
14355
14356 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14357 scaler = wxMax(scaler, 1.0);
14358 wxPoint note_point = wxPoint(
14359 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14360 if (m_notification_button) {
14361 m_notification_button->Move(note_point);
14362 m_notification_button->UpdateStatus();
14363 }
14364
14365 if (b_force_new | b_update) Refresh();
14366}
14367
14368void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14369 ChartTypeEnum New_Type,
14370 ChartFamilyEnum New_Family) {
14371 if (!GetpCurrentStack()) return;
14372 if (!ChartData) return;
14373
14374 if (index < GetpCurrentStack()->nEntry) {
14375 // Open the new chart
14376 ChartBase *pTentative_Chart;
14377 pTentative_Chart = ChartData->OpenStackChartConditional(
14378 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14379
14380 if (pTentative_Chart) {
14381 if (m_singleChart) m_singleChart->Deactivate();
14382
14383 m_singleChart = pTentative_Chart;
14384 m_singleChart->Activate();
14385
14386 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14387 GetpCurrentStack(), m_singleChart->GetFullPath());
14388 }
14389
14390 // Setup the view
14391 double zLat, zLon;
14392 if (m_bFollow) {
14393 zLat = gLat;
14394 zLon = gLon;
14395 } else {
14396 zLat = m_vLat;
14397 zLon = m_vLon;
14398 }
14399
14400 double best_scale_ppm = GetBestVPScale(m_singleChart);
14401 double rotation = GetVPRotation();
14402 double oldskew = GetVPSkew();
14403 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14404
14405 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14406 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14407 if (fabs(newskew) > 0.0001) rotation = newskew;
14408 }
14409
14410 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14411
14412 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14413 }
14414
14415 // refresh Piano
14416 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14417 if (idx < 0) return;
14418
14419 std::vector<int> piano_active_chart_index_array;
14420 piano_active_chart_index_array.push_back(
14421 GetpCurrentStack()->GetCurrentEntrydbIndex());
14422 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14423}
14424
14425void ChartCanvas::SelectdbChart(int dbindex) {
14426 if (!GetpCurrentStack()) return;
14427 if (!ChartData) return;
14428
14429 if (dbindex >= 0) {
14430 // Open the new chart
14431 ChartBase *pTentative_Chart;
14432 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14433
14434 if (pTentative_Chart) {
14435 if (m_singleChart) m_singleChart->Deactivate();
14436
14437 m_singleChart = pTentative_Chart;
14438 m_singleChart->Activate();
14439
14440 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14441 GetpCurrentStack(), m_singleChart->GetFullPath());
14442 }
14443
14444 // Setup the view
14445 double zLat, zLon;
14446 if (m_bFollow) {
14447 zLat = gLat;
14448 zLon = gLon;
14449 } else {
14450 zLat = m_vLat;
14451 zLon = m_vLon;
14452 }
14453
14454 double best_scale_ppm = GetBestVPScale(m_singleChart);
14455
14456 if (m_singleChart)
14457 SetViewPoint(zLat, zLon, best_scale_ppm,
14458 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14459
14460 // SetChartUpdatePeriod( );
14461
14462 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14463 }
14464
14465 // TODO refresh_Piano();
14466}
14467
14468void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14469 double target_scale = GetVP().view_scale_ppm;
14470
14471 if (!GetQuiltMode()) {
14472 if (GetpCurrentStack()) {
14473 int stack_index = -1;
14474 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14475 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14476 if (check_dbIndex < 0) continue;
14477 const ChartTableEntry &cte =
14478 ChartData->GetChartTableEntry(check_dbIndex);
14479 if (type == cte.GetChartType()) {
14480 stack_index = i;
14481 break;
14482 } else if (family == cte.GetChartFamily()) {
14483 stack_index = i;
14484 break;
14485 }
14486 }
14487
14488 if (stack_index >= 0) {
14489 SelectChartFromStack(stack_index);
14490 }
14491 }
14492 } else {
14493 int sel_dbIndex = -1;
14494 std::vector<int> piano_chart_index_array =
14495 GetQuiltExtendedStackdbIndexArray();
14496 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14497 int check_dbIndex = piano_chart_index_array[i];
14498 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14499 if (type == cte.GetChartType()) {
14500 if (IsChartQuiltableRef(check_dbIndex)) {
14501 sel_dbIndex = check_dbIndex;
14502 break;
14503 }
14504 } else if (family == cte.GetChartFamily()) {
14505 if (IsChartQuiltableRef(check_dbIndex)) {
14506 sel_dbIndex = check_dbIndex;
14507 break;
14508 }
14509 }
14510 }
14511
14512 if (sel_dbIndex >= 0) {
14513 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14514 // Re-qualify the quilt reference chart selection
14515 AdjustQuiltRefChart();
14516 }
14517
14518 // Now reset the scale to the target...
14519 SetVPScale(target_scale);
14520 }
14521
14522 SetQuiltChartHiLiteIndex(-1);
14523
14524 ReloadVP();
14525}
14526
14527bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14528 return std::find(m_tile_yesshow_index_array.begin(),
14529 m_tile_yesshow_index_array.end(),
14530 index) != m_tile_yesshow_index_array.end();
14531}
14532
14533bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14534 return std::find(m_tile_noshow_index_array.begin(),
14535 m_tile_noshow_index_array.end(),
14536 index) != m_tile_noshow_index_array.end();
14537}
14538
14539void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14540 if (std::find(m_tile_noshow_index_array.begin(),
14541 m_tile_noshow_index_array.end(),
14542 index) == m_tile_noshow_index_array.end()) {
14543 m_tile_noshow_index_array.push_back(index);
14544 }
14545}
14546
14547//-------------------------------------------------------------------------------------------------------
14548//
14549// Piano support
14550//
14551//-------------------------------------------------------------------------------------------------------
14552
14553void ChartCanvas::HandlePianoClick(
14554 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14555 if (g_options && g_options->IsShown())
14556 return; // Piano might be invalid due to chartset updates.
14557 if (!m_pCurrentStack) return;
14558 if (!ChartData) return;
14559
14560 // stop movement or on slow computer we may get something like :
14561 // zoom out with the wheel (timer is set)
14562 // quickly click and display a chart, which may zoom in
14563 // but the delayed timer fires first and it zooms out again!
14564 StopMovement();
14565
14566 // When switching by piano key click, we may appoint the new target chart to
14567 // be any chart in the composite array.
14568 // As an improvement to UX, find the chart that is "closest" to the current
14569 // vp,
14570 // and select that chart. This will cause a jump to the centroid of that
14571 // chart
14572
14573 double distance = 25000; // RTW
14574 int closest_index = -1;
14575 for (int chart_index : selected_dbIndex_array) {
14576 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14577 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14578 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14579
14580 // measure distance as Manhattan style
14581 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14582 if (test_distance < distance) {
14583 distance = test_distance;
14584 closest_index = chart_index;
14585 }
14586 }
14587
14588 int selected_dbIndex = selected_dbIndex_array[0];
14589 if (closest_index >= 0) selected_dbIndex = closest_index;
14590
14591 if (!GetQuiltMode()) {
14592 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14593 if (IsChartQuiltableRef(selected_dbIndex)) {
14594 ToggleCanvasQuiltMode();
14595 SelectQuiltRefdbChart(selected_dbIndex);
14596 m_bpersistent_quilt = false;
14597 } else {
14598 SelectChartFromStack(selected_index);
14599 }
14600 } else {
14601 SelectChartFromStack(selected_index);
14602 g_sticky_chart = selected_dbIndex;
14603 }
14604
14605 if (m_singleChart)
14606 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14607 } else {
14608 // Handle MBTiles overlays first
14609 // Left click simply toggles the noshow array index entry
14610 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14611 bool bfound = false;
14612 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14613 if (m_tile_noshow_index_array[i] ==
14614 selected_dbIndex) { // chart is in the noshow list
14615 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14616 i); // erase it
14617 bfound = true;
14618 break;
14619 }
14620 }
14621 if (!bfound) {
14622 m_tile_noshow_index_array.push_back(selected_dbIndex);
14623 }
14624
14625 // If not already present, add this tileset to the "yes_show" array.
14626 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14627 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14628 }
14629
14630 else {
14631 if (IsChartQuiltableRef(selected_dbIndex)) {
14632 // if( ChartData ) ChartData->PurgeCache();
14633
14634 // If the chart is a vector chart, and of very large scale,
14635 // then we had better set the new scale directly to avoid excessive
14636 // underzoom on, eg, Inland ENCs
14637 bool set_scale = false;
14638 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14639 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14640 set_scale = true;
14641 }
14642 }
14643
14644 if (!set_scale) {
14645 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14646 } else {
14647 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14648
14649 // Adjust scale so that the selected chart is underzoomed/overzoomed
14650 // by a controlled amount
14651 ChartBase *pc =
14652 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14653 if (pc) {
14654 double proposed_scale_onscreen =
14656
14657 if (g_bPreserveScaleOnX) {
14658 proposed_scale_onscreen =
14659 wxMin(proposed_scale_onscreen,
14660 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14661 GetCanvasWidth()));
14662 } else {
14663 proposed_scale_onscreen =
14664 wxMin(proposed_scale_onscreen,
14665 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14666 GetCanvasWidth()));
14667
14668 proposed_scale_onscreen =
14669 wxMax(proposed_scale_onscreen,
14670 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14672 }
14673
14674 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14675 }
14676 }
14677 } else {
14678 ToggleCanvasQuiltMode();
14679 SelectdbChart(selected_dbIndex);
14680 m_bpersistent_quilt = true;
14681 }
14682 }
14683 }
14684
14685 SetQuiltChartHiLiteIndex(-1);
14686 // update the state of the menu items (checkmarks etc)
14687 top_frame::Get()->UpdateGlobalMenuItems();
14688 HideChartInfoWindow();
14689 DoCanvasUpdate();
14690 ReloadVP(); // Pick up the new selections
14691}
14692
14693void ChartCanvas::HandlePianoRClick(
14694 int x, int y, int selected_index,
14695 const std::vector<int> &selected_dbIndex_array) {
14696 if (g_options && g_options->IsShown())
14697 return; // Piano might be invalid due to chartset updates.
14698 if (!GetpCurrentStack()) return;
14699
14700 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14701 UpdateCanvasControlBar();
14702
14703 SetQuiltChartHiLiteIndex(-1);
14704}
14705
14706void ChartCanvas::HandlePianoRollover(
14707 int selected_index, const std::vector<int> &selected_dbIndex_array,
14708 int n_charts, int scale) {
14709 if (g_options && g_options->IsShown())
14710 return; // Piano might be invalid due to chartset updates.
14711 if (!GetpCurrentStack()) return;
14712 if (!ChartData) return;
14713
14714 if (ChartData->IsBusy()) return;
14715
14716 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14717
14718 if (!GetQuiltMode()) {
14719 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14720 } else {
14721 // Select the correct vector
14722 std::vector<int> piano_chart_index_array;
14723 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14724 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14725 if ((GetpCurrentStack()->nEntry > 1) ||
14726 (piano_chart_index_array.size() >= 1)) {
14727 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14728
14729 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14730 ReloadVP(false); // no VP adjustment allowed
14731 } else if (GetpCurrentStack()->nEntry == 1) {
14732 const ChartTableEntry &cte =
14733 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14734 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14735 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14736 ReloadVP(false);
14737 } else if ((-1 == selected_index) &&
14738 (0 == selected_dbIndex_array.size())) {
14739 ShowChartInfoWindow(key_location.x, -1);
14740 }
14741 }
14742 } else {
14743 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14744
14745 if ((GetpCurrentStack()->nEntry > 1) ||
14746 (piano_chart_index_array.size() >= 1)) {
14747 if (n_charts > 1)
14748 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14749 selected_dbIndex_array);
14750 else if (n_charts == 1)
14751 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14752
14753 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14754 ReloadVP(false); // no VP adjustment allowed
14755 }
14756 }
14757 }
14758}
14759
14760void ChartCanvas::ClearPianoRollover() {
14761 ClearQuiltChartHiLiteIndexArray();
14762 ShowChartInfoWindow(0, -1);
14763 std::vector<int> vec;
14764 ShowCompositeInfoWindow(0, 0, 0, vec);
14765 ReloadVP(false);
14766}
14767
14768void ChartCanvas::UpdateCanvasControlBar() {
14769 if (m_pianoFrozen) return;
14770
14771 if (!GetpCurrentStack()) return;
14772 if (!ChartData) return;
14773 if (!g_bShowChartBar) return;
14774
14775 int sel_type = -1;
14776 int sel_family = -1;
14777
14778 std::vector<int> piano_chart_index_array;
14779 std::vector<int> empty_piano_chart_index_array;
14780
14781 wxString old_hash = m_Piano->GetStoredHash();
14782
14783 if (GetQuiltMode()) {
14784 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14785 GetQuiltFullScreendbIndexArray());
14786
14787 std::vector<int> piano_active_chart_index_array =
14788 GetQuiltCandidatedbIndexArray();
14789 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14790
14791 std::vector<int> piano_eclipsed_chart_index_array =
14792 GetQuiltEclipsedStackdbIndexArray();
14793 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14794
14795 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14796 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14797
14798 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14799 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14800 } else {
14801 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14802 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14803 // TODO refresh_Piano();
14804
14805 if (m_singleChart) {
14806 sel_type = m_singleChart->GetChartType();
14807 sel_family = m_singleChart->GetChartFamily();
14808 }
14809 }
14810
14811 // Set up the TMerc and Skew arrays
14812 std::vector<int> piano_skew_chart_index_array;
14813 std::vector<int> piano_tmerc_chart_index_array;
14814 std::vector<int> piano_poly_chart_index_array;
14815
14816 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14817 const ChartTableEntry &ctei =
14818 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14819 double skew_norm = ctei.GetChartSkew();
14820 if (skew_norm > 180.) skew_norm -= 360.;
14821
14822 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14823 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14824
14825 // Polyconic skewed charts should show as skewed
14826 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14827 if (fabs(skew_norm) > 1.)
14828 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14829 else
14830 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14831 } else if (fabs(skew_norm) > 1.)
14832 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14833 }
14834 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14835 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14836 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14837
14838 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14839 if (new_hash != old_hash) {
14840 m_Piano->FormatKeys();
14841 HideChartInfoWindow();
14842 m_Piano->ResetRollover();
14843 SetQuiltChartHiLiteIndex(-1);
14844 m_brepaint_piano = true;
14845 }
14846
14847 // Create a bitmask int that describes what Family/Type of charts are shown in
14848 // the bar, and notify the platform.
14849 int mask = 0;
14850 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14851 const ChartTableEntry &ctei =
14852 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14853 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14854 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14855 if (e == CHART_FAMILY_RASTER) mask |= 1;
14856 if (e == CHART_FAMILY_VECTOR) {
14857 if (t == CHART_TYPE_CM93COMP)
14858 mask |= 4;
14859 else
14860 mask |= 2;
14861 }
14862 }
14863
14864 wxString s_indicated;
14865 if (sel_type == CHART_TYPE_CM93COMP)
14866 s_indicated = "cm93";
14867 else {
14868 if (sel_family == CHART_FAMILY_RASTER)
14869 s_indicated = "raster";
14870 else if (sel_family == CHART_FAMILY_VECTOR)
14871 s_indicated = "vector";
14872 }
14873
14874 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14875}
14876
14877void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14878
14879void ChartCanvas::PianoPopupMenu(
14880 int x, int y, int selected_index,
14881 const std::vector<int> &selected_dbIndex_array) {
14882 if (!GetpCurrentStack()) return;
14883
14884 // No context menu if quilting is disabled
14885 if (!GetQuiltMode()) return;
14886
14887 m_piano_ctx_menu = new wxMenu();
14888
14889 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14890 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14891 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14892 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14893 } else {
14894 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14895 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14896 // wxEVT_COMMAND_MENU_SELECTED,
14897 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14898
14899 menu_selected_dbIndex = selected_dbIndex_array[0];
14900 menu_selected_index = selected_index;
14901
14902 // Search the no-show array
14903 bool b_is_in_noshow = false;
14904 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14905 if (m_quilt_noshow_index_array[i] ==
14906 menu_selected_dbIndex) // chart is in the noshow list
14907 {
14908 b_is_in_noshow = true;
14909 break;
14910 }
14911 }
14912
14913 if (b_is_in_noshow) {
14914 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14915 _("Show This Chart"));
14916 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14917 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14918 } else if (GetpCurrentStack()->nEntry > 1) {
14919 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14920 _("Hide This Chart"));
14921 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14922 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14923 }
14924 }
14925
14926 wxPoint pos = wxPoint(x, y - 30);
14927
14928 // Invoke the drop-down menu
14929 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14930 PopupMenu(m_piano_ctx_menu, pos);
14931
14932 delete m_piano_ctx_menu;
14933 m_piano_ctx_menu = NULL;
14934
14935 HideChartInfoWindow();
14936 m_Piano->ResetRollover();
14937
14938 SetQuiltChartHiLiteIndex(-1);
14939 ClearQuiltChartHiLiteIndexArray();
14940
14941 ReloadVP();
14942}
14943
14944void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14945 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14946 if (m_quilt_noshow_index_array[i] ==
14947 menu_selected_dbIndex) // chart is in the noshow list
14948 {
14949 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14950 break;
14951 }
14952 }
14953}
14954
14955void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14956 if (!GetpCurrentStack()) return;
14957 if (!ChartData) return;
14958
14959 RemoveChartFromQuilt(menu_selected_dbIndex);
14960
14961 // It could happen that the chart being disabled is the reference
14962 // chart....
14963 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14964 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14965
14966 int i = menu_selected_index + 1; // select next smaller scale chart
14967 bool b_success = false;
14968 while (i < GetpCurrentStack()->nEntry - 1) {
14969 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14970 if (type == ChartData->GetDBChartType(dbIndex)) {
14971 SelectQuiltRefChart(i);
14972 b_success = true;
14973 break;
14974 }
14975 i++;
14976 }
14977
14978 // If that did not work, try to select the next larger scale compatible
14979 // chart
14980 if (!b_success) {
14981 i = menu_selected_index - 1;
14982 while (i > 0) {
14983 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14984 if (type == ChartData->GetDBChartType(dbIndex)) {
14985 SelectQuiltRefChart(i);
14986 b_success = true;
14987 break;
14988 }
14989 i--;
14990 }
14991 }
14992 }
14993}
14994
14995void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14996 // Remove the item from the list (if it appears) to avoid multiple addition
14997 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14998 if (m_quilt_noshow_index_array[i] ==
14999 dbIndex) // chart is already in the noshow list
15000 {
15001 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
15002 break;
15003 }
15004 }
15005
15006 m_quilt_noshow_index_array.push_back(dbIndex);
15007}
15008
15009bool ChartCanvas::UpdateS52State() {
15010 bool retval = false;
15011
15012 if (ps52plib) {
15013 ps52plib->SetShowS57Text(m_encShowText);
15014 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
15015 ps52plib->m_bShowSoundg = m_encShowDepth;
15016 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
15017 ps52plib->m_bShowLdisText = m_encShowLightDesc;
15018
15019 // Lights
15020 if (!m_encShowLights) // On, going off
15021 ps52plib->AddObjNoshow("LIGHTS");
15022 else // Off, going on
15023 ps52plib->RemoveObjNoshow("LIGHTS");
15024 ps52plib->SetLightsOff(!m_encShowLights);
15025 ps52plib->m_bExtendLightSectors = true;
15026
15027 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
15028 ps52plib->SetAnchorOn(m_encShowAnchor);
15029 ps52plib->SetQualityOfData(m_encShowDataQual);
15030 }
15031
15032 return retval;
15033}
15034
15035void ChartCanvas::SetShowENCDataQual(bool show) {
15036 m_encShowDataQual = show;
15037 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15038 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15039
15040 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15041}
15042
15043void ChartCanvas::SetShowENCText(bool show) {
15044 m_encShowText = show;
15045 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15046 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15047
15048 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15049}
15050
15051void ChartCanvas::SetENCDisplayCategory(int category) {
15052 m_encDisplayCategory = category;
15053 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15054}
15055
15056void ChartCanvas::SetShowENCDepth(bool show) {
15057 m_encShowDepth = show;
15058 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15059 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15060
15061 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15062}
15063
15064void ChartCanvas::SetShowENCLightDesc(bool show) {
15065 m_encShowLightDesc = show;
15066 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15067 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15068
15069 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15070}
15071
15072void ChartCanvas::SetShowENCBuoyLabels(bool show) {
15073 m_encShowBuoyLabels = show;
15074 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15075}
15076
15077void ChartCanvas::SetShowENCLights(bool show) {
15078 m_encShowLights = show;
15079 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15080 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15081
15082 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15083}
15084
15085void ChartCanvas::SetShowENCAnchor(bool show) {
15086 m_encShowAnchor = show;
15087 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15088 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15089
15090 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15091}
15092
15093wxRect ChartCanvas::GetMUIBarRect() {
15094 wxRect rv;
15095 if (m_muiBar) {
15096 rv = m_muiBar->GetRect();
15097 }
15098
15099 return rv;
15100}
15101
15102void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
15103 if (!GetAlertString().IsEmpty()) {
15104 wxFont *pfont = wxTheFontList->FindOrCreateFont(
15105 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
15106
15107 dc.SetFont(*pfont);
15108 dc.SetPen(*wxTRANSPARENT_PEN);
15109
15110 dc.SetBrush(wxColour(243, 229, 47));
15111 int w, h;
15112 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
15113 h += 2;
15114 // int yp = vp.pix_height - 20 - h;
15115
15116 wxRect sbr = GetScaleBarRect();
15117 int xp = sbr.x + sbr.width + 10;
15118 int yp = (sbr.y + sbr.height) - h;
15119
15120 int wdraw = w + 10;
15121 dc.DrawRectangle(xp, yp, wdraw, h);
15122 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
15123 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
15124 }
15125}
15126
15127//--------------------------------------------------------------------------------------------------------
15128// Screen Brightness Control Support Routines
15129//
15130//--------------------------------------------------------------------------------------------------------
15131
15132#ifdef __UNIX__
15133#define BRIGHT_XCALIB
15134#define __OPCPN_USEICC__
15135#endif
15136
15137#ifdef __OPCPN_USEICC__
15138int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15139 double co_green, double co_blue);
15140
15141wxString temp_file_name;
15142#endif
15143
15144#if 0
15145class ocpnCurtain: public wxDialog
15146{
15147 DECLARE_CLASS( ocpnCurtain )
15148 DECLARE_EVENT_TABLE()
15149
15150public:
15151 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15152 ~ocpnCurtain( );
15153 bool ProcessEvent(wxEvent& event);
15154
15155};
15156
15157IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15158
15159BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15160END_EVENT_TABLE()
15161
15162ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15163{
15164 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15165}
15166
15167ocpnCurtain::~ocpnCurtain()
15168{
15169}
15170
15171bool ocpnCurtain::ProcessEvent(wxEvent& event)
15172{
15173 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15174 return GetParent()->GetEventHandler()->ProcessEvent(event);
15175}
15176#endif
15177
15178#ifdef _WIN32
15179#include <windows.h>
15180
15181HMODULE hGDI32DLL;
15182typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15183typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15184SetDeviceGammaRamp_ptr_type
15185 g_pSetDeviceGammaRamp; // the API entry points in the dll
15186GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15187
15188WORD *g_pSavedGammaMap;
15189
15190#endif
15191
15192int InitScreenBrightness() {
15193#ifdef _WIN32
15194#ifdef ocpnUSE_GL
15195 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15196 HDC hDC;
15197 BOOL bbr;
15198
15199 if (NULL == hGDI32DLL) {
15200 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15201
15202 if (NULL != hGDI32DLL) {
15203 // Get the entry points of the required functions
15204 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15205 hGDI32DLL, "SetDeviceGammaRamp");
15206 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15207 hGDI32DLL, "GetDeviceGammaRamp");
15208
15209 // If the functions are not found, unload the DLL and return false
15210 if ((NULL == g_pSetDeviceGammaRamp) ||
15211 (NULL == g_pGetDeviceGammaRamp)) {
15212 FreeLibrary(hGDI32DLL);
15213 hGDI32DLL = NULL;
15214 return 0;
15215 }
15216 }
15217 }
15218
15219 // Interface is ready, so....
15220 // Get some storage
15221 if (!g_pSavedGammaMap) {
15222 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15223
15224 hDC = GetDC(NULL); // Get the full screen DC
15225 bbr = g_pGetDeviceGammaRamp(
15226 hDC, g_pSavedGammaMap); // Get the existing ramp table
15227 ReleaseDC(NULL, hDC); // Release the DC
15228 }
15229
15230 // On Windows hosts, try to adjust the registry to allow full range
15231 // setting of Gamma table This is an undocumented Windows hack.....
15232 wxRegKey *pRegKey = new wxRegKey(
15233 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15234 "NT\\CurrentVersion\\ICM");
15235 if (!pRegKey->Exists()) pRegKey->Create();
15236 pRegKey->SetValue("GdiIcmGammaRange", 256);
15237
15238 g_brightness_init = true;
15239 return 1;
15240 }
15241#endif
15242
15243 {
15244 if (NULL == g_pcurtain) {
15245 if (top_frame::Get()->CanSetTransparent()) {
15246 // Build the curtain window
15247 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15248 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15249 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15250 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15251
15252 // g_pcurtain = new ocpnCurtain(gFrame,
15253 // wxPoint(0,0),::wxGetDisplaySize(),
15254 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15255 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15256
15257 g_pcurtain->Hide();
15258
15259 HWND hWnd = GetHwndOf(g_pcurtain);
15260 SetWindowLong(hWnd, GWL_EXSTYLE,
15261 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15262 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15263 g_pcurtain->SetTransparent(0);
15264
15265 g_pcurtain->Maximize();
15266 g_pcurtain->Show();
15267
15268 // All of this is obtuse, but necessary for Windows...
15269 g_pcurtain->Enable();
15270 g_pcurtain->Disable();
15271
15272 top_frame::Get()->Disable();
15273 top_frame::Get()->Enable();
15274 // SetFocus();
15275 }
15276 }
15277 g_brightness_init = true;
15278
15279 return 1;
15280 }
15281#else
15282 // Look for "xcalib" application
15283 wxString cmd("xcalib -version");
15284
15285 wxArrayString output;
15286 long r = wxExecute(cmd, output);
15287 if (0 != r)
15288 wxLogMessage(
15289 " External application \"xcalib\" not found. Screen brightness "
15290 "not changed.");
15291
15292 g_brightness_init = true;
15293 return 0;
15294#endif
15295}
15296
15297int RestoreScreenBrightness() {
15298#ifdef _WIN32
15299
15300 if (g_pSavedGammaMap) {
15301 HDC hDC = GetDC(NULL); // Get the full screen DC
15302 g_pSetDeviceGammaRamp(hDC,
15303 g_pSavedGammaMap); // Restore the saved ramp table
15304 ReleaseDC(NULL, hDC); // Release the DC
15305
15306 free(g_pSavedGammaMap);
15307 g_pSavedGammaMap = NULL;
15308 }
15309
15310 if (g_pcurtain) {
15311 g_pcurtain->Close();
15312 g_pcurtain->Destroy();
15313 g_pcurtain = NULL;
15314 }
15315
15316 g_brightness_init = false;
15317 return 1;
15318
15319#endif
15320
15321#ifdef BRIGHT_XCALIB
15322 if (g_brightness_init) {
15323 wxString cmd;
15324 cmd = "xcalib -clear";
15325 wxExecute(cmd, wxEXEC_ASYNC);
15326 g_brightness_init = false;
15327 }
15328
15329 return 1;
15330#endif
15331
15332 return 0;
15333}
15334
15335// Set brightness. [0..100]
15336int SetScreenBrightness(int brightness) {
15337#ifdef _WIN32
15338
15339 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15340 // some (most modern?) versions of gdi32.dll Load the required library dll,
15341 // if not already in place
15342#ifdef ocpnUSE_GL
15343 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15344 if (g_pcurtain) {
15345 g_pcurtain->Close();
15346 g_pcurtain->Destroy();
15347 g_pcurtain = NULL;
15348 }
15349
15350 InitScreenBrightness();
15351
15352 if (NULL == hGDI32DLL) {
15353 // Unicode stuff.....
15354 wchar_t wdll_name[80];
15355 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15356 LPCWSTR cstr = wdll_name;
15357
15358 hGDI32DLL = LoadLibrary(cstr);
15359
15360 if (NULL != hGDI32DLL) {
15361 // Get the entry points of the required functions
15362 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15363 hGDI32DLL, "SetDeviceGammaRamp");
15364 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15365 hGDI32DLL, "GetDeviceGammaRamp");
15366
15367 // If the functions are not found, unload the DLL and return false
15368 if ((NULL == g_pSetDeviceGammaRamp) ||
15369 (NULL == g_pGetDeviceGammaRamp)) {
15370 FreeLibrary(hGDI32DLL);
15371 hGDI32DLL = NULL;
15372 return 0;
15373 }
15374 }
15375 }
15376
15377 HDC hDC = GetDC(NULL); // Get the full screen DC
15378
15379 /*
15380 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15381 if (cmcap != CM_GAMMA_RAMP)
15382 {
15383 wxLogMessage(" Video hardware does not support brightness control by
15384 gamma ramp adjustment."); return false;
15385 }
15386 */
15387
15388 int increment = brightness * 256 / 100;
15389
15390 // Build the Gamma Ramp table
15391 WORD GammaTable[3][256];
15392
15393 int table_val = 0;
15394 for (int i = 0; i < 256; i++) {
15395 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15396 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15397 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15398
15399 table_val += increment;
15400
15401 if (table_val > 65535) table_val = 65535;
15402 }
15403
15404 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15405 ReleaseDC(NULL, hDC); // Release the DC
15406
15407 return 1;
15408 }
15409#endif
15410
15411 {
15412 if (g_pSavedGammaMap) {
15413 HDC hDC = GetDC(NULL); // Get the full screen DC
15414 g_pSetDeviceGammaRamp(hDC,
15415 g_pSavedGammaMap); // Restore the saved ramp table
15416 ReleaseDC(NULL, hDC); // Release the DC
15417 }
15418
15419 if (brightness < 100) {
15420 if (NULL == g_pcurtain) InitScreenBrightness();
15421
15422 if (g_pcurtain) {
15423 int sbrite = wxMax(1, brightness);
15424 sbrite = wxMin(100, sbrite);
15425
15426 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15427 }
15428 } else {
15429 if (g_pcurtain) {
15430 g_pcurtain->Close();
15431 g_pcurtain->Destroy();
15432 g_pcurtain = NULL;
15433 }
15434 }
15435
15436 return 1;
15437 }
15438
15439#endif
15440
15441#ifdef BRIGHT_XCALIB
15442
15443 if (!g_brightness_init) {
15444 last_brightness = 100;
15445 g_brightness_init = true;
15446 temp_file_name = wxFileName::CreateTempFileName("");
15447 InitScreenBrightness();
15448 }
15449
15450#ifdef __OPCPN_USEICC__
15451 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15452 // desired, and then activate this temporary profile using xcalib <filename>
15453 if (!CreateSimpleICCProfileFile(
15454 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15455 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15456 wxString cmd("xcalib ");
15457 cmd += temp_file_name;
15458
15459 wxExecute(cmd, wxEXEC_ASYNC);
15460 }
15461
15462#else
15463 // Or, use "xcalib -co" to set overall contrast value
15464 // This is not as nice, since the -co parameter wants to be a fraction of
15465 // the current contrast, and values greater than 100 are not allowed. As a
15466 // result, increases of contrast must do a "-clear" step first, which
15467 // produces objectionable flashing.
15468 if (brightness > last_brightness) {
15469 wxString cmd;
15470 cmd = "xcalib -clear";
15471 wxExecute(cmd, wxEXEC_ASYNC);
15472
15473 ::wxMilliSleep(10);
15474
15475 int brite_adj = wxMax(1, brightness);
15476 cmd.Printf("xcalib -co %2d -a", brite_adj);
15477 wxExecute(cmd, wxEXEC_ASYNC);
15478 } else {
15479 int brite_adj = wxMax(1, brightness);
15480 int factor = (brite_adj * 100) / last_brightness;
15481 factor = wxMax(1, factor);
15482 wxString cmd;
15483 cmd.Printf("xcalib -co %2d -a", factor);
15484 wxExecute(cmd, wxEXEC_ASYNC);
15485 }
15486
15487#endif
15488
15489 last_brightness = brightness;
15490
15491#endif
15492
15493 return 0;
15494}
15495
15496#ifdef __OPCPN_USEICC__
15497
15498#define MLUT_TAG 0x6d4c5554L
15499#define VCGT_TAG 0x76636774L
15500
15501int GetIntEndian(unsigned char *s) {
15502 int ret;
15503 unsigned char *p;
15504 int i;
15505
15506 p = (unsigned char *)&ret;
15507
15508 if (1)
15509 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15510 else
15511 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15512
15513 return ret;
15514}
15515
15516unsigned short GetShortEndian(unsigned char *s) {
15517 unsigned short ret;
15518 unsigned char *p;
15519 int i;
15520
15521 p = (unsigned char *)&ret;
15522
15523 if (1)
15524 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15525 else
15526 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15527
15528 return ret;
15529}
15530
15531// Create a very simple Gamma correction file readable by xcalib
15532int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15533 double co_green, double co_blue) {
15534 FILE *fp;
15535
15536 if (file_name) {
15537 fp = fopen(file_name, "wb");
15538 if (!fp) return -1; /* file can not be created */
15539 } else
15540 return -1; /* filename char pointer not valid */
15541
15542 // Write header
15543 char header[128];
15544 for (int i = 0; i < 128; i++) header[i] = 0;
15545
15546 fwrite(header, 128, 1, fp);
15547
15548 // Num tags
15549 int numTags0 = 1;
15550 int numTags = GetIntEndian((unsigned char *)&numTags0);
15551 fwrite(&numTags, 1, 4, fp);
15552
15553 int tagName0 = VCGT_TAG;
15554 int tagName = GetIntEndian((unsigned char *)&tagName0);
15555 fwrite(&tagName, 1, 4, fp);
15556
15557 int tagOffset0 = 128 + 4 * sizeof(int);
15558 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15559 fwrite(&tagOffset, 1, 4, fp);
15560
15561 int tagSize0 = 1;
15562 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15563 fwrite(&tagSize, 1, 4, fp);
15564
15565 fwrite(&tagName, 1, 4, fp); // another copy of tag
15566
15567 fwrite(&tagName, 1, 4, fp); // dummy
15568
15569 // Table type
15570
15571 /* VideoCardGammaTable (The simplest type) */
15572 int gammatype0 = 0;
15573 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15574 fwrite(&gammatype, 1, 4, fp);
15575
15576 int numChannels0 = 3;
15577 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15578 fwrite(&numChannels, 1, 2, fp);
15579
15580 int numEntries0 = 256;
15581 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15582 fwrite(&numEntries, 1, 2, fp);
15583
15584 int entrySize0 = 1;
15585 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15586 fwrite(&entrySize, 1, 2, fp);
15587
15588 unsigned char ramp[256];
15589
15590 // Red ramp
15591 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15592 fwrite(ramp, 256, 1, fp);
15593
15594 // Green ramp
15595 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15596 fwrite(ramp, 256, 1, fp);
15597
15598 // Blue ramp
15599 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15600 fwrite(ramp, 256, 1, fp);
15601
15602 fclose(fp);
15603
15604 return 0;
15605}
15606#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:30
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:173
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13969
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:11994
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:13928
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:807
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
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
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:898
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:183
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:791
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:13967
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:13924
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
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition 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:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:772
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:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
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
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
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:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.