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 case GLOBAL_COLOR_SCHEME_DAY_HICON:
3779 m_pos_image_red = &m_os_image_red_day;
3780 m_pos_image_grey = &m_os_image_grey_day;
3781 m_pos_image_yellow = &m_os_image_yellow_day;
3782 m_pos_image_user = m_pos_image_user_day;
3783 m_pos_image_user_grey = m_pos_image_user_grey_day;
3784 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3785 m_cTideBitmap = m_bmTideDay;
3786 m_cCurrentBitmap = m_bmCurrentDay;
3787
3788 break;
3789 case GLOBAL_COLOR_SCHEME_DUSK:
3790 case GLOBAL_COLOR_SCHEME_NIGHT_HICON:
3791 m_pos_image_red = &m_os_image_red_dusk;
3792 m_pos_image_grey = &m_os_image_grey_dusk;
3793 m_pos_image_yellow = &m_os_image_yellow_dusk;
3794 m_pos_image_user = m_pos_image_user_dusk;
3795 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3796 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3797 m_cTideBitmap = m_bmTideDusk;
3798 m_cCurrentBitmap = m_bmCurrentDusk;
3799 break;
3800 case GLOBAL_COLOR_SCHEME_NIGHT:
3801 m_pos_image_red = &m_os_image_red_night;
3802 m_pos_image_grey = &m_os_image_grey_night;
3803 m_pos_image_yellow = &m_os_image_yellow_night;
3804 m_pos_image_user = m_pos_image_user_night;
3805 m_pos_image_user_grey = m_pos_image_user_grey_night;
3806 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3807 m_cTideBitmap = m_bmTideNight;
3808 m_cCurrentBitmap = m_bmCurrentNight;
3809 break;
3810 default:
3811 m_pos_image_red = &m_os_image_red_day;
3812 m_pos_image_grey = &m_os_image_grey_day;
3813 m_pos_image_yellow = &m_os_image_yellow_day;
3814 m_pos_image_user = m_pos_image_user_day;
3815 m_pos_image_user_grey = m_pos_image_user_grey_day;
3816 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3817 m_cTideBitmap = m_bmTideDay;
3818 m_cCurrentBitmap = m_bmCurrentDay;
3819 break;
3820 }
3821
3822 CreateDepthUnitEmbossMaps(cs);
3823 CreateOZEmbossMapData(cs);
3824
3825 // Set up fog effect base color
3826 m_fog_color = wxColor(
3827 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3828 float dim = 1.0;
3829 switch (cs) {
3830 case GLOBAL_COLOR_SCHEME_DUSK:
3831 case GLOBAL_COLOR_SCHEME_NIGHT_HICON:
3832 dim = 0.5;
3833 break;
3834 case GLOBAL_COLOR_SCHEME_NIGHT:
3835 dim = 0.25;
3836 break;
3837 default:
3838 break;
3839 }
3840 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3841 m_fog_color.Blue() * dim);
3842
3843 // Really dark
3844#if 0
3845 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3846 SetBackgroundColour( wxColour(0,0,0) );
3847
3848 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3849 }
3850 else{
3851 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3852#ifndef __WXMAC__
3853 SetBackgroundColour( wxNullColour );
3854#endif
3855 }
3856#endif
3857
3858 // UpdateToolbarColorScheme(cs);
3859
3860 m_Piano->SetColorScheme(cs);
3861
3862 m_Compass->SetColorScheme(cs);
3863
3864 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3865
3866 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3867
3868 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3869 if (m_notification_button) {
3870 m_notification_button->SetColorScheme(cs);
3871 }
3872
3873#ifdef ocpnUSE_GL
3874 if (g_bopengl && m_glcc) {
3875 m_glcc->SetColorScheme(cs);
3876 g_glTextureManager->ClearAllRasterTextures();
3877 // m_glcc->FlushFBO();
3878 }
3879#endif
3880 SetbTCUpdate(true); // force re-render of tide/current locators
3881 m_brepaint_piano = true;
3882
3883 ReloadVP();
3884
3885 m_cs = cs;
3886}
3887
3888wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3889 wxImage img = Bitmap.ConvertToImage();
3890 int sx = img.GetWidth();
3891 int sy = img.GetHeight();
3892
3893 wxImage new_img(img);
3894
3895 for (int i = 0; i < sx; i++) {
3896 for (int j = 0; j < sy; j++) {
3897 if (!img.IsTransparent(i, j)) {
3898 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3899 (unsigned char)(img.GetGreen(i, j) * factor),
3900 (unsigned char)(img.GetBlue(i, j) * factor));
3901 }
3902 }
3903 }
3904
3905 wxBitmap ret = wxBitmap(new_img);
3906
3907 return ret;
3908}
3909
3910void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3911 int max) {
3912 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3913 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3914
3915 if (!m_pBrightPopup) {
3916 // Calculate size
3917 int x, y;
3918 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3919
3920 m_pBrightPopup = new TimedPopupWin(this, 3);
3921
3922 m_pBrightPopup->SetSize(x, y);
3923 m_pBrightPopup->Move(120, 120);
3924 }
3925
3926 int bmpsx = m_pBrightPopup->GetSize().x;
3927 int bmpsy = m_pBrightPopup->GetSize().y;
3928
3929 wxBitmap bmp(bmpsx, bmpsx);
3930 wxMemoryDC mdc(bmp);
3931
3932 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3933 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3934 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3935 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3936 mdc.Clear();
3937
3938 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3939
3940 mdc.SetFont(*pfont);
3941 wxString val;
3942
3943 if (brightness == max)
3944 val = "MAX";
3945 else if (brightness == min)
3946 val = "MIN";
3947 else
3948 val.Printf("%3d", brightness);
3949
3950 mdc.DrawText(val, 0, 0);
3951
3952 mdc.SelectObject(wxNullBitmap);
3953
3954 m_pBrightPopup->SetBitmap(bmp);
3955 m_pBrightPopup->Show();
3956 m_pBrightPopup->Refresh();
3957}
3958
3959void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3960 m_b_rot_hidef = true;
3961 ReloadVP();
3962}
3963
3964void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3965 if (!g_bRollover) return;
3966
3967 bool b_need_refresh = false;
3968
3969 wxSize win_size = GetSize() * m_displayScale;
3970 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3971
3972 // Handle the AIS Rollover Window first
3973 bool showAISRollover = false;
3974 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3975 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3976 SelectItem *pFind = pSelectAIS->FindSelection(
3977 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3978 if (pFind) {
3979 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3980 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3981
3982 if (ptarget) {
3983 showAISRollover = true;
3984
3985 if (NULL == m_pAISRolloverWin) {
3986 m_pAISRolloverWin = new RolloverWin(this);
3987 m_pAISRolloverWin->IsActive(false);
3988 b_need_refresh = true;
3989 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3990 m_AISRollover_MMSI != FoundAIS_MMSI) {
3991 // Sometimes the mouse moves fast enough to get over a new AIS
3992 // target before the one-shot has fired to remove the old target.
3993 // Result: wrong target data is shown.
3994 // Detect this case,close the existing rollover ASAP, and restart
3995 // the timer.
3996 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3997 m_pAISRolloverWin->IsActive(false);
3998 m_AISRollover_MMSI = 0;
3999 Refresh();
4000 return;
4001 }
4002
4003 m_AISRollover_MMSI = FoundAIS_MMSI;
4004
4005 if (!m_pAISRolloverWin->IsActive()) {
4006 wxString s = ptarget->GetRolloverString();
4007 m_pAISRolloverWin->SetString(s);
4008
4009 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4010 AIS_ROLLOVER, win_size);
4011 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
4012 m_pAISRolloverWin->IsActive(true);
4013 b_need_refresh = true;
4014 }
4015 }
4016 } else {
4017 m_AISRollover_MMSI = 0;
4018 showAISRollover = false;
4019 }
4020 }
4021
4022 // Maybe turn the rollover off
4023 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
4024 m_pAISRolloverWin->IsActive(false);
4025 m_AISRollover_MMSI = 0;
4026 b_need_refresh = true;
4027 }
4028
4029 // Now the Route info rollover
4030 // Show the route segment info
4031 bool showRouteRollover = false;
4032
4033 if (NULL == m_pRolloverRouteSeg) {
4034 // Get a list of all selectable sgements, and search for the first
4035 // visible segment as the rollover target.
4036
4037 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4038 SelectableItemList SelList = pSelect->FindSelectionList(
4039 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4040 auto node = SelList.begin();
4041 while (node != SelList.end()) {
4042 SelectItem *pFindSel = *node;
4043
4044 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4045
4046 if (pr && pr->IsVisible()) {
4047 m_pRolloverRouteSeg = pFindSel;
4048 showRouteRollover = true;
4049
4050 if (NULL == m_pRouteRolloverWin) {
4051 m_pRouteRolloverWin = new RolloverWin(this, 10);
4052 m_pRouteRolloverWin->IsActive(false);
4053 }
4054
4055 if (!m_pRouteRolloverWin->IsActive()) {
4056 wxString s;
4057 RoutePoint *segShow_point_a =
4058 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4059 RoutePoint *segShow_point_b =
4060 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4061
4062 // Minimally validate routepoint pointers
4063 int ia = pr->GetIndexOf(segShow_point_a);
4064 int ib = pr->GetIndexOf(segShow_point_b);
4065 if (ia < 0 || ib < 0) {
4066 wxLogMessage("Ignoring stale route segment selection.");
4067 return;
4068 }
4069
4070 double brg, dist;
4071 DistanceBearingMercator(
4072 segShow_point_b->m_lat, segShow_point_b->m_lon,
4073 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4074
4075 if (!pr->m_bIsInLayer)
4076 s.Append(_("Route") + ": ");
4077 else
4078 s.Append(_("Layer Route: "));
4079
4080 if (pr->m_RouteNameString.IsEmpty())
4081 s.Append(_("(unnamed)"));
4082 else
4083 s.Append(pr->m_RouteNameString);
4084
4085 s << "\n"
4086 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4087 << "\n"
4088 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4089 << segShow_point_b->GetName() << "\n";
4090
4091 if (g_bShowTrue)
4092 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4093 (int)floor(brg + 0.5), 0x00B0);
4094 if (g_bShowMag) {
4095 double latAverage =
4096 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4097 double lonAverage =
4098 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4099 double varBrg =
4100 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4101
4102 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4103 (int)floor(varBrg + 0.5), 0x00B0);
4104 }
4105
4106 s << FormatDistanceAdaptive(dist);
4107
4108 // Compute and display cumulative distance from route start point to
4109 // current leg end point and RNG,TTG,ETA from ship to current leg end
4110 // point for active route
4111 double shiptoEndLeg = 0.;
4112 bool validActive = false;
4113 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4114 validActive = true;
4115
4116 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4117 auto node = pr->pRoutePointList->begin();
4118 RoutePoint *prp;
4119 float dist_to_endleg = 0;
4120 wxString t;
4121
4122 for (++node; node != pr->pRoutePointList->end(); ++node) {
4123 prp = *node;
4124 if (validActive)
4125 shiptoEndLeg += prp->m_seg_len;
4126 else if (prp->m_bIsActive)
4127 validActive = true;
4128 dist_to_endleg += prp->m_seg_len;
4129 if (prp->IsSame(segShow_point_a)) break;
4130 }
4131 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4132 }
4133 // write from ship to end selected leg point data if the route is
4134 // active
4135 if (validActive) {
4136 s << "\n"
4137 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4138 shiptoEndLeg +=
4140 ->GetCurrentRngToActivePoint(); // add distance from ship
4141 // to active point
4142 shiptoEndLeg +=
4143 segShow_point_b
4144 ->m_seg_len; // add the lenght of the selected leg
4145 s << FormatDistanceAdaptive(shiptoEndLeg);
4146 // ensure sog/cog are valid and vmg is positive to keep data
4147 // coherent
4148 double vmg = 0.;
4149 if (!std::isnan(gCog) && !std::isnan(gSog))
4150 vmg = gSog *
4151 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4152 PI / 180.);
4153 if (vmg > 0.) {
4154 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4155 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4156 s << " - "
4157 << wxString(ttg_sec > SECONDS_PER_DAY
4158 ? ttg_span.Format(_("%Dd %H:%M"))
4159 : ttg_span.Format(_("%H:%M")));
4160 wxDateTime dtnow, eta;
4161 eta = dtnow.SetToCurrent().Add(ttg_span);
4162 s << " - " << eta.Format("%b").Mid(0, 4)
4163 << eta.Format(" %d %H:%M");
4164 } else
4165 s << " ---- ----";
4166 }
4167 m_pRouteRolloverWin->SetString(s);
4168
4169 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4170 LEG_ROLLOVER, win_size);
4171 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4172 m_pRouteRolloverWin->IsActive(true);
4173 b_need_refresh = true;
4174 showRouteRollover = true;
4175 break;
4176 }
4177 } else {
4178 ++node;
4179 }
4180 }
4181 } else {
4182 // Is the cursor still in select radius, and not timed out?
4183 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4184 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4185 m_pRolloverRouteSeg))
4186 showRouteRollover = false;
4187 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4188 showRouteRollover = false;
4189 else
4190 showRouteRollover = true;
4191 }
4192
4193 // If currently creating a route, do not show this rollover window
4194 if (m_routeState) showRouteRollover = false;
4195
4196 // Similar for AIS target rollover window
4197 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4198 showRouteRollover = false;
4199
4200 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4201 !showRouteRollover) {
4202 m_pRouteRolloverWin->IsActive(false);
4203 m_pRolloverRouteSeg = NULL;
4204 m_pRouteRolloverWin->Destroy();
4205 m_pRouteRolloverWin = NULL;
4206 b_need_refresh = true;
4207 } else if (m_pRouteRolloverWin && showRouteRollover) {
4208 m_pRouteRolloverWin->IsActive(true);
4209 b_need_refresh = true;
4210 }
4211
4212 // Now the Track info rollover
4213 // Show the track segment info
4214 bool showTrackRollover = false;
4215
4216 if (NULL == m_pRolloverTrackSeg) {
4217 // Get a list of all selectable sgements, and search for the first
4218 // visible segment as the rollover target.
4219
4220 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4221 SelectableItemList SelList = pSelect->FindSelectionList(
4222 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4223
4224 auto node = SelList.begin();
4225 while (node != SelList.end()) {
4226 SelectItem *pFindSel = *node;
4227
4228 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4229
4230 if (pt && pt->IsVisible()) {
4231 m_pRolloverTrackSeg = pFindSel;
4232 showTrackRollover = true;
4233
4234 if (NULL == m_pTrackRolloverWin) {
4235 m_pTrackRolloverWin = new RolloverWin(this, 10);
4236 m_pTrackRolloverWin->IsActive(false);
4237 }
4238
4239 if (!m_pTrackRolloverWin->IsActive()) {
4240 wxString s;
4241 TrackPoint *segShow_point_a =
4242 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4243 TrackPoint *segShow_point_b =
4244 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4245
4246 double brg, dist;
4247 DistanceBearingMercator(
4248 segShow_point_b->m_lat, segShow_point_b->m_lon,
4249 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4250
4251 if (!pt->m_bIsInLayer)
4252 s.Append(_("Track") + ": ");
4253 else
4254 s.Append(_("Layer Track: "));
4255
4256 if (pt->GetName().IsEmpty())
4257 s.Append(_("(unnamed)"));
4258 else
4259 s.Append(pt->GetName());
4260 double tlenght = pt->Length();
4261 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4262 if (pt->GetLastPoint()->GetTimeString() &&
4263 pt->GetPoint(0)->GetTimeString()) {
4264 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4265 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4266 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4267 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4268 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4269 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4270 << getUsrSpeedUnit();
4271 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4272 : ttime.Format(" %H:%M"));
4273 }
4274 }
4275
4276 if (g_bShowTrackPointTime &&
4277 strlen(segShow_point_b->GetTimeString())) {
4278 wxString stamp = segShow_point_b->GetTimeString();
4279 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4280 if (timestamp.IsValid()) {
4281 // Format track rollover timestamp to OCPN global TZ setting
4284 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4285 }
4286 s << "\n" << _("Segment Created: ") << stamp;
4287 }
4288
4289 s << "\n";
4290 if (g_bShowTrue)
4291 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4292 0x00B0);
4293
4294 if (g_bShowMag) {
4295 double latAverage =
4296 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4297 double lonAverage =
4298 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4299 double varBrg =
4300 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4301
4302 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4303 0x00B0);
4304 }
4305
4306 s << FormatDistanceAdaptive(dist);
4307
4308 if (segShow_point_a->GetTimeString() &&
4309 segShow_point_b->GetTimeString()) {
4310 wxDateTime apoint = segShow_point_a->GetCreateTime();
4311 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4312 if (apoint.IsValid() && bpoint.IsValid()) {
4313 double segmentSpeed = toUsrSpeed(
4314 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4315 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4316 << getUsrSpeedUnit();
4317 }
4318 }
4319
4320 m_pTrackRolloverWin->SetString(s);
4321
4322 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4323 LEG_ROLLOVER, win_size);
4324 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4325 m_pTrackRolloverWin->IsActive(true);
4326 b_need_refresh = true;
4327 showTrackRollover = true;
4328 break;
4329 }
4330 } else {
4331 ++node;
4332 }
4333 }
4334 } else {
4335 // Is the cursor still in select radius, and not timed out?
4336 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4337 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4338 m_pRolloverTrackSeg))
4339 showTrackRollover = false;
4340 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4341 showTrackRollover = false;
4342 else
4343 showTrackRollover = true;
4344 }
4345
4346 // Similar for AIS target rollover window
4347 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4348 showTrackRollover = false;
4349
4350 // Similar for route rollover window
4351 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4352 showTrackRollover = false;
4353
4354 // TODO We onlt show tracks on primary canvas....
4355 // if(!IsPrimaryCanvas())
4356 // showTrackRollover = false;
4357
4358 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4359 !showTrackRollover) {
4360 m_pTrackRolloverWin->IsActive(false);
4361 m_pRolloverTrackSeg = NULL;
4362 m_pTrackRolloverWin->Destroy();
4363 m_pTrackRolloverWin = NULL;
4364 b_need_refresh = true;
4365 } else if (m_pTrackRolloverWin && showTrackRollover) {
4366 m_pTrackRolloverWin->IsActive(true);
4367 b_need_refresh = true;
4368 }
4369
4370 if (b_need_refresh) Refresh();
4371}
4372
4373void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4374 if ((GetShowENCLights() || m_bsectors_shown) &&
4375 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4376 extendedSectorLegs)) {
4377 if (!m_bsectors_shown) {
4378 ReloadVP(false);
4379 m_bsectors_shown = true;
4380 }
4381 } else {
4382 if (m_bsectors_shown) {
4383 ReloadVP(false);
4384 m_bsectors_shown = false;
4385 }
4386 }
4387
4388// This is here because GTK status window update is expensive..
4389// cairo using pango rebuilds the font every time so is very
4390// inefficient
4391// Anyway, only update the status bar when this timer expires
4392#if defined(__WXGTK__) || defined(__WXQT__)
4393 {
4394 // Check the absolute range of the cursor position
4395 // There could be a window wherein the chart geoereferencing is not
4396 // valid....
4397 double cursor_lat, cursor_lon;
4398 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4399
4400 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4401 while (cursor_lon < -180.) cursor_lon += 360.;
4402
4403 while (cursor_lon > 180.) cursor_lon -= 360.;
4404
4405 SetCursorStatus(cursor_lat, cursor_lon);
4406 }
4407 }
4408#endif
4409}
4410
4411void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4412 if (!top_frame::Get()->GetFrameStatusBar()) return;
4413
4414 wxString s1;
4415 s1 += " ";
4416 s1 += toSDMM(1, cursor_lat);
4417 s1 += " ";
4418 s1 += toSDMM(2, cursor_lon);
4419
4420 if (STAT_FIELD_CURSOR_LL >= 0)
4421 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4422
4423 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4424
4425 double brg, dist;
4426 wxString sm;
4427 wxString st;
4428 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4429 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4430 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4431
4432 wxString s = st + sm;
4433 s << FormatDistanceAdaptive(dist);
4434
4435 // CUSTOMIZATION - LIVE ETA OPTION
4436 // -------------------------------------------------------
4437 // Calculate an "live" ETA based on route starting from the current
4438 // position of the boat and goes to the cursor of the mouse.
4439 // In any case, an standard ETA will be calculated with a default speed
4440 // of the boat to give an estimation of the route (in particular if GPS
4441 // is off).
4442
4443 // Display only if option "live ETA" is selected in Settings > Display >
4444 // General.
4445 if (g_bShowLiveETA) {
4446 float realTimeETA;
4447 float boatSpeed;
4448 float boatSpeedDefault = g_defaultBoatSpeed;
4449
4450 // Calculate Estimate Time to Arrival (ETA) in minutes
4451 // Check before is value not closed to zero (it will make an very big
4452 // number...)
4453 if (!std::isnan(gSog)) {
4454 boatSpeed = gSog;
4455 if (boatSpeed < 0.5) {
4456 realTimeETA = 0;
4457 } else {
4458 realTimeETA = dist / boatSpeed * 60;
4459 }
4460 } else {
4461 realTimeETA = 0;
4462 }
4463
4464 // Add space after distance display
4465 s << " ";
4466 // Display ETA
4467 s << minutesToHoursDays(realTimeETA);
4468
4469 // In any case, display also an ETA with default speed at 6knts
4470
4471 s << " [@";
4472 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4473 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4474 s << " ";
4475 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4476 s << "]";
4477 }
4478 // END OF - LIVE ETA OPTION
4479
4480 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4481}
4482
4483// CUSTOMIZATION - FORMAT MINUTES
4484// -------------------------------------------------------
4485// New function to format minutes into a more readable format:
4486// * Hours + minutes, or
4487// * Days + hours.
4488wxString minutesToHoursDays(float timeInMinutes) {
4489 wxString s;
4490
4491 if (timeInMinutes == 0) {
4492 s << "--min";
4493 }
4494
4495 // Less than 60min, keep time in minutes
4496 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4497 s << wxString::Format("%d", (int)timeInMinutes);
4498 s << "min";
4499 }
4500
4501 // Between 1h and less than 24h, display time in hours, minutes
4502 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4503 int hours;
4504 int min;
4505 hours = (int)timeInMinutes / 60;
4506 min = (int)timeInMinutes % 60;
4507
4508 if (min == 0) {
4509 s << wxString::Format("%d", hours);
4510 s << "h";
4511 } else {
4512 s << wxString::Format("%d", hours);
4513 s << "h";
4514 s << wxString::Format("%d", min);
4515 s << "min";
4516 }
4517
4518 }
4519
4520 // More than 24h, display time in days, hours
4521 else if (timeInMinutes > 24 * 60) {
4522 int days;
4523 int hours;
4524 days = (int)(timeInMinutes / 60) / 24;
4525 hours = (int)(timeInMinutes / 60) % 24;
4526
4527 if (hours == 0) {
4528 s << wxString::Format("%d", days);
4529 s << "d";
4530 } else {
4531 s << wxString::Format("%d", days);
4532 s << "d";
4533 s << wxString::Format("%d", hours);
4534 s << "h";
4535 }
4536 }
4537
4538 return s;
4539}
4540
4541// END OF CUSTOMIZATION - FORMAT MINUTES
4542// Thanks open source code ;-)
4543// -------------------------------------------------------
4544
4545void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4546 double clat, clon;
4547 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4548 *lat = clat;
4549 *lon = clon;
4550}
4551
4552void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4553 wxPoint2DDouble *r) {
4554 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4555}
4556
4558 double rlon, wxPoint2DDouble *r) {
4559 // If the Current Chart is a raster chart, and the
4560 // requested lat/long is within the boundaries of the chart,
4561 // and the VP is not rotated,
4562 // then use the embedded BSB chart georeferencing algorithm
4563 // for greater accuracy
4564 // Additionally, use chart embedded georef if the projection is TMERC
4565 // i.e. NOT MERCATOR and NOT POLYCONIC
4566
4567 // If for some reason the chart rejects the request by returning an error,
4568 // then fall back to Viewport Projection estimate from canvas parameters
4569 if (!g_bopengl && m_singleChart &&
4570 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4571 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4572 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4573 (m_singleChart->GetChartProjectionType() !=
4574 PROJECTION_TRANSVERSE_MERCATOR) &&
4575 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4576 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4577 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4578 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4579 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4580 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4581 // Cur_BSB_Ch->GetCOVRTablenPoints
4582 // ( 0 ), rlon,
4583 // rlat );
4584 // bInside = true;
4585 // if ( bInside )
4586 if (Cur_BSB_Ch) {
4587 // This is a Raster chart....
4588 // If the VP is changing, the raster chart parameters may not yet be
4589 // setup So do that before accessing the chart's embedded
4590 // georeferencing
4591 Cur_BSB_Ch->SetVPRasterParms(vp);
4592 double rpixxd, rpixyd;
4593 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4594 r->m_x = rpixxd;
4595 r->m_y = rpixyd;
4596 return;
4597 }
4598 }
4599 }
4600
4601 // if needed, use the VPoint scaling estimator,
4602 *r = vp.GetDoublePixFromLL(rlat, rlon);
4603}
4604
4605// This routine might be deleted and all of the rendering improved
4606// to have floating point accuracy
4607bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4608 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4609}
4610
4611bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4612 wxPoint *r) {
4613 wxPoint2DDouble p;
4614 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4615
4616 // some projections give nan values when invisible values (other side of
4617 // world) are requested we should stop using integer coordinates or return
4618 // false here (and test it everywhere)
4619 if (std::isnan(p.m_x)) {
4620 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4621 return false;
4622 }
4623
4624 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4625 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4626 else
4627 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4628
4629 return true;
4630}
4631
4632void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4633 double &lon) {
4634 // If the Current Chart is a raster chart, and the
4635 // requested x,y is within the boundaries of the chart,
4636 // and the VP is not rotated,
4637 // then use the embedded BSB chart georeferencing algorithm
4638 // for greater accuracy
4639 // Additionally, use chart embedded georef if the projection is TMERC
4640 // i.e. NOT MERCATOR and NOT POLYCONIC
4641
4642 // If for some reason the chart rejects the request by returning an error,
4643 // then fall back to Viewport Projection estimate from canvas parameters
4644 bool bUseVP = true;
4645
4646 if (!g_bopengl && m_singleChart &&
4647 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4648 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4649 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4650 (m_singleChart->GetChartProjectionType() !=
4651 PROJECTION_TRANSVERSE_MERCATOR) &&
4652 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4653 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4654 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4655 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4656
4657 // TODO maybe need iterative process to validate bInside
4658 // first pass is mercator, then check chart boundaries
4659
4660 if (Cur_BSB_Ch) {
4661 // This is a Raster chart....
4662 // If the VP is changing, the raster chart parameters may not yet be
4663 // setup So do that before accessing the chart's embedded
4664 // georeferencing
4665 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4666
4667 double slat, slon;
4668 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4669 lat = slat;
4670
4671 if (slon < -180.)
4672 slon += 360.;
4673 else if (slon > 180.)
4674 slon -= 360.;
4675
4676 lon = slon;
4677 bUseVP = false;
4678 }
4679 }
4680 }
4681
4682 // if needed, use the VPoint scaling estimator
4683 if (bUseVP) {
4684 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4685 }
4686}
4687
4689 StopMovement();
4690 DoZoomCanvas(factor, false);
4691 extendedSectorLegs.clear();
4692}
4693
4694void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4695 bool stoptimer) {
4696 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4697
4698 if (g_bsmoothpanzoom) {
4699 if (StartTimedMovement(stoptimer)) {
4700 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4701 m_zoom_factor = factor;
4702 }
4703
4704 m_zoom_target = VPoint.chart_scale / factor;
4705 } else {
4706 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4707
4708 DoZoomCanvas(factor, can_zoom_to_cursor);
4709 }
4710
4711 extendedSectorLegs.clear();
4712}
4713
4714void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4715 // possible on startup
4716 if (!ChartData) return;
4717 if (!m_pCurrentStack) return;
4718
4719 /* TODO: queue the quilted loading code to a background thread
4720 so yield is never called from here, and also rendering is not delayed */
4721
4722 // Cannot allow Yield() re-entrancy here
4723 if (m_bzooming) return;
4724 m_bzooming = true;
4725
4726 double old_ppm = GetVP().view_scale_ppm;
4727
4728 // Capture current cursor position for zoom to cursor
4729 double zlat = m_cursor_lat;
4730 double zlon = m_cursor_lon;
4731
4732 double proposed_scale_onscreen =
4733 GetVP().chart_scale /
4734 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4735 bool b_do_zoom = false;
4736
4737 if (factor > 1) {
4738 b_do_zoom = true;
4739
4740 // double zoom_factor = factor;
4741
4742 ChartBase *pc = NULL;
4743
4744 if (!VPoint.b_quilt) {
4745 pc = m_singleChart;
4746 } else {
4747 if (!m_disable_adjust_on_zoom) {
4748 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4749 if (new_db_index >= 0)
4750 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4751 else { // for whatever reason, no reference chart is known
4752 // Choose the smallest scale chart on the current stack
4753 // and then adjust for scale range
4754 int current_ref_stack_index = -1;
4755 if (m_pCurrentStack->nEntry) {
4756 int trial_index =
4757 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4758 m_pQuilt->SetReferenceChart(trial_index);
4759 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4760 if (new_db_index >= 0)
4761 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4762 }
4763 }
4764
4765 if (m_pCurrentStack)
4766 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4767 new_db_index); // highlite the correct bar entry
4768 }
4769 }
4770
4771 if (pc) {
4772 // double target_scale_ppm = GetVPScale() * zoom_factor;
4773 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4774 // target_scale_ppm;
4775
4776 // Query the chart to determine the appropriate zoom range
4777 double min_allowed_scale =
4778 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4779
4780 if (proposed_scale_onscreen < min_allowed_scale) {
4781 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4782 m_zoom_factor = 1; /* stop zooming */
4783 b_do_zoom = false;
4784 } else
4785 proposed_scale_onscreen = min_allowed_scale;
4786 }
4787
4788 } else {
4789 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4790 }
4791
4792 } else if (factor < 1) {
4793 b_do_zoom = true;
4794
4795 ChartBase *pc = NULL;
4796
4797 bool b_smallest = false;
4798
4799 if (!VPoint.b_quilt) { // not quilted
4800 pc = m_singleChart;
4801
4802 if (pc) {
4803 // If m_singleChart is not on the screen, unbound the zoomout
4804 LLBBox viewbox = VPoint.GetBBox();
4805 // BoundingBox chart_box;
4806 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4807 double max_allowed_scale;
4808
4809 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4810
4811 // We can allow essentially unbounded zoomout in single chart mode
4812 // if( ChartData->GetDBBoundingBox( current_index,
4813 // &chart_box ) &&
4814 // !viewbox.IntersectOut( chart_box ) )
4815 // // Clamp the minimum scale zoom-out to the value
4816 // specified by the chart max_allowed_scale =
4817 // wxMin(max_allowed_scale, 4.0 *
4818 // pc->GetNormalScaleMax(
4819 // GetCanvasScaleFactor(),
4820 // GetCanvasWidth() ) );
4821 if (proposed_scale_onscreen > max_allowed_scale) {
4822 m_zoom_factor = 1; /* stop zooming */
4823 proposed_scale_onscreen = max_allowed_scale;
4824 }
4825 }
4826
4827 } else {
4828 if (!m_disable_adjust_on_zoom) {
4829 int new_db_index =
4830 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4831 if (new_db_index >= 0)
4832 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4833
4834 if (m_pCurrentStack)
4835 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4836 new_db_index); // highlite the correct bar entry
4837
4838 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4839
4840 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4841 proposed_scale_onscreen =
4842 wxMin(proposed_scale_onscreen,
4843 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4844 }
4845
4846 // set a minimum scale
4847 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4848 m_absolute_min_scale_ppm)
4849 proposed_scale_onscreen =
4850 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4851 }
4852 }
4853 double new_scale =
4854 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4855
4856 if (b_do_zoom) {
4857 // Disable ZTC if lookahead is ON, and currently b_follow is active
4858 bool b_allow_ztc = true;
4859 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4860 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4861 if (m_bLookAhead) {
4862 double brg, distance;
4863 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4864 &distance);
4865 dir_to_shift = brg;
4866 meters_to_shift = distance * 1852;
4867 }
4868 // Arrange to combine the zoom and pan into one operation for smoother
4869 // appearance
4870 SetVPScale(new_scale, false); // adjust, but deferred refresh
4871 wxPoint r;
4872 GetCanvasPointPix(zlat, zlon, &r);
4873 // this will emit the Refresh()
4874 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4875 } else {
4876 SetVPScale(new_scale);
4877 if (m_bFollow) DoCanvasUpdate();
4878 }
4879 }
4880
4881 m_bzooming = false;
4882}
4883
4884void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4885 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4886 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4887}
4888
4889int rot;
4890void ChartCanvas::RotateCanvas(double dir) {
4891 // SetUpMode(NORTH_UP_MODE);
4892
4893 if (g_bsmoothpanzoom) {
4894 if (StartTimedMovement()) {
4895 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4896 m_rotation_speed = dir * 60;
4897 }
4898 } else {
4899 double speed = dir * 10;
4900 if (m_modkeys == wxMOD_ALT) speed /= 20;
4901 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4902 }
4903}
4904
4905void ChartCanvas::DoRotateCanvas(double rotation) {
4906 while (rotation < 0) rotation += 2 * PI;
4907 while (rotation > 2 * PI) rotation -= 2 * PI;
4908
4909 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4910
4911 SetVPRotation(rotation);
4912 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4913}
4914
4915void ChartCanvas::DoTiltCanvas(double tilt) {
4916 while (tilt < 0) tilt = 0;
4917 while (tilt > .95) tilt = .95;
4918
4919 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4920
4921 VPoint.tilt = tilt;
4922 Refresh(false);
4923}
4924
4925void ChartCanvas::TogglebFollow() {
4926 if (!m_bFollow)
4927 SetbFollow();
4928 else
4929 ClearbFollow();
4930}
4931
4932void ChartCanvas::ClearbFollow() {
4933 m_bFollow = false; // update the follow flag
4934
4935 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4936
4937 UpdateFollowButtonState();
4938
4939 DoCanvasUpdate();
4940 ReloadVP();
4941 top_frame::Get()->SetChartUpdatePeriod();
4942}
4943
4944void ChartCanvas::SetbFollow() {
4945 // Is the OWNSHIP on-screen?
4946 // If not, then reset the OWNSHIP offset to 0 (center screen)
4947 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4948 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4949 m_OSoffsetx = 0;
4950 m_OSoffsety = 0;
4951 }
4952
4953 // Apply the present b_follow offset values to ship position
4954 wxPoint2DDouble p;
4956 p.m_x += m_OSoffsetx;
4957 p.m_y -= m_OSoffsety;
4958
4959 // compute the target center screen lat/lon
4960 double dlat, dlon;
4961 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4962
4963 JumpToPosition(dlat, dlon, GetVPScale());
4964 m_bFollow = true;
4965
4966 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4967 UpdateFollowButtonState();
4968
4969 if (!g_bSmoothRecenter) {
4970 DoCanvasUpdate();
4971 ReloadVP();
4972 }
4973 top_frame::Get()->SetChartUpdatePeriod();
4974}
4975
4976void ChartCanvas::UpdateFollowButtonState() {
4977 if (m_muiBar) {
4978 if (!m_bFollow)
4979 m_muiBar->SetFollowButtonState(0);
4980 else {
4981 if (m_bLookAhead)
4982 m_muiBar->SetFollowButtonState(2);
4983 else
4984 m_muiBar->SetFollowButtonState(1);
4985 }
4986 }
4987
4988#ifdef __ANDROID__
4989 if (!m_bFollow)
4990 androidSetFollowTool(0);
4991 else {
4992 if (m_bLookAhead)
4993 androidSetFollowTool(2);
4994 else
4995 androidSetFollowTool(1);
4996 }
4997#endif
4998
4999 // Look for plugin using API-121 or later
5000 // If found, make the follow state callback.
5001 if (g_pi_manager) {
5002 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
5003 if (pic->m_enabled && pic->m_init_state) {
5004 switch (pic->m_api_version) {
5005 case 121: {
5006 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
5007 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
5008 break;
5009 }
5010 default:
5011 break;
5012 }
5013 }
5014 }
5015 }
5016}
5017
5018void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
5019 if (g_bSmoothRecenter && !m_routeState) {
5020 if (StartSmoothJump(lat, lon, scale_ppm))
5021 return;
5022 else {
5023 // move closer to the target destination, and try again
5024 double gcDist, gcBearingEnd;
5025 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
5026 &gcBearingEnd);
5027 gcBearingEnd += 180;
5028 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
5029 GetCanvasWidth() / GetVPScale(); // meters
5030 double lon_offset =
5031 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
5032 double new_lat = lat + (lat_offset / (1852 * 60));
5033 double new_lon = lon + (lon_offset / (1852 * 60));
5034 SetViewPoint(new_lat, new_lon);
5035 ReloadVP();
5036 StartSmoothJump(lat, lon, scale_ppm);
5037 return;
5038 }
5039 }
5040
5041 if (lon > 180.0) lon -= 360.0;
5042 m_vLat = lat;
5043 m_vLon = lon;
5044 StopMovement();
5045 m_bFollow = false;
5046
5047 if (!GetQuiltMode()) {
5048 double skew = 0;
5049 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5050 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5051 } else {
5052 if (scale_ppm != GetVPScale()) {
5053 // XXX should be done in SetViewPoint
5054 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5055 AdjustQuiltRefChart();
5056 }
5057 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5058 }
5059
5060 ReloadVP();
5061
5062 UpdateFollowButtonState();
5063
5064 // TODO
5065 // if( g_pi_manager ) {
5066 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5067 // }
5068}
5069
5070bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5071 // Check distance to jump, in pixels at current chart scale
5072 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5073 // width.
5074 double gcDist;
5075 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5076 double distance_pixels = gcDist * GetVPScale();
5077 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5078 // Jump is too far, try again
5079 return false;
5080 }
5081
5082 // Save where we're coming from
5083 m_startLat = m_vLat;
5084 m_startLon = m_vLon;
5085 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5086
5087 // Save where we want to end up
5088 m_endLat = lat;
5089 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5090 m_endScale = scale_ppm;
5091
5092 // Setup timing
5093 m_animationDuration = 600; // ms
5094 m_animationStart = wxGetLocalTimeMillis();
5095
5096 // Stop any previous movement, ensure no conflicts
5097 StopMovement();
5098 m_bFollow = false;
5099
5100 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5101 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5102 m_animationActive = true;
5103
5104 return true;
5105}
5106
5107void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5108 // Calculate time fraction from 0..1
5109 wxLongLong now = wxGetLocalTimeMillis();
5110 double elapsed = (now - m_animationStart).ToDouble();
5111 double t = elapsed / m_animationDuration.ToDouble();
5112 if (t > 1.0) t = 1.0;
5113
5114 // Ease function for smoother movement
5115 double e = easeOutCubic(t);
5116
5117 // Interpolate lat/lon/scale
5118 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5119 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5120 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5121
5122 // Update viewpoint
5123 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5124 // portion)
5125 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5126 ReloadVP();
5127
5128 // If we reached the end, stop the timer and finalize
5129 if (t >= 1.0) {
5130 m_easeTimer.Stop();
5131 m_animationActive = false;
5132 UpdateFollowButtonState();
5133 ZoomCanvasSimple(1.0001);
5134 DoCanvasUpdate();
5135 ReloadVP();
5136 }
5137}
5138
5139bool ChartCanvas::PanCanvas(double dx, double dy) {
5140 if (!ChartData) return false;
5141 extendedSectorLegs.clear();
5142
5143 double dlat, dlon;
5144 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5145
5146 int iters = 0;
5147 for (;;) {
5148 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5149
5150 if (iters++ > 5) return false;
5151 if (!std::isnan(dlat)) break;
5152
5153 dx *= .5, dy *= .5;
5154 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5155 }
5156
5157 if (dlon > 360.) dlon -= 360.;
5158 if (dlon < -360.) dlon += 360.;
5159
5160 // This should not really be necessary, but round-trip georef on some
5161 // charts is not perfect, So we can get creep on repeated unidimensional
5162 // pans, and corrupt chart cacheing.......
5163
5164 // But this only works on north-up projections
5165 // TODO: can we remove this now?
5166 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5167 // .001 ) ) {
5168 //
5169 // if( dx == 0 ) dlon = clon;
5170 // if( dy == 0 ) dlat = clat;
5171 // }
5172
5173 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5174
5175 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5176
5177 if (VPoint.b_quilt) {
5178 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5179 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5180 // Tweak the scale slightly for a new ref chart
5181 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5182 if (pc) {
5183 double tweak_scale_ppm =
5184 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5185 SetVPScale(tweak_scale_ppm);
5186 }
5187 }
5188
5189#ifndef __ANDROID__
5190 // ToDo: Have seen crashes on "if (m_pCurrentStack->nEntry)"
5191 // Action: convert m_pCurrentStack to member object, not pointer.
5192
5193 if (new_ref_dbIndex == -1) {
5194#pragma GCC diagnostic push
5195#pragma GCC diagnostic ignored "-Warray-bounds"
5196 // The compiler sees a -1 index being used. Does not happen, though.
5197
5198 // for whatever reason, no reference chart is known
5199 // Probably panned out of the coverage region
5200 // If any charts are anywhere on-screen, choose the smallest
5201 // scale chart on the screen to be a new reference chart.
5202 int trial_index = -1;
5203 if (m_pCurrentStack->nEntry) {
5204 trial_index = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5205 }
5206
5207 if (trial_index < 0) {
5208 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5209 if (full_screen_array.size())
5210 trial_index = full_screen_array[full_screen_array.size() - 1];
5211 }
5212
5213 if (trial_index >= 0) {
5214 m_pQuilt->SetReferenceChart(trial_index);
5215 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5216 VPoint.rotation);
5217 ReloadVP();
5218 }
5219#pragma GCC diagnostic pop
5220 }
5221#endif
5222 }
5223
5224 // Turn off bFollow only if the ownship has left the screen
5225 if (m_bFollow) {
5226 double offx, offy;
5227 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5228
5229 double offset_angle = atan2(offy, offx);
5230 double offset_distance = sqrt((offy * offy) + (offx * offx));
5231 double chart_angle = GetVPRotation();
5232 double target_angle = chart_angle - offset_angle;
5233 double d_east_mod = offset_distance * cos(target_angle);
5234 double d_north_mod = offset_distance * sin(target_angle);
5235
5236 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5237 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5238
5239 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5240 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5241 m_bFollow = false; // update the follow flag
5242 UpdateFollowButtonState();
5243 }
5244 }
5245
5246 Refresh(false);
5247
5248 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5249
5250 return true;
5251}
5252
5253bool ChartCanvas::IsOwnshipOnScreen() {
5254 wxPoint r;
5256 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5257 ((r.y > 0) && r.y < GetCanvasHeight()))
5258 return true;
5259 else
5260 return false;
5261}
5262
5263void ChartCanvas::ReloadVP(bool b_adjust) {
5264 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5265
5266 LoadVP(VPoint, b_adjust);
5267}
5268
5269void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5270#ifdef ocpnUSE_GL
5271 if (g_bopengl && m_glcc) {
5272 m_glcc->Invalidate();
5273 if (m_glcc->GetSize() != GetSize()) {
5274 m_glcc->SetSize(GetSize());
5275 }
5276 } else
5277#endif
5278 {
5279 m_cache_vp.Invalidate();
5280 m_bm_cache_vp.Invalidate();
5281 }
5282
5283 VPoint.Invalidate();
5284
5285 if (m_pQuilt) m_pQuilt->Invalidate();
5286
5287 // Make sure that the Selected Group is sensible...
5288 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5289 // m_groupIndex = 0;
5290 // if( !CheckGroup( m_groupIndex ) )
5291 // m_groupIndex = 0;
5292
5293 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5294 vp.m_projection_type, b_adjust);
5295}
5296
5297void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5298 m_pQuilt->SetReferenceChart(dbIndex);
5299 VPoint.Invalidate();
5300 m_pQuilt->Invalidate();
5301}
5302
5303double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5304 if (m_pQuilt)
5305 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5306 else
5307 return vp.view_scale_ppm;
5308}
5309
5310// Verify and adjust the current reference chart,
5311// so that it will not lead to excessive overzoom or underzoom onscreen
5312int ChartCanvas::AdjustQuiltRefChart() {
5313 int ret = -1;
5314 if (m_pQuilt) {
5315 wxASSERT(ChartData);
5316 ChartBase *pc =
5317 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5318 if (pc) {
5319 double min_ref_scale =
5320 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5321 double max_ref_scale =
5322 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5323
5324 if (VPoint.chart_scale < min_ref_scale) {
5325 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5326 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5327 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5328 } else {
5329 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5330
5331 if (!brender_ok) {
5332 int target_stack_index = wxNOT_FOUND;
5333 int il = 0;
5334 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5335 if (index == m_pQuilt->GetRefChartdbIndex()) {
5336 target_stack_index = il;
5337 break;
5338 }
5339 il++;
5340 }
5341 if (wxNOT_FOUND == target_stack_index) // should never happen...
5342 target_stack_index = 0;
5343
5344 int ref_family = pc->GetChartFamily();
5345 int extended_array_count =
5346 m_pQuilt->GetExtendedStackIndexArray().size();
5347 while ((!brender_ok) &&
5348 ((int)target_stack_index < (extended_array_count - 1))) {
5349 target_stack_index++;
5350 int test_db_index =
5351 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5352
5353 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5354 IsChartQuiltableRef(test_db_index)) {
5355 // open the target, and check the min_scale
5356 ChartBase *ptest_chart =
5357 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5358 if (ptest_chart) {
5359 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5360 }
5361 }
5362 }
5363
5364 if (brender_ok) { // found a better reference chart
5365 int new_db_index =
5366 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5367 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5368 IsChartQuiltableRef(new_db_index)) {
5369 m_pQuilt->SetReferenceChart(new_db_index);
5370 ret = new_db_index;
5371 } else
5372 ret = m_pQuilt->GetRefChartdbIndex();
5373 } else
5374 ret = m_pQuilt->GetRefChartdbIndex();
5375
5376 } else
5377 ret = m_pQuilt->GetRefChartdbIndex();
5378 }
5379 } else
5380 ret = -1;
5381 }
5382
5383 return ret;
5384}
5385
5386void ChartCanvas::UpdateCanvasOnGroupChange() {
5387 delete m_pCurrentStack;
5388 m_pCurrentStack = new ChartStack;
5389 wxASSERT(ChartData);
5390 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5391 m_groupIndex);
5392
5393 if (m_pQuilt) {
5394 m_pQuilt->Compose(VPoint);
5395 SetFocus();
5396 }
5397}
5398
5399bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5400 double latNE, double lonNE) {
5401 // Center Point
5402 double latc = (latSW + latNE) / 2.0;
5403 double lonc = (lonSW + lonNE) / 2.0;
5404
5405 // Get scale in ppm (latitude)
5406 double ne_easting, ne_northing;
5407 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5408
5409 double sw_easting, sw_northing;
5410 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5411
5412 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5413
5414 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5415}
5416
5417bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5418 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5419 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5420}
5421
5422bool ChartCanvas::SetVPProjection(int projection) {
5423 if (!g_bopengl) // alternative projections require opengl
5424 return false;
5425
5426 // the view scale varies depending on geographic location and projection
5427 // rescale to keep the relative scale on the screen the same
5428 double prev_true_scale_ppm = m_true_scale_ppm;
5429 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5430 VPoint.skew, VPoint.rotation, projection) &&
5431 SetVPScale(wxMax(
5432 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5433 m_absolute_min_scale_ppm));
5434}
5435
5436bool ChartCanvas::SetViewPoint(double lat, double lon) {
5437 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5438 VPoint.rotation);
5439}
5440
5441bool ChartCanvas::SetVPRotation(double angle) {
5442 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5443 VPoint.skew, angle);
5444}
5445bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5446 double skew, double rotation, int projection,
5447 bool b_adjust, bool b_refresh) {
5448 if (ChartData->IsBusy()) return false;
5449 bool b_ret = false;
5450 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5451 skew -= 2 * PI;
5452 // Any sensible change?
5453 if (VPoint.IsValid()) {
5454 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5455 (fabs(VPoint.skew - skew) < 1e-9) &&
5456 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5457 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5458 (VPoint.m_projection_type == projection ||
5459 projection == PROJECTION_UNKNOWN))
5460 return false;
5461 }
5462 if (VPoint.m_projection_type != projection)
5463 VPoint.InvalidateTransformCache(); // invalidate
5464
5465 // Take a local copy of the last viewport
5466 ViewPort last_vp = VPoint;
5467
5468 VPoint.skew = skew;
5469 VPoint.clat = lat;
5470 VPoint.clon = lon;
5471 VPoint.rotation = rotation;
5472 VPoint.view_scale_ppm = scale_ppm;
5473 if (projection != PROJECTION_UNKNOWN)
5474 VPoint.SetProjectionType(projection);
5475 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5476 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5477
5478 // Clamp so the viewport edges don't extend beyond the maximum latitude
5479 // coverage. The basemap covers ±85°, and Mercator is undefined at ±90°.
5480 // Iterate to converge since Mercator is non-linear.
5481 static const double MAX_LAT = 85.0;
5482
5483 if (VPoint.pix_height > 0 && scale_ppm > 0) {
5484 double edge_lat, edge_lon;
5485
5486 for (int i = 0; i < 3; i++) {
5487 VPoint.GetLLFromPix(wxPoint2DDouble(VPoint.pix_width / 2.0, 0), &edge_lat,
5488 &edge_lon);
5489 if (!std::isnan(edge_lat) && edge_lat > MAX_LAT) {
5490 VPoint.clat -= (edge_lat - MAX_LAT);
5491 } else {
5492 break;
5493 }
5494 }
5495
5496 for (int i = 0; i < 3; i++) {
5497 VPoint.GetLLFromPix(
5498 wxPoint2DDouble(VPoint.pix_width / 2.0, VPoint.pix_height), &edge_lat,
5499 &edge_lon);
5500 if (!std::isnan(edge_lat) && edge_lat < -MAX_LAT) {
5501 VPoint.clat -= (edge_lat + MAX_LAT);
5502 } else {
5503 break;
5504 }
5505 }
5506 }
5507
5508 // Hard clamp as safety net
5509 if (VPoint.clat > MAX_LAT)
5510 VPoint.clat = MAX_LAT;
5511 else if (VPoint.clat < -MAX_LAT)
5512 VPoint.clat = -MAX_LAT;
5513
5514 // don't zoom out too far for transverse mercator polyconic until we resolve
5515 // issues
5516 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5517 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5518 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5519
5520 // SetVPRotation(rotation);
5521
5522 if (!g_bopengl) // tilt is not possible without opengl
5523 VPoint.tilt = 0;
5524
5525 if ((VPoint.pix_width <= 0) ||
5526 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5527 return false;
5528
5529 bool bwasValid = VPoint.IsValid();
5530 VPoint.Validate(); // Mark this ViewPoint as OK
5531
5532 // Has the Viewport scale changed? If so, invalidate the vp
5533 if (last_vp.view_scale_ppm != scale_ppm) {
5534 m_cache_vp.Invalidate();
5535 InvalidateGL();
5536 }
5537
5538 // A preliminary value, may be tweaked below
5539 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5540
5541 // recompute cursor position
5542 // and send to interested plugins if the mouse is actually in this window
5543 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5544 int mouseX = mouse_x;
5545 int mouseY = mouse_y;
5546 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5547 (mouseY < VPoint.pix_height)) {
5548 double lat_mouse, lon_mouse;
5549 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5550 m_cursor_lat = lat_mouse;
5551 m_cursor_lon = lon_mouse;
5552 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5553 }
5554 }
5555
5556 if (!VPoint.b_quilt && m_singleChart) {
5557 VPoint.SetBoxes();
5558
5559 // Allow the chart to adjust the new ViewPort for performance optimization
5560 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5561 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5562
5563 // If there is a sensible change in the chart render, refresh the whole
5564 // screen
5565 if ((!m_cache_vp.IsValid()) ||
5566 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5567 Refresh(false);
5568 b_ret = true;
5569 } else {
5570 wxPoint cp_last, cp_this;
5571 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5572 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5573
5574 if (cp_last != cp_this) {
5575 Refresh(false);
5576 b_ret = true;
5577 }
5578 }
5579 // Create the stack
5580 if (m_pCurrentStack) {
5581 assert(ChartData != 0);
5582 int current_db_index;
5583 current_db_index =
5584 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5585
5586 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5587 m_groupIndex);
5588 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5589 }
5590
5591 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5592 }
5593
5594 // Handle the quilted case
5595 if (VPoint.b_quilt) {
5596 VPoint.SetBoxes();
5597
5598 if (last_vp.view_scale_ppm != scale_ppm)
5599 m_pQuilt->InvalidateAllQuiltPatchs();
5600
5601 // Create the quilt
5602 if (ChartData /*&& ChartData->IsValid()*/) {
5603 if (!m_pCurrentStack) return false;
5604
5605 int current_db_index;
5606 current_db_index =
5607 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5608
5609 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5610 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5611
5612 // Check to see if the current quilt reference chart is in the new stack
5613 int current_ref_stack_index = -1;
5614 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5615 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5616 current_ref_stack_index = i;
5617 }
5618
5619 if (g_bFullScreenQuilt) {
5620 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5621 }
5622
5623 // We might need a new Reference Chart
5624 bool b_needNewRef = false;
5625
5626 // If the new stack does not contain the current ref chart....
5627 if ((-1 == current_ref_stack_index) &&
5628 (m_pQuilt->GetRefChartdbIndex() >= 0))
5629 b_needNewRef = true;
5630
5631 // Would the current Ref Chart be excessively underzoomed?
5632 // We need to check this here to be sure, since we cannot know where the
5633 // reference chart was assigned. For instance, the reference chart may
5634 // have been selected from the config file, or from a long jump with a
5635 // chart family switch implicit. Anyway, we check to be sure....
5636 bool renderable = true;
5637 ChartBase *referenceChart =
5638 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5639 if (referenceChart) {
5640 double chartMaxScale = referenceChart->GetNormalScaleMax(
5641 GetCanvasScaleFactor(), GetCanvasWidth());
5642 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5643 }
5644 if (!renderable) b_needNewRef = true;
5645
5646 // Need new refchart?
5647 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5648 const ChartTableEntry &cte_ref =
5649 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5650 int target_scale = cte_ref.GetScale();
5651 int target_type = cte_ref.GetChartType();
5652 int candidate_stack_index;
5653
5654 // reset the ref chart in a way that does not lead to excessive
5655 // underzoom, for performance reasons Try to find a chart that is the
5656 // same type, and has a scale of just smaller than the current ref
5657 // chart
5658
5659 candidate_stack_index = 0;
5660 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5661 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5662 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5663 int candidate_scale = cte_candidate.GetScale();
5664 int candidate_type = cte_candidate.GetChartType();
5665
5666 if ((candidate_scale >= target_scale) &&
5667 (candidate_type == target_type)) {
5668 bool renderable = true;
5669 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5670 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5671 if (tentative_referenceChart) {
5672 double chartMaxScale =
5673 tentative_referenceChart->GetNormalScaleMax(
5674 GetCanvasScaleFactor(), GetCanvasWidth());
5675 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5676 }
5677
5678 if (renderable) break;
5679 }
5680
5681 candidate_stack_index++;
5682 }
5683
5684 // If that did not work, look for a chart of just larger scale and
5685 // same type
5686 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5687 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5688 while (candidate_stack_index >= 0) {
5689 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5690 if (idx >= 0) {
5691 const ChartTableEntry &cte_candidate =
5692 ChartData->GetChartTableEntry(idx);
5693 int candidate_scale = cte_candidate.GetScale();
5694 int candidate_type = cte_candidate.GetChartType();
5695
5696 if ((candidate_scale <= target_scale) &&
5697 (candidate_type == target_type))
5698 break;
5699 }
5700 candidate_stack_index--;
5701 }
5702 }
5703
5704 // and if that did not work, chose stack entry 0
5705 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5706 (candidate_stack_index < 0))
5707 candidate_stack_index = 0;
5708
5709 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5710
5711 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5712 }
5713
5714 if (!g_bopengl) {
5715 // Preset the VPoint projection type to match what the quilt projection
5716 // type will be
5717 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5718
5719 // Always keep the default Mercator projection if the reference chart is
5720 // not in the PatchList or the scale is too small for it to render.
5721
5722 bool renderable = true;
5723 ChartBase *referenceChart =
5724 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5725 if (referenceChart) {
5726 double chartMaxScale = referenceChart->GetNormalScaleMax(
5727 GetCanvasScaleFactor(), GetCanvasWidth());
5728 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5729 proj = ChartData->GetDBChartProj(ref_db_index);
5730 } else
5731 proj = PROJECTION_MERCATOR;
5732
5733 VPoint.b_MercatorProjectionOverride =
5734 (m_pQuilt->GetnCharts() == 0 || !renderable);
5735
5736 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5737
5738 VPoint.SetProjectionType(proj);
5739 }
5740
5741 // If this quilt will be a perceptible delta from the existing quilt,
5742 // then refresh the entire screen
5743 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5744 // Allow the quilt to adjust the new ViewPort for performance
5745 // optimization This will normally be only a fractional (i.e.
5746 // sub-pixel) adjustment...
5747 if (b_adjust) {
5748 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5749 }
5750
5751 // ChartData->ClearCacheInUseFlags();
5752 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5753
5754 // wxStopWatch sw;
5755
5756#ifdef __ANDROID__
5757 // This is an optimization for panning on touch screen systems.
5758 // The quilt composition is deferred until the OnPaint() message gets
5759 // finally removed and processed from the message queue.
5760 // Takes advantage of the fact that touch-screen pan gestures are
5761 // usually short in distance,
5762 // so not requiring a full quilt rebuild until the pan gesture is
5763 // complete.
5764 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5765 // qDebug() << "Force compose";
5766 m_pQuilt->Compose(VPoint);
5767 } else {
5768 m_pQuilt->Invalidate();
5769 }
5770#else
5771 m_pQuilt->Compose(VPoint);
5772#endif
5773
5774 // printf("comp time %ld\n", sw.Time());
5775
5776 // If the extended chart stack has changed, invalidate any cached
5777 // render bitmap
5778 // if(m_pQuilt->GetXStackHash() != hash1) {
5779 // m_bm_cache_vp.Invalidate();
5780 // InvalidateGL();
5781 // }
5782
5783 ChartData->PurgeCacheUnusedCharts(0.7);
5784
5785 if (b_refresh) Refresh(false);
5786
5787 b_ret = true;
5788 }
5789 }
5790
5791 VPoint.skew = 0.; // Quilting supports 0 Skew
5792 } else if (!g_bopengl) {
5793 OcpnProjType projection = PROJECTION_UNKNOWN;
5794 if (m_singleChart) // viewport projection must match chart projection
5795 // without opengl
5796 projection = m_singleChart->GetChartProjectionType();
5797 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5798 VPoint.SetProjectionType(projection);
5799 }
5800
5801 // Has the Viewport projection changed? If so, invalidate the vp
5802 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5803 m_cache_vp.Invalidate();
5804 InvalidateGL();
5805 }
5806
5807 UpdateCanvasControlBar(); // Refresh the Piano
5808
5809 VPoint.chart_scale = 1.0; // fallback default value
5810
5811 if (VPoint.GetBBox().GetValid()) {
5812 // Update the viewpoint reference scale
5813 if (m_singleChart)
5814 VPoint.ref_scale = m_singleChart->GetNativeScale();
5815 else {
5816#ifdef __ANDROID__
5817 // This is an optimization for panning on touch screen systems.
5818 // See above.
5819 // Quilt might not be fully composed at this point, so for cm93
5820 // the reference scale may not be known.
5821 // In this case, do not update the VP ref_scale.
5822 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5823 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5824 }
5825#else
5826 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5827#endif
5828 }
5829
5830 // Calculate the on-screen displayed actual scale
5831 // by a simple traverse northward from the center point
5832 // of roughly one eighth of the canvas height
5833 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5834
5835 double delta_check =
5836 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5837 delta_check /= 8.;
5838
5839 double check_point = wxMin(89., VPoint.clat);
5840
5841 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5842
5843 double rhumbDist;
5844 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5845 VPoint.clon, 0, &rhumbDist);
5846
5847 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5848 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5849 // Calculate the distance between r1 and r in physical pixels.
5850 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5851 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5852
5853 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5854
5855 // A fall back in case of very high zoom-out, giving delta_y == 0
5856 // which can probably only happen with vector charts
5857 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5858
5859 // Another fallback, for highly zoomed out charts
5860 // This adjustment makes the displayed TrueScale correspond to the
5861 // same algorithm used to calculate the chart zoom-out limit for
5862 // ChartDummy.
5863 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5864
5865 if (m_true_scale_ppm)
5866 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5867 else
5868 VPoint.chart_scale = 1.0;
5869
5870 // Create a nice renderable string
5871 double round_factor = 1000.;
5872 if (VPoint.chart_scale <= 1000.)
5873 round_factor = 10.;
5874 else if (VPoint.chart_scale <= 10000.)
5875 round_factor = 100.;
5876 else if (VPoint.chart_scale <= 100000.)
5877 round_factor = 1000.;
5878
5879 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5880 double retina_coef = 1;
5881#ifdef ocpnUSE_GL
5882#ifdef __WXOSX__
5883 if (g_bopengl) {
5884 retina_coef = GetContentScaleFactor();
5885 }
5886#endif
5887#endif
5888
5889 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5890 // rounded to the nearest 10, 100 or 1000.
5891 //
5892 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5893 // true_scale_display. That does not make sense. The chart scale should be
5894 // the same as the true scale within the limits of the rounding factor.
5895 double true_scale_display =
5896 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5897 wxString text;
5898
5899 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5900
5901 if (m_displayed_scale_factor > 10.0)
5902 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5903 m_displayed_scale_factor);
5904 else if (m_displayed_scale_factor > 1.0)
5905 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5906 m_displayed_scale_factor);
5907 else if (m_displayed_scale_factor > 0.1) {
5908 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5909 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5910 } else if (m_displayed_scale_factor > 0.01) {
5911 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5912 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5913 } else {
5914 text.Printf(
5915 "%s %4.0f (---)", _("Scale"),
5916 true_scale_display); // Generally, no chart, so no chart scale factor
5917 }
5918
5919 m_scaleValue = true_scale_display;
5920 m_scaleText = text;
5921 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5922
5923 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5924 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5925 STAT_FIELD_SCALE)) {
5926 // Check to see if the text will fit in the StatusBar field...
5927 bool b_noshow = false;
5928 {
5929 int w = 0;
5930 int h;
5931 wxClientDC dc(top_frame::Get()->GetStatusBar());
5932 if (dc.IsOk()) {
5933 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5934 dc.SetFont(*templateFont);
5935 dc.GetTextExtent(text, &w, &h);
5936
5937 // If text is too long for the allocated field, try to reduce the text
5938 // string a bit.
5939 wxRect rect;
5940 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5941 rect);
5942 if (w && w > rect.width) {
5943 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5944 }
5945
5946 // Test again...if too big still, then give it up.
5947 dc.GetTextExtent(text, &w, &h);
5948
5949 if (w && w > rect.width) {
5950 b_noshow = true;
5951 }
5952 }
5953 }
5954
5955 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5956 }
5957 }
5958
5959 // Maintain member vLat/vLon
5960 m_vLat = VPoint.clat;
5961 m_vLon = VPoint.clon;
5962
5963 return b_ret;
5964}
5965
5966// Static Icon definitions for some symbols requiring
5967// scaling/rotation/translation Very specific wxDC draw commands are
5968// necessary to properly render these icons...See the code in
5969// ShipDraw()
5970
5971// This icon was adapted and scaled from the S52 Presentation Library
5972// version 3_03.
5973// Symbol VECGND02
5974
5975static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5976
5977// This ownship icon was adapted and scaled from the S52 Presentation
5978// Library version 3_03 Symbol OWNSHP05
5979static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5980 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5981
5982wxColour ChartCanvas::PredColor() {
5983 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5984 // visibility.
5985 if (SHIP_NORMAL == m_ownship_state)
5986 return GetGlobalColor("URED");
5987
5988 else if (SHIP_LOWACCURACY == m_ownship_state)
5989 return GetGlobalColor("YELO1");
5990
5991 return GetGlobalColor("NODTA");
5992}
5993
5994wxColour ChartCanvas::ShipColor() {
5995 // Establish ship color
5996 // It changes color based on GPS and Chart accuracy/availability
5997
5998 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5999
6000 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
6001
6002 return GetGlobalColor("URED"); // default is OK
6003}
6004
6005void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
6006 wxPoint2DDouble lShipMidPoint) {
6007 dc.SetPen(wxPen(PredColor(), 2));
6008
6009 if (SHIP_NORMAL == m_ownship_state)
6010 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
6011 else
6012 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
6013
6014 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
6015 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
6016
6017 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
6018 lShipMidPoint.m_y);
6019 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
6020 lShipMidPoint.m_y + 12);
6021}
6022
6023void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
6024 wxPoint GPSOffsetPixels,
6025 wxPoint2DDouble lGPSPoint) {
6026 // if (m_animationActive) return;
6027 // Develop a uniform length for course predictor line dash length, based on
6028 // physical display size Use this reference length to size all other graphics
6029 // elements
6030 float ref_dim = m_display_size_mm / 24;
6031 ref_dim = wxMin(ref_dim, 12);
6032 ref_dim = wxMax(ref_dim, 6);
6033
6034 wxColour cPred;
6035 cPred.Set(g_cog_predictor_color);
6036 if (cPred == wxNullColour) cPred = PredColor();
6037
6038 // Establish some graphic element line widths dependent on the platform
6039 // display resolution
6040 // double nominal_line_width_pix = wxMax(1.0,
6041 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
6042 // not less than 1 pixel
6043 double nominal_line_width_pix = wxMax(
6044 1.0,
6045 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
6046
6047 // If the calculated value is greater than the config file spec value, then
6048 // use it.
6049 if (nominal_line_width_pix > g_cog_predictor_width)
6050 g_cog_predictor_width = nominal_line_width_pix;
6051
6052 // Calculate ownship Position Predictor
6053 wxPoint lPredPoint, lHeadPoint;
6054
6055 float pCog = std::isnan(gCog) ? 0 : gCog;
6056 float pSog = std::isnan(gSog) ? 0 : gSog;
6057
6058 double pred_lat, pred_lon;
6059 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
6060 &pred_lat, &pred_lon);
6061 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
6062
6063 // test to catch the case where COG/HDG line crosses the screen
6064 LLBBox box;
6065
6066 // Should we draw the Head vector?
6067 // Compare the points lHeadPoint and lPredPoint
6068 // If they differ by more than n pixels, and the head vector is valid, then
6069 // render the head vector
6070
6071 float ndelta_pix = 10.;
6072 double hdg_pred_lat, hdg_pred_lon;
6073 bool b_render_hdt = false;
6074 if (!std::isnan(gHdt)) {
6075 // Calculate ownship Heading pointer as a predictor
6076 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6077 &hdg_pred_lon);
6078 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6079 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6080 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6081 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6082 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6083 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6084 }
6085 }
6086
6087 // draw course over ground if they are longer than the ship
6088 wxPoint lShipMidPoint;
6089 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6090 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6091 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6092 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6093
6094 if (lpp >= img_height / 2) {
6095 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6096 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6097 !std::isnan(gSog)) {
6098 // COG Predictor
6099 float dash_length = ref_dim;
6100 wxDash dash_long[2];
6101 dash_long[0] =
6102 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6103 g_cog_predictor_width); // Long dash , in mm <---------+
6104 dash_long[1] = dash_long[0] / 2.0; // Short gap
6105
6106 // On ultra-hi-res displays, do not allow the dashes to be greater than
6107 // 250, since it is defined as (char)
6108 if (dash_length > 250.) {
6109 dash_long[0] = 250. / g_cog_predictor_width;
6110 dash_long[1] = dash_long[0] / 2;
6111 }
6112
6113 wxPen ppPen2(cPred, g_cog_predictor_width,
6114 (wxPenStyle)g_cog_predictor_style);
6115 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6116 ppPen2.SetDashes(2, dash_long);
6117 dc.SetPen(ppPen2);
6118 dc.StrokeLine(
6119 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6120 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6121
6122 if (g_cog_predictor_width > 1) {
6123 float line_width = g_cog_predictor_width / 3.;
6124
6125 wxDash dash_long3[2];
6126 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6127 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6128
6129 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6130 (wxPenStyle)g_cog_predictor_style);
6131 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6132 ppPen3.SetDashes(2, dash_long3);
6133 dc.SetPen(ppPen3);
6134 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6135 lGPSPoint.m_y + GPSOffsetPixels.y,
6136 lPredPoint.x + GPSOffsetPixels.x,
6137 lPredPoint.y + GPSOffsetPixels.y);
6138 }
6139
6140 if (g_cog_predictor_endmarker) {
6141 // Prepare COG predictor endpoint icon
6142 double png_pred_icon_scale_factor = .4;
6143 if (g_ShipScaleFactorExp > 1.0)
6144 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6145 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6146
6147 wxPoint icon[4];
6148
6149 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6150 (float)(lPredPoint.x - lShipMidPoint.x));
6151 cog_rad += (float)PI;
6152
6153 for (int i = 0; i < 4; i++) {
6154 int j = i * 2;
6155 double pxa = (double)(s_png_pred_icon[j]);
6156 double pya = (double)(s_png_pred_icon[j + 1]);
6157
6158 pya *= png_pred_icon_scale_factor;
6159 pxa *= png_pred_icon_scale_factor;
6160
6161 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6162 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6163
6164 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6165 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6166 }
6167
6168 // Render COG endpoint icon
6169 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6170 wxPENSTYLE_SOLID);
6171 dc.SetPen(ppPen1);
6172 dc.SetBrush(wxBrush(cPred));
6173
6174 dc.StrokePolygon(4, icon);
6175 }
6176 }
6177 }
6178
6179 // HDT Predictor
6180 if (b_render_hdt) {
6181 float hdt_dash_length = ref_dim * 0.4;
6182
6183 cPred.Set(g_ownship_HDTpredictor_color);
6184 if (cPred == wxNullColour) cPred = PredColor();
6185 float hdt_width =
6186 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6187 : g_cog_predictor_width * 0.8);
6188 wxDash dash_short[2];
6189 dash_short[0] =
6190 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6191 hdt_width); // Short dash , in mm <---------+
6192 dash_short[1] =
6193 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6194 hdt_width); // Short gap |
6195
6196 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6197 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6198 ppPen2.SetDashes(2, dash_short);
6199
6200 dc.SetPen(ppPen2);
6201 dc.StrokeLine(
6202 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6203 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6204
6205 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6206 dc.SetPen(ppPen1);
6207 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6208
6209 if (g_ownship_HDTpredictor_endmarker) {
6210 double nominal_circle_size_pixels = wxMax(
6211 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6212
6213 // Scale the circle to ChartScaleFactor, slightly softened....
6214 if (g_ShipScaleFactorExp > 1.0)
6215 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6216
6217 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6218 lHeadPoint.y + GPSOffsetPixels.y,
6219 nominal_circle_size_pixels / 2);
6220 }
6221 }
6222
6223 // Draw radar rings if activated
6224 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6225 double factor = 1.00;
6226 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6227 factor = 1 / 1.852;
6228 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6229 if (std::isnan(gSog))
6230 factor = 0.0;
6231 else
6232 factor = gSog / 60;
6233 }
6234 factor *= g_fNavAidRadarRingsStep;
6235
6236 double tlat, tlon;
6237 wxPoint r;
6238 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6239 GetCanvasPointPix(tlat, tlon, &r);
6240
6241 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6242 pow((double)(lGPSPoint.m_y - r.y), 2));
6243 int pix_radius = (int)lpp;
6244
6245 wxColor rangeringcolour =
6246 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6247
6248 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6249
6250 dc.SetPen(ppPen1);
6251 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6252
6253 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6254 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6255 }
6256}
6257
6258#if ocpnUSE_GL
6259void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6260bool ChartCanvas::CanAccelerateGlPanning() {
6261 return GetglCanvas()->CanAcceleratePanning();
6262}
6263void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6264
6265#else
6266void ChartCanvas::ResetGlGridFont() {}
6267bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6268void ChartCanvas::SetupGlCompression() {}
6269#endif
6270
6271void ChartCanvas::ComputeShipScaleFactor(
6272 float icon_hdt, int ownShipWidth, int ownShipLength,
6273 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6274 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6275 float screenResolution = m_pix_per_mm;
6276
6277 // Calculate the true ship length in exact pixels
6278 double ship_bow_lat, ship_bow_lon;
6279 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6280 &ship_bow_lat, &ship_bow_lon);
6281 wxPoint lShipBowPoint;
6282 wxPoint2DDouble b_point =
6283 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6284 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6285
6286 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6287 powf((float)(b_point.m_y - a_point.m_y), 2));
6288
6289 // And in mm
6290 float shipLength_mm = shipLength_px / screenResolution;
6291
6292 // Set minimum ownship drawing size
6293 float ownship_min_mm = g_n_ownship_min_mm;
6294 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6295
6296 // Calculate Nautical Miles distance from midships to gps antenna
6297 float hdt_ant = icon_hdt + 180.;
6298 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6299 float dx = g_n_gps_antenna_offset_x / 1852.;
6300 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6301 {
6302 hdt_ant = icon_hdt;
6303 dy = -dy;
6304 }
6305
6306 // If the drawn ship size is going to be clamped, adjust the gps antenna
6307 // offsets
6308 if (shipLength_mm < ownship_min_mm) {
6309 dy /= shipLength_mm / ownship_min_mm;
6310 dx /= shipLength_mm / ownship_min_mm;
6311 }
6312
6313 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6314
6315 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6316 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6317 &ship_mid_lon1);
6318
6319 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6320 &lShipMidPoint);
6321
6322 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6323 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6324
6325 float scale_factor = shipLength_px / ownShipLength;
6326
6327 // Calculate a scale factor that would produce a reasonably sized icon
6328 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6329
6330 // And choose the correct one
6331 scale_factor = wxMax(scale_factor, scale_factor_min);
6332
6333 scale_factor_y = scale_factor;
6334 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6335 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6336}
6337
6338void ChartCanvas::ShipDraw(ocpnDC &dc) {
6339 if (!GetVP().IsValid()) return;
6340
6341 wxPoint GPSOffsetPixels(0, 0);
6342 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6343
6344 // COG/SOG may be undefined in NMEA data stream
6345 float pCog = std::isnan(gCog) ? 0 : gCog;
6346 float pSog = std::isnan(gSog) ? 0 : gSog;
6347
6348 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6349
6350 lShipMidPoint = lGPSPoint;
6351
6352 // Draw the icon rotated to the COG
6353 // or to the Hdt if available
6354 float icon_hdt = pCog;
6355 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6356
6357 // COG may be undefined in NMEA data stream
6358 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6359
6360 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6361 // predictor
6362 double osd_head_lat, osd_head_lon;
6363 wxPoint osd_head_point;
6364
6365 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6366 &osd_head_lon);
6367
6368 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6369
6370 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6371 (float)(osd_head_point.x - lShipMidPoint.m_x));
6372 icon_rad += (float)PI;
6373
6374 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6375
6376 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6377 // nominal size and is just barely outside the viewport ....
6378 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6379
6380 // TODO: fix to include actual size of boat that will be rendered
6381 int img_height = 0;
6382 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6383 if (GetVP().chart_scale >
6384 300000) // According to S52, this should be 50,000
6385 {
6386 ShipDrawLargeScale(dc, lShipMidPoint);
6387 img_height = 20;
6388 } else {
6389 wxImage pos_image;
6390
6391 // Substitute user ownship image if found
6392 if (m_pos_image_user)
6393 pos_image = m_pos_image_user->Copy();
6394 else if (SHIP_NORMAL == m_ownship_state)
6395 pos_image = m_pos_image_red->Copy();
6396 if (SHIP_LOWACCURACY == m_ownship_state)
6397 pos_image = m_pos_image_yellow->Copy();
6398 else if (SHIP_NORMAL != m_ownship_state)
6399 pos_image = m_pos_image_grey->Copy();
6400
6401 // Substitute user ownship image if found
6402 if (m_pos_image_user) {
6403 pos_image = m_pos_image_user->Copy();
6404
6405 if (SHIP_LOWACCURACY == m_ownship_state)
6406 pos_image = m_pos_image_user_yellow->Copy();
6407 else if (SHIP_NORMAL != m_ownship_state)
6408 pos_image = m_pos_image_user_grey->Copy();
6409 }
6410
6411 img_height = pos_image.GetHeight();
6412
6413 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6414 g_OwnShipIconType > 0) // use large ship
6415 {
6416 int ownShipWidth = 22; // Default values from s_ownship_icon
6417 int ownShipLength = 84;
6418 if (g_OwnShipIconType == 1) {
6419 ownShipWidth = pos_image.GetWidth();
6420 ownShipLength = pos_image.GetHeight();
6421 }
6422
6423 float scale_factor_x, scale_factor_y;
6424 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6425 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6426 scale_factor_x, scale_factor_y);
6427
6428 if (g_OwnShipIconType == 1) { // Scaled bitmap
6429 pos_image.Rescale(ownShipWidth * scale_factor_x,
6430 ownShipLength * scale_factor_y,
6431 wxIMAGE_QUALITY_HIGH);
6432 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6433 wxImage rot_image =
6434 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6435
6436 // Simple sharpening algorithm.....
6437 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6438 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6439 if (rot_image.GetAlpha(ip, jp) > 64)
6440 rot_image.SetAlpha(ip, jp, 255);
6441
6442 wxBitmap os_bm(rot_image);
6443
6444 int w = os_bm.GetWidth();
6445 int h = os_bm.GetHeight();
6446 img_height = h;
6447
6448 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6449 lShipMidPoint.m_y - h / 2, true);
6450
6451 // Maintain dirty box,, missing in __WXMSW__ library
6452 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6453 lShipMidPoint.m_y - h / 2);
6454 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6455 lShipMidPoint.m_y - h / 2 + h);
6456 }
6457
6458 else if (g_OwnShipIconType == 2) { // Scaled Vector
6459 wxPoint ownship_icon[10];
6460
6461 for (int i = 0; i < 10; i++) {
6462 int j = i * 2;
6463 float pxa = (float)(s_ownship_icon[j]);
6464 float pya = (float)(s_ownship_icon[j + 1]);
6465 pya *= scale_factor_y;
6466 pxa *= scale_factor_x;
6467
6468 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6469 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6470
6471 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6472 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6473 }
6474
6475 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6476 dc.SetPen(ppPen1);
6477 dc.SetBrush(wxBrush(ShipColor()));
6478
6479 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6480
6481 // draw reference point (midships) cross
6482 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6483 ownship_icon[7].y);
6484 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6485 ownship_icon[9].y);
6486 }
6487
6488 img_height = ownShipLength * scale_factor_y;
6489
6490 // Reference point, where the GPS antenna is
6491 int circle_rad = 3;
6492 if (m_pos_image_user) circle_rad = 1;
6493
6494 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6495 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6496 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6497 } else { // Fixed bitmap icon.
6498 /* non opengl, or suboptimal opengl via ocpndc: */
6499 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6500 wxImage rot_image =
6501 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6502
6503 // Simple sharpening algorithm.....
6504 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6505 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6506 if (rot_image.GetAlpha(ip, jp) > 64)
6507 rot_image.SetAlpha(ip, jp, 255);
6508
6509 wxBitmap os_bm(rot_image);
6510
6511 if (g_ShipScaleFactorExp > 1) {
6512 wxImage scaled_image = os_bm.ConvertToImage();
6513 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6514 1.0; // soften the scale factor a bit
6515 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6516 scaled_image.GetHeight() * factor,
6517 wxIMAGE_QUALITY_HIGH));
6518 }
6519 int w = os_bm.GetWidth();
6520 int h = os_bm.GetHeight();
6521 img_height = h;
6522
6523 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6524 lShipMidPoint.m_y - h / 2, true);
6525
6526 // Reference point, where the GPS antenna is
6527 int circle_rad = 3;
6528 if (m_pos_image_user) circle_rad = 1;
6529
6530 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6531 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6532 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6533
6534 // Maintain dirty box,, missing in __WXMSW__ library
6535 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6536 lShipMidPoint.m_y - h / 2);
6537 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6538 lShipMidPoint.m_y - h / 2 + h);
6539 }
6540 } // ownship draw
6541 }
6542
6543 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6544}
6545
6546/* @ChartCanvas::CalcGridSpacing
6547 **
6548 ** Calculate the major and minor spacing between the lat/lon grid
6549 **
6550 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6551 *window
6552 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6553 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6554 ** @return [void]
6555 */
6556void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6557 float &MinorSpacing) {
6558 // table for calculating the distance between the grids
6559 // [0] view_scale ppm
6560 // [1] spacing between major grid lines in degrees
6561 // [2] spacing between minor grid lines in degrees
6562 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6563 {.000001f, 45.0f, 15.0f},
6564 {.0002f, 30.0f, 10.0f},
6565 {.0003f, 10.0f, 2.0f},
6566 {.0008f, 5.0f, 1.0f},
6567 {.001f, 2.0f, 30.0f / 60.0f},
6568 {.003f, 1.0f, 20.0f / 60.0f},
6569 {.006f, 0.5f, 10.0f / 60.0f},
6570 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6571 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6572 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6573 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6574 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6575 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6576 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6577 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6578
6579 unsigned int tabi;
6580 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6581 if (view_scale_ppm < lltab[tabi][0]) break;
6582 MajorSpacing = lltab[tabi][1]; // major latitude distance
6583 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6584 return;
6585}
6586/* @ChartCanvas::CalcGridText *************************************
6587 **
6588 ** Calculates text to display at the major grid lines
6589 **
6590 ** @param [r] latlon [float] latitude or longitude of grid line
6591 ** @param [r] spacing [float] distance between two major grid lines
6592 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6593 **
6594 ** @return
6595 */
6596
6597wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6598 int deg = (int)fabs(latlon); // degrees
6599 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6600 char postfix;
6601
6602 // calculate postfix letter (NSEW)
6603 if (latlon > 0.0) {
6604 if (bPostfix) {
6605 postfix = 'N';
6606 } else {
6607 postfix = 'E';
6608 }
6609 } else if (latlon < 0.0) {
6610 if (bPostfix) {
6611 postfix = 'S';
6612 } else {
6613 postfix = 'W';
6614 }
6615 } else {
6616 postfix = ' '; // no postfix for equator and greenwich
6617 }
6618 // calculate text, display minutes only if spacing is smaller than one degree
6619
6620 wxString ret;
6621 if (spacing >= 1.0) {
6622 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6623 } else if (spacing >= (1.0 / 60.0)) {
6624 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6625 } else {
6626 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6627 }
6628
6629 return ret;
6630}
6631
6632/* @ChartCanvas::GridDraw *****************************************
6633 **
6634 ** Draws major and minor Lat/Lon Grid on the chart
6635 ** - distance between Grid-lm ines are calculated automatic
6636 ** - major grid lines will be across the whole chart window
6637 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6638 **
6639 ** @param [w] dc [wxDC&] the wx drawing context
6640 **
6641 ** @return [void]
6642 ************************************************************************/
6643void ChartCanvas::GridDraw(ocpnDC &dc) {
6644 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6645
6646 double nlat, elon, slat, wlon;
6647 float lat, lon;
6648 float dlon;
6649 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6650 wxCoord w, h;
6651 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6652 dc.SetPen(GridPen);
6653 if (!m_pgridFont) SetupGridFont();
6654 dc.SetFont(*m_pgridFont);
6655 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6656
6657 w = m_canvas_width;
6658 h = m_canvas_height;
6659
6660 GetCanvasPixPoint(0, 0, nlat,
6661 wlon); // get lat/lon of upper left point of the window
6662 GetCanvasPixPoint(w, h, slat,
6663 elon); // get lat/lon of lower right point of the window
6664 dlon =
6665 elon -
6666 wlon; // calculate how many degrees of longitude are shown in the window
6667 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6668 {
6669 dlon = dlon + 360.0;
6670 }
6671 // calculate distance between latitude grid lines
6672 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6673
6674 // calculate position of first major latitude grid line
6675 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6676
6677 // Draw Major latitude grid lines and text
6678 while (lat < nlat) {
6679 wxPoint r;
6680 wxString st =
6681 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6682 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6683 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6684 dc.DrawText(st, 0, r.y); // draw text
6685 lat = lat + gridlatMajor;
6686
6687 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6688 }
6689
6690 // calculate position of first minor latitude grid line
6691 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6692
6693 // Draw minor latitude grid lines
6694 while (lat < nlat) {
6695 wxPoint r;
6696 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6697 dc.DrawLine(0, r.y, 10, r.y, false);
6698 dc.DrawLine(w - 10, r.y, w, r.y, false);
6699 lat = lat + gridlatMinor;
6700 }
6701
6702 // calculate distance between grid lines
6703 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6704
6705 // calculate position of first major latitude grid line
6706 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6707
6708 // draw major longitude grid lines
6709 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6710 wxPoint r;
6711 wxString st = CalcGridText(lon, gridlonMajor, false);
6712 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6713 dc.DrawLine(r.x, 0, r.x, h, false);
6714 dc.DrawText(st, r.x, 0);
6715 lon = lon + gridlonMajor;
6716 if (lon > 180.0) {
6717 lon = lon - 360.0;
6718 }
6719
6720 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6721 }
6722
6723 // calculate position of first minor longitude grid line
6724 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6725 // draw minor longitude grid lines
6726 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6727 wxPoint r;
6728 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6729 dc.DrawLine(r.x, 0, r.x, 10, false);
6730 dc.DrawLine(r.x, h - 10, r.x, h, false);
6731 lon = lon + gridlonMinor;
6732 if (lon > 180.0) {
6733 lon = lon - 360.0;
6734 }
6735 }
6736}
6737
6738void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6739 if (0 ) {
6740 double blat, blon, tlat, tlon;
6741 wxPoint r;
6742
6743 int x_origin = m_bDisplayGrid ? 60 : 20;
6744 int y_origin = m_canvas_height - 50;
6745
6746 float dist;
6747 int count;
6748 wxPen pen1, pen2;
6749
6750 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6751 {
6752 dist = 10.0;
6753 count = 5;
6754 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6755 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6756 } else // Draw 1 mile scale as SCALEB10
6757 {
6758 dist = 1.0;
6759 count = 10;
6760 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6761 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6762 }
6763
6764 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6765 double rotation = -VPoint.rotation;
6766
6767 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6768 GetCanvasPointPix(tlat, tlon, &r);
6769 int l1 = (y_origin - r.y) / count;
6770
6771 for (int i = 0; i < count; i++) {
6772 int y = l1 * i;
6773 if (i & 1)
6774 dc.SetPen(pen1);
6775 else
6776 dc.SetPen(pen2);
6777
6778 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6779 }
6780 } else {
6781 double blat, blon, tlat, tlon;
6782
6783 int x_origin = 5.0 * GetPixPerMM();
6784 int chartbar_height = GetChartbarHeight();
6785 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6786 // if (style->chartStatusWindowTransparent)
6787 // chartbar_height = 0;
6788 int y_origin = m_canvas_height - chartbar_height - 5;
6789#ifdef __WXOSX__
6790 if (!g_bopengl)
6791 y_origin =
6792 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6793#endif
6794
6795 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6796 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6797
6798 double d;
6799 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6800 d /= 2;
6801
6802 int unit = g_iDistanceFormat;
6803 if (d < .5 &&
6804 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6805 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6806
6807 // nice number
6808 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6809 float places = floor(logdist), rem = logdist - places;
6810 dist = pow(10, places);
6811
6812 if (rem < .2)
6813 dist /= 5;
6814 else if (rem < .5)
6815 dist /= 2;
6816
6817 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6818 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6819 double rotation = -VPoint.rotation;
6820
6821 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6822 &tlat, &tlon);
6823 wxPoint r;
6824 GetCanvasPointPix(tlat, tlon, &r);
6825 int l1 = r.x - x_origin;
6826
6827 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6828 12); // Store this for later reference
6829
6830 dc.SetPen(pen1);
6831
6832 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6833 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6834 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6835
6836 if (!m_pgridFont) SetupGridFont();
6837 dc.SetFont(*m_pgridFont);
6838 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6839 int w, h;
6840 dc.GetTextExtent(s, &w, &h);
6841 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6842 if (g_bopengl) {
6843 w /= dpi_factor;
6844 h /= dpi_factor;
6845 }
6846 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6847 }
6848}
6849
6850void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6851 // Constants?
6852 double da_min = 2.;
6853 double da_max = 6.;
6854 double ra_min = 0.;
6855 double ra_max = 40.;
6856
6857 wxPen pen_save = dc.GetPen();
6858
6859 wxDateTime now = wxDateTime::Now();
6860
6861 dc.SetPen(pen);
6862
6863 int x0, y0, x1, y1;
6864
6865 x0 = x1 = x + radius; // Start point
6866 y0 = y1 = y;
6867 double angle = 0.;
6868 int i = 0;
6869
6870 while (angle < 360.) {
6871 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6872 angle += da;
6873
6874 if (angle > 360.) angle = 360.;
6875
6876 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6877
6878 double r;
6879 if (i & 1)
6880 r = radius + ra;
6881 else
6882 r = radius - ra;
6883
6884 x1 = (int)(x + cos(angle * PI / 180.) * r);
6885 y1 = (int)(y + sin(angle * PI / 180.) * r);
6886
6887 dc.DrawLine(x0, y0, x1, y1);
6888
6889 x0 = x1;
6890 y0 = y1;
6891
6892 i++;
6893 }
6894
6895 dc.DrawLine(x + radius, y, x1, y1); // closure
6896
6897 dc.SetPen(pen_save);
6898}
6899
6900static bool bAnchorSoundPlaying = false;
6901
6902static void onAnchorSoundFinished(void *ptr) {
6903 o_sound::g_anchorwatch_sound->UnLoad();
6904 bAnchorSoundPlaying = false;
6905}
6906
6907void ChartCanvas::AlertDraw(ocpnDC &dc) {
6908 using namespace o_sound;
6909 // Visual and audio alert for anchorwatch goes here
6910 bool play_sound = false;
6911 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6912 if (AnchorAlertOn1) {
6913 wxPoint TargetPoint;
6915 &TargetPoint);
6916 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6917 TargetPoint.y, 100);
6918 play_sound = true;
6919 }
6920 } else
6921 AnchorAlertOn1 = false;
6922
6923 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6924 if (AnchorAlertOn2) {
6925 wxPoint TargetPoint;
6927 &TargetPoint);
6928 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6929 TargetPoint.y, 100);
6930 play_sound = true;
6931 }
6932 } else
6933 AnchorAlertOn2 = false;
6934
6935 if (play_sound) {
6936 if (!bAnchorSoundPlaying) {
6937 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6938 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6939 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6940 if (g_anchorwatch_sound->IsOk()) {
6941 bAnchorSoundPlaying = true;
6942 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6943 g_anchorwatch_sound->Play();
6944 }
6945 }
6946 }
6947}
6948
6949void ChartCanvas::UpdateShips() {
6950 // Get the rectangle in the current dc which bounds the "ownship" symbol
6951
6952 wxClientDC dc(this);
6953 // Sometimes during startup the dc x or y size is zero, which causes the
6954 // bitmap creation to fail. Just skip the update in this case.
6955 if (!dc.IsOk() || dc.GetSize().x < 1 || dc.GetSize().y < 1) return;
6956
6957 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6958 if (!test_bitmap.IsOk()) return;
6959
6960 wxMemoryDC temp_dc(test_bitmap);
6961
6962 temp_dc.ResetBoundingBox();
6963 temp_dc.DestroyClippingRegion();
6964 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6965
6966 // Draw the ownship on the temp_dc
6967 ocpnDC ocpndc = ocpnDC(temp_dc);
6968 ShipDraw(ocpndc);
6969
6970 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6971 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6972 if (p) {
6973 wxPoint px;
6974 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6975 ocpndc.CalcBoundingBox(px.x, px.y);
6976 }
6977 }
6978
6979 ship_draw_rect =
6980 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6981 temp_dc.MaxY() - temp_dc.MinY());
6982
6983 wxRect own_ship_update_rect = ship_draw_rect;
6984
6985 if (!own_ship_update_rect.IsEmpty()) {
6986 // The required invalidate rectangle is the union of the last drawn
6987 // rectangle and this drawn rectangle
6988 own_ship_update_rect.Union(ship_draw_last_rect);
6989 own_ship_update_rect.Inflate(2);
6990 }
6991
6992 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6993
6994 ship_draw_last_rect = ship_draw_rect;
6995
6996 temp_dc.SelectObject(wxNullBitmap);
6997}
6998
6999void ChartCanvas::UpdateAlerts() {
7000 // Get the rectangle in the current dc which bounds the detected Alert
7001 // targets
7002
7003 // Use this dc
7004 wxClientDC dc(this);
7005
7006 // Get dc boundary
7007 int sx, sy;
7008 dc.GetSize(&sx, &sy);
7009
7010 // Need a bitmap
7011 wxBitmap test_bitmap(sx, sy, -1);
7012
7013 // Create a memory DC
7014 wxMemoryDC temp_dc;
7015 temp_dc.SelectObject(test_bitmap);
7016
7017 temp_dc.ResetBoundingBox();
7018 temp_dc.DestroyClippingRegion();
7019 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7020
7021 // Draw the Alert Targets on the temp_dc
7022 ocpnDC ocpndc = ocpnDC(temp_dc);
7023 AlertDraw(ocpndc);
7024
7025 // Retrieve the drawing extents
7026 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
7027 temp_dc.MaxX() - temp_dc.MinX(),
7028 temp_dc.MaxY() - temp_dc.MinY());
7029
7030 if (!alert_rect.IsEmpty())
7031 alert_rect.Inflate(2); // clear all drawing artifacts
7032
7033 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
7034 // The required invalidate rectangle is the union of the last drawn
7035 // rectangle and this drawn rectangle
7036 wxRect alert_update_rect = alert_draw_rect;
7037 alert_update_rect.Union(alert_rect);
7038
7039 // Invalidate the rectangular region
7040 RefreshRect(alert_update_rect, false);
7041 }
7042
7043 // Save this rectangle for next time
7044 alert_draw_rect = alert_rect;
7045
7046 temp_dc.SelectObject(wxNullBitmap); // clean up
7047}
7048
7049void ChartCanvas::UpdateAIS() {
7050 if (!g_pAIS) return;
7051
7052 // Get the rectangle in the current dc which bounds the detected AIS targets
7053
7054 // Use this dc
7055 wxClientDC dc(this);
7056
7057 // Get dc boundary
7058 int sx, sy;
7059 dc.GetSize(&sx, &sy);
7060
7061 wxRect ais_rect;
7062
7063 // How many targets are there?
7064
7065 // If more than "some number", it will be cheaper to refresh the entire
7066 // screen than to build update rectangles for each target.
7067 if (g_pAIS->GetTargetList().size() > 10) {
7068 ais_rect = wxRect(0, 0, sx, sy); // full screen
7069 } else {
7070 // Need a bitmap
7071 wxBitmap test_bitmap(sx, sy, -1);
7072
7073 // Create a memory DC
7074 wxMemoryDC temp_dc;
7075 temp_dc.SelectObject(test_bitmap);
7076
7077 temp_dc.ResetBoundingBox();
7078 temp_dc.DestroyClippingRegion();
7079 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7080
7081 // Draw the AIS Targets on the temp_dc
7082 ocpnDC ocpndc = ocpnDC(temp_dc);
7083 AISDraw(ocpndc, GetVP(), this);
7084 AISDrawAreaNotices(ocpndc, GetVP(), this);
7085
7086 // Retrieve the drawing extents
7087 ais_rect =
7088 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7089 temp_dc.MaxY() - temp_dc.MinY());
7090
7091 if (!ais_rect.IsEmpty())
7092 ais_rect.Inflate(2); // clear all drawing artifacts
7093
7094 temp_dc.SelectObject(wxNullBitmap); // clean up
7095 }
7096
7097 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7098 // The required invalidate rectangle is the union of the last drawn
7099 // rectangle and this drawn rectangle
7100 wxRect ais_update_rect = ais_draw_rect;
7101 ais_update_rect.Union(ais_rect);
7102
7103 // Invalidate the rectangular region
7104 RefreshRect(ais_update_rect, false);
7105 }
7106
7107 // Save this rectangle for next time
7108 ais_draw_rect = ais_rect;
7109}
7110
7111void ChartCanvas::ToggleCPAWarn() {
7112 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7113 wxString mess;
7114 if (g_bCPAWarn) {
7115 g_bTCPA_Max = true;
7116 mess = _("ON");
7117 } else {
7118 g_bTCPA_Max = false;
7119 mess = _("OFF");
7120 }
7121 // Print to status bar if available.
7122 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7123 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7124 } else {
7125 if (!g_AisFirstTimeUse) {
7126 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7127 _("CPA") + " " + mess, 4, 4);
7128 }
7129 }
7130}
7131
7132void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7133
7134void ChartCanvas::OnSize(wxSizeEvent &event) {
7135 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7136 // GetClientSize returns the size of the canvas area in logical pixels.
7137 GetClientSize(&m_canvas_width, &m_canvas_height);
7138
7139#ifdef __WXOSX__
7140 // Support scaled HDPI displays.
7141 m_displayScale = GetContentScaleFactor();
7142#endif
7143
7144 // Convert to physical pixels.
7145 m_canvas_width *= m_displayScale;
7146 m_canvas_height *= m_displayScale;
7147
7148 // Resize the current viewport
7149 VPoint.pix_width = m_canvas_width;
7150 VPoint.pix_height = m_canvas_height;
7151 VPoint.SetPixelScale(m_displayScale);
7152
7153 // Get some canvas metrics
7154
7155 // Rescale to current value, in order to rebuild VPoint data
7156 // structures for new canvas size
7158
7159 m_absolute_min_scale_ppm =
7160 m_canvas_width /
7161 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7162
7163 // Inform the parent Frame that I am being resized...
7164 top_frame::Get()->ProcessCanvasResize();
7165
7166 // if MUIBar is active, size the bar
7167 // if(g_useMUI && !m_muiBar){ // rebuild if
7168 // necessary
7169 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7170 // m_muiBarHOSize = m_muiBar->GetSize();
7171 // }
7172
7173 if (m_muiBar) {
7174 SetMUIBarPosition();
7175 UpdateFollowButtonState();
7176 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7177 }
7178
7179 // Set up the scroll margins
7180 xr_margin = m_canvas_width * 95 / 100;
7181 xl_margin = m_canvas_width * 5 / 100;
7182 yt_margin = m_canvas_height * 5 / 100;
7183 yb_margin = m_canvas_height * 95 / 100;
7184
7185 if (m_pQuilt)
7186 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7187
7188 // Resize the scratch BM
7189 delete pscratch_bm;
7190 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7191 m_brepaint_piano = true;
7192
7193 // Resize the Route Calculation BM
7194 m_dc_route.SelectObject(wxNullBitmap);
7195 delete proute_bm;
7196 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7197 m_dc_route.SelectObject(*proute_bm);
7198
7199 // Resize the saved Bitmap
7200 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7201
7202 // Resize the working Bitmap
7203 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7204
7205 // Rescale again, to capture all the changes for new canvas size
7207
7208#ifdef ocpnUSE_GL
7209 if (/*g_bopengl &&*/ m_glcc) {
7210 // FIXME (dave) This can go away?
7211 m_glcc->OnSize(event);
7212 }
7213#endif
7214
7215 FormatPianoKeys();
7216 // Invalidate the whole window
7217 ReloadVP();
7218}
7219
7220void ChartCanvas::ProcessNewGUIScale() {
7221 // m_muiBar->Hide();
7222 delete m_muiBar;
7223 m_muiBar = 0;
7224
7225 CreateMUIBar();
7226}
7227
7228void ChartCanvas::CreateMUIBar() {
7229 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7230 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7231 m_muiBar->SetColorScheme(m_cs);
7232 m_muiBarHOSize = m_muiBar->m_size;
7233 }
7234
7235 if (m_muiBar) {
7236 // We need to update the m_bENCGroup flag, not least for the initial
7237 // creation of a MUIBar
7238 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7239
7240 SetMUIBarPosition();
7241 UpdateFollowButtonState();
7242 m_muiBar->UpdateDynamicValues();
7243 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7244 }
7245}
7246
7247void ChartCanvas::SetMUIBarPosition() {
7248 // if MUIBar is active, size the bar
7249 if (m_muiBar) {
7250 // We estimate the piano width based on the canvas width
7251 int pianoWidth = GetClientSize().x * 0.6f;
7252 // If the piano already exists, we can use its exact width
7253 // if(m_Piano)
7254 // pianoWidth = m_Piano->GetWidth();
7255
7256 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7257 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7258 delete m_muiBar;
7259 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7260 m_muiBar->SetColorScheme(m_cs);
7261 }
7262 }
7263
7264 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7265 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7266 delete m_muiBar;
7267 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7268 m_muiBar->SetColorScheme(m_cs);
7269 }
7270 }
7271
7272 m_muiBar->SetBestPosition();
7273 }
7274}
7275
7276void ChartCanvas::DestroyMuiBar() {
7277 if (m_muiBar) {
7278 delete m_muiBar;
7279 m_muiBar = NULL;
7280 }
7281}
7282
7283void ChartCanvas::ShowCompositeInfoWindow(
7284 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7285 if (n_charts > 0) {
7286 if (NULL == m_pCIWin) {
7287 m_pCIWin = new ChInfoWin(this);
7288 m_pCIWin->Hide();
7289 }
7290
7291 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7292 wxString s;
7293
7294 s = _("Composite of ");
7295
7296 wxString s1;
7297 s1.Printf("%d ", n_charts);
7298 if (n_charts > 1)
7299 s1 += _("charts");
7300 else
7301 s1 += _("chart");
7302 s += s1;
7303 s += '\n';
7304
7305 s1.Printf(_("Chart scale"));
7306 s1 += ": ";
7307 wxString s2;
7308 s2.Printf("1:%d\n", scale);
7309 s += s1;
7310 s += s2;
7311
7312 s1 = _("Zoom in for more information");
7313 s += s1;
7314 s += '\n';
7315
7316 int char_width = s1.Length();
7317 int char_height = 3;
7318
7319 if (g_bChartBarEx) {
7320 s += '\n';
7321 int j = 0;
7322 for (int i : index_vector) {
7323 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7324 wxString path = cte.GetFullSystemPath();
7325 s += path;
7326 s += '\n';
7327 char_height++;
7328 char_width = wxMax(char_width, path.Length());
7329 if (j++ >= 9) break;
7330 }
7331 if (j >= 9) {
7332 s += " .\n .\n .\n";
7333 char_height += 3;
7334 }
7335 s += '\n';
7336 char_height += 1;
7337
7338 char_width += 4; // Fluff
7339 }
7340
7341 m_pCIWin->SetString(s);
7342
7343 m_pCIWin->FitToChars(char_width, char_height);
7344
7345 wxPoint p;
7346 p.x = x / GetContentScaleFactor();
7347 if ((p.x + m_pCIWin->GetWinSize().x) >
7348 (m_canvas_width / GetContentScaleFactor()))
7349 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7350 m_pCIWin->GetWinSize().x) /
7351 2; // centered
7352
7353 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7354 4 - m_pCIWin->GetWinSize().y;
7355
7356 m_pCIWin->dbIndex = 0;
7357 m_pCIWin->chart_scale = 0;
7358 m_pCIWin->SetPosition(p);
7359 m_pCIWin->SetBitmap();
7360 m_pCIWin->Refresh();
7361 m_pCIWin->Show();
7362 }
7363 } else {
7364 HideChartInfoWindow();
7365 }
7366}
7367
7368void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7369 if (dbIndex >= 0) {
7370 if (NULL == m_pCIWin) {
7371 m_pCIWin = new ChInfoWin(this);
7372 m_pCIWin->Hide();
7373 }
7374
7375 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7376 wxString s;
7377 ChartBase *pc = NULL;
7378
7379 // TOCTOU race but worst case will reload chart.
7380 // need to lock it or the background spooler may evict charts in
7381 // OpenChartFromDBAndLock
7382 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7383 pc = ChartData->OpenChartFromDBAndLock(
7384 dbIndex, FULL_INIT); // this must come from cache
7385
7386 int char_width, char_height;
7387 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7388 if (pc) ChartData->UnLockCacheChart(dbIndex);
7389
7390 m_pCIWin->SetString(s);
7391 m_pCIWin->FitToChars(char_width, char_height);
7392
7393 wxPoint p;
7394 p.x = x / GetContentScaleFactor();
7395 if ((p.x + m_pCIWin->GetWinSize().x) >
7396 (m_canvas_width / GetContentScaleFactor()))
7397 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7398 m_pCIWin->GetWinSize().x) /
7399 2; // centered
7400
7401 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7402 4 - m_pCIWin->GetWinSize().y;
7403
7404 m_pCIWin->dbIndex = dbIndex;
7405 m_pCIWin->SetPosition(p);
7406 m_pCIWin->SetBitmap();
7407 m_pCIWin->Refresh();
7408 m_pCIWin->Show();
7409 }
7410 } else {
7411 HideChartInfoWindow();
7412 }
7413}
7414
7415void ChartCanvas::HideChartInfoWindow() {
7416 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7417 m_pCIWin->Hide();
7418 m_pCIWin->Destroy();
7419 m_pCIWin = NULL;
7420
7421#ifdef __ANDROID__
7422 androidForceFullRepaint();
7423#endif
7424 }
7425}
7426
7427void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7428 wxMouseEvent ev(wxEVT_MOTION);
7429 ev.m_x = mouse_x;
7430 ev.m_y = mouse_y;
7431 ev.m_leftDown = mouse_leftisdown;
7432
7433 wxEvtHandler *evthp = GetEventHandler();
7434
7435 ::wxPostEvent(evthp, ev);
7436}
7437
7438void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7439 if ((m_panx_target_final - m_panx_target_now) ||
7440 (m_pany_target_final - m_pany_target_now)) {
7441 DoTimedMovementTarget();
7442 } else
7443 DoTimedMovement();
7444}
7445
7446void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7447
7448bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7449 int delta) {
7450 if (m_disable_edge_pan) return false;
7451
7452 bool bft = false;
7453 int pan_margin = m_canvas_width * margin / 100;
7454 int pan_timer_set = 200;
7455 double pan_delta = GetVP().pix_width * delta / 100;
7456 int pan_x = 0;
7457 int pan_y = 0;
7458
7459 if (x > m_canvas_width - pan_margin) {
7460 bft = true;
7461 pan_x = pan_delta;
7462 }
7463
7464 else if (x < pan_margin) {
7465 bft = true;
7466 pan_x = -pan_delta;
7467 }
7468
7469 if (y < pan_margin) {
7470 bft = true;
7471 pan_y = -pan_delta;
7472 }
7473
7474 else if (y > m_canvas_height - pan_margin) {
7475 bft = true;
7476 pan_y = pan_delta;
7477 }
7478
7479 // Of course, if dragging, and the mouse left button is not down, we must
7480 // stop the event injection
7481 if (bdragging) {
7482 if (!g_btouch) {
7483 wxMouseState state = ::wxGetMouseState();
7484#if wxCHECK_VERSION(3, 0, 0)
7485 if (!state.LeftIsDown())
7486#else
7487 if (!state.LeftDown())
7488#endif
7489 bft = false;
7490 }
7491 }
7492 if ((bft) && !pPanTimer->IsRunning()) {
7493 PanCanvas(pan_x, pan_y);
7494 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7495 return true;
7496 }
7497
7498 // This mouse event must not be due to pan timer event injector
7499 // Mouse is out of the pan zone, so prevent any orphan event injection
7500 if ((!bft) && pPanTimer->IsRunning()) {
7501 pPanTimer->Stop();
7502 }
7503
7504 return (false);
7505}
7506
7507// Look for waypoints at the current position.
7508// Used to determine what a mouse event should act on.
7509
7510void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7511 bool setBeingEdited) {
7512 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7513 m_pRoutePointEditTarget = NULL;
7514 m_pFoundPoint = NULL;
7515
7516 SelectItem *pFind = NULL;
7517 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7518 SelectableItemList SelList = pSelect->FindSelectionList(
7519 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7520 for (SelectItem *pFind : SelList) {
7521 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7522
7523 // Get an array of all routes using this point
7524 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7525 // TODO: delete m_pEditRouteArray after use?
7526
7527 // Use route array to determine actual visibility for the point
7528 bool brp_viz = false;
7529 if (m_pEditRouteArray) {
7530 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7531 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7532 if (pr->IsVisible()) {
7533 brp_viz = true;
7534 break;
7535 }
7536 }
7537 } else
7538 brp_viz = frp->IsVisible(); // isolated point
7539
7540 if (brp_viz) {
7541 // Use route array to rubberband all affected routes
7542 if (m_pEditRouteArray) // Editing Waypoint as part of route
7543 {
7544 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7545 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7546 pr->m_bIsBeingEdited = setBeingEdited;
7547 }
7548 m_bRouteEditing = setBeingEdited;
7549 } else // editing Mark
7550 {
7551 frp->m_bRPIsBeingEdited = setBeingEdited;
7552 m_bMarkEditing = setBeingEdited;
7553 }
7554
7555 m_pRoutePointEditTarget = frp;
7556 m_pFoundPoint = pFind;
7557 break; // out of the while(node)
7558 }
7559 } // for (SelectItem...
7560}
7561std::shared_ptr<HostApi121::PiPointContext>
7562ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7563 // General Right Click
7564 // Look for selectable objects
7565 double slat, slon;
7566 GetCanvasPixPoint(x, y, slat, slon);
7567
7568 SelectItem *pFindAIS = NULL;
7569 SelectItem *pFindRP;
7570 SelectItem *pFindRouteSeg;
7571 SelectItem *pFindTrackSeg;
7572 SelectItem *pFindCurrent = NULL;
7573 SelectItem *pFindTide = NULL;
7574
7575 // Get all the selectable things at the selected point
7576 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7577 if (m_bShowAIS) // look for AIS targets only if they are shown
7578 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7579 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7580 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7581 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7582
7583 if (m_bShowCurrent)
7584 pFindCurrent =
7585 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7586
7587 if (m_bShowTide) // look for tide stations
7588 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7589
7590 int seltype = 0;
7591
7592 // Try for AIS targets first
7593 int FoundAIS_MMSI = 0;
7594 if (pFindAIS) {
7595 FoundAIS_MMSI = pFindAIS->GetUserData();
7596
7597 // Make sure the target data is available
7598 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7599 seltype |= SELTYPE_AISTARGET;
7600 }
7601
7602 // Now the various Route Parts
7603
7604 RoutePoint *FoundRoutePoint = NULL;
7605 Route *SelectedRoute = NULL;
7606
7607 if (pFindRP) {
7608 RoutePoint *pFirstVizPoint = NULL;
7609 RoutePoint *pFoundActiveRoutePoint = NULL;
7610 RoutePoint *pFoundVizRoutePoint = NULL;
7611 Route *pSelectedActiveRoute = NULL;
7612 Route *pSelectedVizRoute = NULL;
7613
7614 // There is at least one routepoint, so get the whole list
7615 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7616 SelectableItemList SelList =
7617 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7618 for (SelectItem *pFindSel : SelList) {
7619 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7620
7621 // Get an array of all routes using this point
7622 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7623
7624 // Use route array (if any) to determine actual visibility for this point
7625 bool brp_viz = false;
7626 if (proute_array) {
7627 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7628 Route *pr = (Route *)proute_array->Item(ir);
7629 if (pr->IsVisible()) {
7630 brp_viz = true;
7631 break;
7632 }
7633 }
7634 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7635 // but still exists as a waypoint
7636 brp_viz = prp->IsVisible(); // so treat as isolated point
7637
7638 } else
7639 brp_viz = prp->IsVisible(); // isolated point
7640
7641 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7642
7643 // Use route array to choose the appropriate route
7644 // Give preference to any active route, otherwise select the first visible
7645 // route in the array for this point
7646 if (proute_array) {
7647 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7648 Route *pr = (Route *)proute_array->Item(ir);
7649 if (pr->m_bRtIsActive) {
7650 pSelectedActiveRoute = pr;
7651 pFoundActiveRoutePoint = prp;
7652 break;
7653 }
7654 }
7655
7656 if (NULL == pSelectedVizRoute) {
7657 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7658 Route *pr = (Route *)proute_array->Item(ir);
7659 if (pr->IsVisible()) {
7660 pSelectedVizRoute = pr;
7661 pFoundVizRoutePoint = prp;
7662 break;
7663 }
7664 }
7665 }
7666
7667 delete proute_array;
7668 }
7669 }
7670
7671 // Now choose the "best" selections
7672 if (pFoundActiveRoutePoint) {
7673 FoundRoutePoint = pFoundActiveRoutePoint;
7674 SelectedRoute = pSelectedActiveRoute;
7675 } else if (pFoundVizRoutePoint) {
7676 FoundRoutePoint = pFoundVizRoutePoint;
7677 SelectedRoute = pSelectedVizRoute;
7678 } else
7679 // default is first visible point in list
7680 FoundRoutePoint = pFirstVizPoint;
7681
7682 if (SelectedRoute) {
7683 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7684 } else if (FoundRoutePoint) {
7685 seltype |= SELTYPE_MARKPOINT;
7686 }
7687
7688 // Highlight the selected point, to verify the proper right click selection
7689#if 0
7690 if (m_pFoundRoutePoint) {
7691 m_pFoundRoutePoint->m_bPtIsSelected = true;
7692 wxRect wp_rect;
7693 RoutePointGui(*m_pFoundRoutePoint)
7694 .CalculateDCRect(m_dc_route, this, &wp_rect);
7695 RefreshRect(wp_rect, true);
7696 }
7697#endif
7698 }
7699
7700 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7701 // routes But call the popup handler with identifier appropriate to the type
7702 if (pFindRouteSeg) // there is at least one select item
7703 {
7704 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7705 SelectableItemList SelList =
7706 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7707
7708 if (NULL == SelectedRoute) // the case where a segment only is selected
7709 {
7710 // Choose the first visible route containing segment in the list
7711 for (SelectItem *pFindSel : SelList) {
7712 Route *pr = (Route *)pFindSel->m_pData3;
7713 if (pr->IsVisible()) {
7714 SelectedRoute = pr;
7715 break;
7716 }
7717 }
7718 }
7719
7720 if (SelectedRoute) {
7721 if (NULL == FoundRoutePoint)
7722 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7723
7724 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7725 seltype |= SELTYPE_ROUTESEGMENT;
7726 }
7727 }
7728
7729 if (pFindTrackSeg) {
7730 m_pSelectedTrack = NULL;
7731 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7732 SelectableItemList SelList =
7733 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7734
7735 // Choose the first visible track containing segment in the list
7736 for (SelectItem *pFindSel : SelList) {
7737 Track *pt = (Track *)pFindSel->m_pData3;
7738 if (pt->IsVisible()) {
7739 m_pSelectedTrack = pt;
7740 break;
7741 }
7742 }
7743 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7744 }
7745
7746 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7747
7748 // Populate the return struct
7749 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7750 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7751 rstruct->object_ident = "";
7752
7753 if (seltype == SELTYPE_AISTARGET) {
7754 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7755 wxString val;
7756 val.Printf("%d", FoundAIS_MMSI);
7757 rstruct->object_ident = val.ToStdString();
7758 } else if (seltype & SELTYPE_MARKPOINT) {
7759 if (FoundRoutePoint) {
7760 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7761 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7762 }
7763 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7764 if (SelectedRoute) {
7765 rstruct->object_type =
7766 HostApi121::PiContextObjectType::kObjectRoutesegment;
7767 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7768 }
7769 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7770 if (m_pSelectedTrack) {
7771 rstruct->object_type =
7772 HostApi121::PiContextObjectType::kObjectTracksegment;
7773 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7774 }
7775 }
7776
7777 return rstruct;
7778}
7779
7780void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7781 if (singleClickEventIsValid) {
7782 const bool wasReplaying = m_isReplayingClickEvent;
7783 m_isReplayingClickEvent = true;
7784 MouseEvent(singleClickEvent);
7785 m_isReplayingClickEvent = wasReplaying;
7786 }
7787
7788 singleClickEventIsValid = false;
7789 m_DoubleClickTimer->Stop();
7790}
7791
7792bool leftIsDown;
7793
7794bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7795 if (!m_bChartDragging && !m_bDrawingRoute) {
7796 /*
7797 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7798 * mouse event coordinates are in logical pixels.
7799 */
7800 if (m_Compass && m_Compass->IsShown()) {
7801 wxRect logicalRect = m_Compass->GetLogicalRect();
7802 bool isInCompass = logicalRect.Contains(event.GetPosition());
7803 if (isInCompass || m_mouseWasInCompass) {
7804 if (m_Compass->MouseEvent(event)) {
7805 cursor_region = CENTER;
7806 if (!g_btouch) SetCanvasCursor(event);
7807 m_mouseWasInCompass = isInCompass;
7808 return true;
7809 }
7810 }
7811 m_mouseWasInCompass = isInCompass;
7812 }
7813
7814 if (m_notification_button && m_notification_button->IsShown()) {
7815 wxRect logicalRect = m_notification_button->GetLogicalRect();
7816 bool isinButton = logicalRect.Contains(event.GetPosition());
7817 if (isinButton) {
7818 SetCursor(*pCursorArrow);
7819 if (event.LeftDown()) HandleNotificationMouseClick();
7820 return true;
7821 }
7822 }
7823
7824 if (MouseEventToolbar(event)) return true;
7825
7826 if (MouseEventChartBar(event)) return true;
7827
7828 if (MouseEventMUIBar(event)) return true;
7829
7830 if (MouseEventIENCBar(event)) return true;
7831 }
7832 return false;
7833}
7834
7835void ChartCanvas::HandleNotificationMouseClick() {
7836 if (!m_NotificationsList) {
7837 m_NotificationsList = new NotificationsList(this);
7838
7839 // calculate best size for Notification list
7840 m_NotificationsList->RecalculateSize();
7841 m_NotificationsList->Hide();
7842 }
7843
7844 if (m_NotificationsList->IsShown()) {
7845 m_NotificationsList->Hide();
7846 } else {
7847 m_NotificationsList->RecalculateSize();
7848 m_NotificationsList->ReloadNotificationList();
7849 m_NotificationsList->Show();
7850 }
7851}
7852bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7853 if (!g_bShowChartBar) return false;
7854
7855 if (!m_Piano->MouseEvent(event)) return false;
7856
7857 cursor_region = CENTER;
7858 if (!g_btouch) SetCanvasCursor(event);
7859 return true;
7860}
7861
7862bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7863 if (!IsPrimaryCanvas()) return false;
7864
7865 if (g_MainToolbar) {
7866 if (!g_MainToolbar->MouseEvent(event))
7867 return false;
7868 else
7869 g_MainToolbar->RefreshToolbar();
7870 }
7871
7872 cursor_region = CENTER;
7873 if (!g_btouch) SetCanvasCursor(event);
7874 return true;
7875}
7876
7877bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7878 if (!IsPrimaryCanvas()) return false;
7879
7880 if (g_iENCToolbar) {
7881 if (!g_iENCToolbar->MouseEvent(event))
7882 return false;
7883 else {
7884 g_iENCToolbar->RefreshToolbar();
7885 return true;
7886 }
7887 }
7888 return false;
7889}
7890
7891bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7892 if (m_muiBar) {
7893 if (!m_muiBar->MouseEvent(event)) return false;
7894 }
7895
7896 cursor_region = CENTER;
7897 if (!g_btouch) SetCanvasCursor(event);
7898 if (m_muiBar)
7899 return true;
7900 else
7901 return false;
7902}
7903
7904bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7905 int x, y;
7906
7907 bool bret = false;
7908
7909 event.GetPosition(&x, &y);
7910
7911 x *= m_displayScale;
7912 y *= m_displayScale;
7913
7914 m_MouseDragging = event.Dragging();
7915
7916 // Some systems produce null drag events, where the pointer position has not
7917 // changed from the previous value. Detect this case, and abort further
7918 // processing (FS#1748)
7919#ifdef __WXMSW__
7920 if (event.Dragging()) {
7921 if ((x == mouse_x) && (y == mouse_y)) return true;
7922 }
7923#endif
7924
7925 mouse_x = x;
7926 mouse_y = y;
7927 mouse_leftisdown = event.LeftDown();
7929
7930 // Establish the event region
7931 cursor_region = CENTER;
7932
7933 int chartbar_height = GetChartbarHeight();
7934
7935 if (m_Compass && m_Compass->IsShown() &&
7936 m_Compass->GetRect().Contains(event.GetPosition())) {
7937 cursor_region = CENTER;
7938 } else if (x > xr_margin) {
7939 cursor_region = MID_RIGHT;
7940 } else if (x < xl_margin) {
7941 cursor_region = MID_LEFT;
7942 } else if (y > yb_margin - chartbar_height &&
7943 y < m_canvas_height - chartbar_height) {
7944 cursor_region = MID_TOP;
7945 } else if (y < yt_margin) {
7946 cursor_region = MID_BOT;
7947 } else {
7948 cursor_region = CENTER;
7949 }
7950
7951 if (!g_btouch) SetCanvasCursor(event);
7952
7953 // Protect from leftUp's coming from event handlers in child
7954 // windows who return focus to the canvas.
7955 leftIsDown = event.LeftDown();
7956
7957#ifndef __WXOSX__
7958 if (event.LeftDown()) {
7959 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7960 // The menu bar is temporarily visible due to alt having been pressed.
7961 // Clicking will hide it, and do nothing else.
7962 g_bTempShowMenuBar = false;
7963 top_frame::Get()->ApplyGlobalSettings(false);
7964 return (true);
7965 }
7966 }
7967#endif
7968
7969 // Update modifiers here; some window managers never send the key event
7970 m_modkeys = 0;
7971 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7972 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7973
7974#ifdef __WXMSW__
7975 // TODO Test carefully in other platforms, remove ifdef....
7976 // Avoid capture release on delayed/replayed timer events.
7977 if (!m_isReplayingClickEvent) {
7978 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7979 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7980 }
7981#endif
7982
7983 event.SetEventObject(this);
7984 if (SendMouseEventToPlugins(event))
7985 return (true); // PlugIn did something, and does not want the canvas to
7986 // do anything else
7987
7988 // Capture LeftUp's and time them, unless it already came from the timer.
7989
7990 // Detect end of chart dragging
7991 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7992 StartChartDragInertia();
7993 }
7994
7995 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7996 !singleClickEventIsValid) {
7997 // Ignore the second LeftUp after the DClick.
7998 if (m_DoubleClickTimer->IsRunning()) {
7999 m_DoubleClickTimer->Stop();
8000 return (true);
8001 }
8002
8003 // Save the event for later running if there is no DClick.
8004 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
8005 singleClickEvent = event;
8006 singleClickEventIsValid = true;
8007 return (true);
8008 }
8009
8010 // This logic is necessary on MSW to handle the case where
8011 // a context (right-click) menu is dismissed without action
8012 // by clicking on the chart surface.
8013 // We need to avoid an unintentional pan by eating some clicks...
8014#ifdef __WXMSW__
8015 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
8016 if (g_click_stop > 0) {
8017 g_click_stop--;
8018 return (true);
8019 }
8020 }
8021#endif
8022
8023 // Kick off the Rotation control timer
8024 if (GetUpMode() == COURSE_UP_MODE) {
8025 m_b_rot_hidef = false;
8026 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
8027 } else
8028 pRotDefTimer->Stop();
8029
8030 // Retrigger the route leg / AIS target popup timer
8031 bool bRoll = !g_btouch;
8032#ifdef __ANDROID__
8033 bRoll = g_bRollover;
8034#endif
8035 if (bRoll) {
8036 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
8037 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
8038 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
8039 m_RolloverPopupTimer.Start(
8040 10,
8041 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
8042 else
8043 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
8044 }
8045
8046 // Retrigger the cursor tracking timer
8047 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
8048
8049// Show cursor position on Status Bar, if present
8050// except for GTK, under which status bar updates are very slow
8051// due to Update() call.
8052// In this case, as a workaround, update the status window
8053// after an interval timer (pCurTrackTimer) pops, which will happen
8054// whenever the mouse has stopped moving for specified interval.
8055// See the method OnCursorTrackTimerEvent()
8056#if !defined(__WXGTK__) && !defined(__WXQT__)
8057 SetCursorStatus(m_cursor_lat, m_cursor_lon);
8058#endif
8059
8060 // Send the current cursor lat/lon to all PlugIns requesting it
8061 if (g_pi_manager) {
8062 // Occasionally, MSW will produce nonsense events on right click....
8063 // This results in an error in cursor geo position, so we skip this case
8064 if ((x >= 0) && (y >= 0))
8065 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8066 }
8067
8068 if (!g_btouch) {
8069 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8070 wxPoint p = ClientToScreen(wxPoint(x, y));
8071 }
8072 }
8073
8074 if (1 ) {
8075 // Route Creation Rubber Banding
8076 if (m_routeState >= 2) {
8077 r_rband.x = x;
8078 r_rband.y = y;
8079 m_bDrawingRoute = true;
8080
8081 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8082 Refresh(false);
8083 }
8084
8085 // Measure Tool Rubber Banding
8086 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8087 r_rband.x = x;
8088 r_rband.y = y;
8089 m_bDrawingRoute = true;
8090
8091 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8092 Refresh(false);
8093 }
8094 }
8095 return bret;
8096}
8097
8098int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8099 // On general Right Click
8100 // Look for selectable objects
8101 double slat = lat;
8102 double slon = lon;
8103
8104#if defined(__WXMAC__) || defined(__ANDROID__)
8105 wxScreenDC sdc;
8106 ocpnDC dc(sdc);
8107#else
8108 wxClientDC cdc(GetParent());
8109 ocpnDC dc(cdc);
8110#endif
8111
8112 SelectItem *pFindAIS;
8113 SelectItem *pFindRP;
8114 SelectItem *pFindRouteSeg;
8115 SelectItem *pFindTrackSeg;
8116 SelectItem *pFindCurrent = NULL;
8117 SelectItem *pFindTide = NULL;
8118
8119 // Deselect any current objects
8120 if (m_pSelectedRoute) {
8121 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8122 m_pSelectedRoute->DeSelectRoute();
8123#ifdef ocpnUSE_GL
8124 if (g_bopengl && m_glcc) {
8125 InvalidateGL();
8126 Update();
8127 } else
8128#endif
8129 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8130 }
8131
8132 if (m_pFoundRoutePoint) {
8133 m_pFoundRoutePoint->m_bPtIsSelected = false;
8134 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8135 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8136 }
8137
8140 if (g_btouch && m_pRoutePointEditTarget) {
8141 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8142 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8143 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8144 }
8145
8146 // Get all the selectable things at the cursor
8147 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8148 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8149 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8150 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8151 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8152
8153 if (m_bShowCurrent)
8154 pFindCurrent =
8155 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8156
8157 if (m_bShowTide) // look for tide stations
8158 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8159
8160 int seltype = 0;
8161
8162 // Try for AIS targets first
8163 if (pFindAIS) {
8164 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8165
8166 // Make sure the target data is available
8167 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8168 seltype |= SELTYPE_AISTARGET;
8169 }
8170
8171 // Now examine the various Route parts
8172
8173 m_pFoundRoutePoint = NULL;
8174 if (pFindRP) {
8175 RoutePoint *pFirstVizPoint = NULL;
8176 RoutePoint *pFoundActiveRoutePoint = NULL;
8177 RoutePoint *pFoundVizRoutePoint = NULL;
8178 Route *pSelectedActiveRoute = NULL;
8179 Route *pSelectedVizRoute = NULL;
8180
8181 // There is at least one routepoint, so get the whole list
8182 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8183 SelectableItemList SelList =
8184 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8185 for (SelectItem *pFindSel : SelList) {
8186 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8187
8188 // Get an array of all routes using this point
8189 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8190
8191 // Use route array (if any) to determine actual visibility for this point
8192 bool brp_viz = false;
8193 if (proute_array) {
8194 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8195 Route *pr = (Route *)proute_array->Item(ir);
8196 if (pr->IsVisible()) {
8197 brp_viz = true;
8198 break;
8199 }
8200 }
8201 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8202 // but still exists as a waypoint
8203 brp_viz = prp->IsVisible(); // so treat as isolated point
8204
8205 } else
8206 brp_viz = prp->IsVisible(); // isolated point
8207
8208 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8209
8210 // Use route array to choose the appropriate route
8211 // Give preference to any active route, otherwise select the first visible
8212 // route in the array for this point
8213 m_pSelectedRoute = NULL;
8214 if (proute_array) {
8215 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8216 Route *pr = (Route *)proute_array->Item(ir);
8217 if (pr->m_bRtIsActive) {
8218 pSelectedActiveRoute = pr;
8219 pFoundActiveRoutePoint = prp;
8220 break;
8221 }
8222 }
8223
8224 if (NULL == pSelectedVizRoute) {
8225 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8226 Route *pr = (Route *)proute_array->Item(ir);
8227 if (pr->IsVisible()) {
8228 pSelectedVizRoute = pr;
8229 pFoundVizRoutePoint = prp;
8230 break;
8231 }
8232 }
8233 }
8234
8235 delete proute_array;
8236 }
8237 }
8238
8239 // Now choose the "best" selections
8240 if (pFoundActiveRoutePoint) {
8241 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8242 m_pSelectedRoute = pSelectedActiveRoute;
8243 } else if (pFoundVizRoutePoint) {
8244 m_pFoundRoutePoint = pFoundVizRoutePoint;
8245 m_pSelectedRoute = pSelectedVizRoute;
8246 } else
8247 // default is first visible point in list
8248 m_pFoundRoutePoint = pFirstVizPoint;
8249
8250 if (m_pSelectedRoute) {
8251 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8252 } else if (m_pFoundRoutePoint) {
8253 seltype |= SELTYPE_MARKPOINT;
8254 }
8255
8256 // Highlight the selected point, to verify the proper right click selection
8257 if (m_pFoundRoutePoint) {
8258 m_pFoundRoutePoint->m_bPtIsSelected = true;
8259 wxRect wp_rect;
8260 RoutePointGui(*m_pFoundRoutePoint)
8261 .CalculateDCRect(m_dc_route, this, &wp_rect);
8262 RefreshRect(wp_rect, true);
8263 }
8264 }
8265
8266 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8267 // routes But call the popup handler with identifier appropriate to the type
8268 if (pFindRouteSeg) // there is at least one select item
8269 {
8270 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8271 SelectableItemList SelList =
8272 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8273
8274 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8275 {
8276 // Choose the first visible route containing segment in the list
8277 for (SelectItem *pFindSel : SelList) {
8278 Route *pr = (Route *)pFindSel->m_pData3;
8279 if (pr->IsVisible()) {
8280 m_pSelectedRoute = pr;
8281 break;
8282 }
8283 }
8284 }
8285
8286 if (m_pSelectedRoute) {
8287 if (NULL == m_pFoundRoutePoint)
8288 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8289
8290 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8291 if (m_pSelectedRoute->m_bRtIsSelected) {
8292#ifdef ocpnUSE_GL
8293 if (g_bopengl && m_glcc) {
8294 InvalidateGL();
8295 Update();
8296 } else
8297#endif
8298 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8299 }
8300 seltype |= SELTYPE_ROUTESEGMENT;
8301 }
8302 }
8303
8304 if (pFindTrackSeg) {
8305 m_pSelectedTrack = NULL;
8306 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8307 SelectableItemList SelList =
8308 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8309
8310 // Choose the first visible track containing segment in the list
8311 for (SelectItem *pFindSel : SelList) {
8312 Track *pt = (Track *)pFindSel->m_pData3;
8313 if (pt->IsVisible()) {
8314 m_pSelectedTrack = pt;
8315 break;
8316 }
8317 }
8318 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8319 }
8320
8321#if 0 // disable tide and current graph on right click
8322 {
8323 if (pFindCurrent) {
8324 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8325 seltype |= SELTYPE_CURRENTPOINT;
8326 }
8327
8328 else if (pFindTide) {
8329 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8330 seltype |= SELTYPE_TIDEPOINT;
8331 }
8332 }
8333#endif
8334
8335 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8336
8337 return seltype;
8338}
8339
8340IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8341 // There may be multiple current entries at the same point.
8342 // For example, there often is a current substation (with directions
8343 // specified) co-located with its master. We want to select the
8344 // substation, so that the direction will be properly indicated on the
8345 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8346 // substation)
8347 IDX_entry *pIDX_best_candidate;
8348
8349 SelectItem *pFind = NULL;
8350 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8351 SelectableItemList SelList =
8352 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8353
8354 // Default is first entry
8355 pFind = *SelList.begin();
8356 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8357
8358 auto node = SelList.begin();
8359 if (SelList.size() > 1) {
8360 for (++node; node != SelList.end(); ++node) {
8361 pFind = *node;
8362 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8363 if (pIDX_candidate->IDX_type == 'c') {
8364 pIDX_best_candidate = pIDX_candidate;
8365 break;
8366 }
8367 } // while (node)
8368 } else {
8369 pFind = *SelList.begin();
8370 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8371 }
8372
8373 return pIDX_best_candidate;
8374}
8375void ChartCanvas::CallPopupMenu(int x, int y) {
8376 last_drag.x = x;
8377 last_drag.y = y;
8378 if (m_routeState) { // creating route?
8379 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8380 return;
8381 }
8382
8384
8385 // If tide or current point is selected, then show the TC dialog immediately
8386 // without context menu
8387 if (SELTYPE_CURRENTPOINT == seltype) {
8388 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8389 Refresh(false);
8390 return;
8391 }
8392
8393 if (SELTYPE_TIDEPOINT == seltype) {
8394 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8395 Refresh(false);
8396 return;
8397 }
8398
8399 InvokeCanvasMenu(x, y, seltype);
8400
8401 // Clean up if not deleted in InvokeCanvasMenu
8402 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8403 m_pSelectedRoute->m_bRtIsSelected = false;
8404 }
8405
8406 m_pSelectedRoute = NULL;
8407
8408 if (m_pFoundRoutePoint) {
8409 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8410 m_pFoundRoutePoint->m_bPtIsSelected = false;
8411 }
8412 m_pFoundRoutePoint = NULL;
8413
8414 // A route/mark edit may have been in progress (m_pRoutePointEditTarget set)
8415 // when the menu action deleted that very RoutePoint (e.g. "Delete" on a
8416 // waypoint of a route). Nulling m_pSelectedRoute/m_pFoundRoutePoint above is
8417 // not enough: m_pRoutePointEditTarget (and m_pFoundPoint) would still point
8418 // at the freed point, so the next mouse motion in MouseEventProcessObjects
8419 // dereferences it (GetIconName()) -> use-after-free -> crash. Drop the edit
8420 // state if its target no longer exists.
8421 if (m_pRoutePointEditTarget &&
8422 !pSelect->IsSelectableRoutePointValid(m_pRoutePointEditTarget)) {
8423 m_pRoutePointEditTarget = NULL;
8424 m_lastRoutePointEditTarget = NULL;
8425 m_pFoundPoint = NULL;
8426 m_bRouteEditing = false;
8427 m_bMarkEditing = false;
8428 }
8429
8430 Refresh(true);
8431 // Refresh(false); // needed for MSW, not GTK Why??
8432}
8433
8434bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8435 // For now just bail out completely if the point clicked is not on the chart
8436 if (std::isnan(m_cursor_lat)) return false;
8437
8438 // Mouse Clicks
8439 bool ret = false; // return true if processed
8440
8441 int x, y, mx, my;
8442 event.GetPosition(&x, &y);
8443 mx = x;
8444 my = y;
8445
8446 // Calculate meaningful SelectRadius
8447 float SelectRadius;
8448 SelectRadius = g_Platform->GetSelectRadiusPix() /
8449 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8450
8452 // We start with Double Click processing. The first left click just starts a
8453 // timer and is remembered, then we actually do something if there is a
8454 // LeftDClick. If there is, the two single clicks are ignored.
8455
8456 if (event.LeftDClick() && (cursor_region == CENTER)) {
8457 m_DoubleClickTimer->Start();
8458 singleClickEventIsValid = false;
8459
8460 double zlat, zlon;
8462 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8463
8464 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8465 if (m_bShowAIS) {
8466 SelectItem *pFindAIS;
8467 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8468
8469 if (pFindAIS) {
8470 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8471 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8472 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8473 }
8474 return true;
8475 }
8476 }
8477
8478 SelectableItemList rpSelList =
8479 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8480 bool b_onRPtarget = false;
8481 for (SelectItem *pFind : rpSelList) {
8482 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8483 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8484 b_onRPtarget = true;
8485 break;
8486 }
8487 }
8488
8489 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8490
8491 // Get and honor the plugin API ContextMenuMask
8492 std::unique_ptr<HostApi> host_api = GetHostApi();
8493 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8494
8495 if (m_pRoutePointEditTarget) {
8496 if (b_onRPtarget) {
8497 if ((api_121->GetContextMenuMask() &
8498 api_121->kContextMenuDisableWaypoint))
8499 return true;
8500 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8501 return true;
8502 } else {
8503 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8504 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8505 if (g_btouch)
8506 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8507 wxRect wp_rect;
8508 RoutePointGui(*m_pRoutePointEditTarget)
8509 .CalculateDCRect(m_dc_route, this, &wp_rect);
8510 m_pRoutePointEditTarget = NULL; // cancel selection
8511 RefreshRect(wp_rect, true);
8512 return true;
8513 }
8514 } else {
8515 auto node = rpSelList.begin();
8516 if (node != rpSelList.end()) {
8517 SelectItem *pFind = *node;
8518 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8519 if (frp) {
8520 wxArrayPtrVoid *proute_array =
8522
8523 // Use route array (if any) to determine actual visibility for this
8524 // point
8525 bool brp_viz = false;
8526 if (proute_array) {
8527 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8528 Route *pr = (Route *)proute_array->Item(ir);
8529 if (pr->IsVisible()) {
8530 brp_viz = true;
8531 break;
8532 }
8533 }
8534 delete proute_array;
8535 if (!brp_viz &&
8536 frp->IsShared()) // is not visible as part of route, but
8537 // still exists as a waypoint
8538 brp_viz = frp->IsVisible(); // so treat as isolated point
8539 } else
8540 brp_viz = frp->IsVisible(); // isolated point
8541
8542 if (brp_viz) {
8543 if ((api_121->GetContextMenuMask() &
8544 api_121->kContextMenuDisableWaypoint))
8545 return true;
8546
8547 ShowMarkPropertiesDialog(frp);
8548 return true;
8549 }
8550 }
8551 }
8552 }
8553
8554 SelectItem *cursorItem;
8555
8556 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8557 if (cursorItem) {
8558 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8559 return true;
8560 Route *pr = (Route *)cursorItem->m_pData3;
8561 if (pr->IsVisible()) {
8562 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8563 return true;
8564 }
8565 }
8566
8567 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8568 if (cursorItem) {
8569 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8570 return true;
8571 Track *pt = (Track *)cursorItem->m_pData3;
8572 if (pt->IsVisible()) {
8573 ShowTrackPropertiesDialog(pt);
8574 return true;
8575 }
8576 }
8577
8578 // Tide and current points
8579 SelectItem *pFindCurrent = NULL;
8580 SelectItem *pFindTide = NULL;
8581
8582 if (m_bShowCurrent) { // look for current stations
8583 pFindCurrent =
8584 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8585 if (pFindCurrent) {
8586 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8587 // Check for plugin graphic override
8588 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8589 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8590 PlugInContainer *pic = plugin_array->Item(i);
8591 if (pic->m_enabled && pic->m_init_state &&
8592 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8593 if (ptcmgr) {
8594 TCClickInfo info;
8595 if (m_pIDXCandidate) {
8596 info.point_type = CURRENT_STATION;
8597 info.index = m_pIDXCandidate->IDX_rec_num;
8598 info.name = m_pIDXCandidate->IDX_station_name;
8599 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8600 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8601 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8602 };
8603 auto plugin =
8604 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8605 if (plugin) plugin->OnTideCurrentClick(info);
8606 return true;
8607 }
8608 }
8609 }
8610 }
8611
8612 // Default action
8613 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8614 Refresh(false);
8615 return true;
8616 }
8617 }
8618
8619 if (m_bShowTide) { // look for tide stations
8620 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8621 if (pFindTide) {
8622 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8623 // Check for plugin graphic override
8624 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8625 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8626 PlugInContainer *pic = plugin_array->Item(i);
8627 if (pic->m_enabled && pic->m_init_state &&
8628 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8629 if (ptcmgr) {
8630 TCClickInfo info;
8631 if (m_pIDXCandidate) {
8632 info.point_type = TIDE_STATION;
8633 info.index = m_pIDXCandidate->IDX_rec_num;
8634 info.name = m_pIDXCandidate->IDX_station_name;
8635 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8636 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8637 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8638 };
8639 auto plugin =
8640 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8641 if (plugin) plugin->OnTideCurrentClick(info);
8642 return true;
8643 }
8644 }
8645 }
8646 }
8647
8648 // Default action
8649 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8650 Refresh(false);
8651 return true;
8652 }
8653 }
8654
8655 // Found no object to act on, so show chart info.
8656 ShowObjectQueryWindow(x, y, zlat, zlon);
8657 return true;
8658 }
8659
8661 if (event.LeftDown()) {
8662 // This really should not be needed, but....
8663 // on Windows, when using wxAUIManager, sometimes the focus is lost
8664 // when clicking into another pane, e.g.the AIS target list, and then back
8665 // to this pane. Oddly, some mouse events are not lost, however. Like this
8666 // one....
8667 SetFocus();
8668
8669 last_drag.x = mx;
8670 last_drag.y = my;
8671 leftIsDown = true;
8672
8673 if (!g_btouch) {
8674 if (m_routeState) // creating route?
8675 {
8676 double rlat, rlon;
8677 bool appending = false;
8678 bool inserting = false;
8679 Route *tail = 0;
8680
8681 SetCursor(*pCursorPencil);
8682 rlat = m_cursor_lat;
8683 rlon = m_cursor_lon;
8684
8685 m_bRouteEditing = true;
8686
8687 if (m_routeState == 1) {
8688 m_pMouseRoute = new Route();
8689 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8690 pRouteList->push_back(m_pMouseRoute);
8691 r_rband.x = x;
8692 r_rband.y = y;
8693 }
8694
8695 // Check to see if there is a nearby point which may be reused
8696 RoutePoint *pMousePoint = NULL;
8697
8698 // Calculate meaningful SelectRadius
8699 double nearby_radius_meters =
8700 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8701
8702 RoutePoint *pNearbyPoint =
8703 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8704 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8705 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8706 wxArrayPtrVoid *proute_array =
8707 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8708
8709 // Use route array (if any) to determine actual visibility for this
8710 // point
8711 bool brp_viz = false;
8712 if (proute_array) {
8713 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8714 Route *pr = (Route *)proute_array->Item(ir);
8715 if (pr->IsVisible()) {
8716 brp_viz = true;
8717 break;
8718 }
8719 }
8720 delete proute_array;
8721 if (!brp_viz &&
8722 pNearbyPoint->IsShared()) // is not visible as part of route,
8723 // but still exists as a waypoint
8724 brp_viz =
8725 pNearbyPoint->IsVisible(); // so treat as isolated point
8726 } else
8727 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8728
8729 if (brp_viz) {
8730 wxString msg = _("Use nearby waypoint?");
8731 // Don't add a mark without name to the route. Name it if needed
8732 const bool noname(pNearbyPoint->GetName() == "");
8733 if (noname) {
8734 msg =
8735 _("Use nearby nameless waypoint and name it M with"
8736 " a unique number?");
8737 }
8738 // Avoid route finish on focus change for message dialog
8739 m_FinishRouteOnKillFocus = false;
8740 int dlg_return =
8741 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8742 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8743 m_FinishRouteOnKillFocus = true;
8744 if (dlg_return == wxID_YES) {
8745 if (noname) {
8746 if (m_pMouseRoute) {
8747 int last_wp_num = m_pMouseRoute->GetnPoints();
8748 // AP-ECRMB will truncate to 6 characters
8749 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8750 wxString wp_name = wxString::Format(
8751 "M%002i-%s", last_wp_num + 1, guid_short);
8752 pNearbyPoint->SetName(wp_name);
8753 } else
8754 pNearbyPoint->SetName("WPXX");
8755 }
8756 pMousePoint = pNearbyPoint;
8757
8758 // Using existing waypoint, so nothing to delete for undo.
8759 if (m_routeState > 1)
8760 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8761 Undo_HasParent, NULL);
8762
8763 tail =
8764 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8765 bool procede = false;
8766 if (tail) {
8767 procede = true;
8768 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8769 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8770 procede = false;
8771 }
8772
8773 if (procede) {
8774 int dlg_return;
8775 m_FinishRouteOnKillFocus = false;
8776 if (m_routeState ==
8777 1) { // first point in new route, preceeding route to be
8778 // added? Not touch case
8779
8780 wxString dmsg =
8781 _("Insert first part of this route in the new route?");
8782 if (tail->GetIndexOf(pMousePoint) ==
8783 tail->GetnPoints()) // Starting on last point of another
8784 // route?
8785 dmsg = _("Insert this route in the new route?");
8786
8787 if (tail->GetIndexOf(pMousePoint) > 0) { // Anything to do?
8788 dlg_return = OCPNMessageBox(
8789 this, dmsg, _("OpenCPN Route Create"),
8790 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8791 m_FinishRouteOnKillFocus = true;
8792
8793 if (dlg_return == wxID_YES) {
8794 inserting = true; // part of the other route will be
8795 // preceeding the new route
8796 }
8797 }
8798 } else {
8799 wxString dmsg =
8800 _("Append last part of this route to the new route?");
8801 if (tail->GetIndexOf(pMousePoint) == 1)
8802 dmsg = _(
8803 "Append this route to the new route?"); // Picking the
8804 // first point
8805 // of another
8806 // route?
8807
8808 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8809 dlg_return = OCPNMessageBox(
8810 this, dmsg, _("OpenCPN Route Create"),
8811 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8812 m_FinishRouteOnKillFocus = true;
8813
8814 if (dlg_return == wxID_YES) {
8815 appending = true; // part of the other route will be
8816 // appended to the new route
8817 }
8818 }
8819 }
8820 }
8821
8822 // check all other routes to see if this point appears in any
8823 // other route If it appears in NO other route, then it should e
8824 // considered an isolated mark
8825 if (!FindRouteContainingWaypoint(pMousePoint))
8826 pMousePoint->SetShared(true);
8827 }
8828 }
8829 }
8830
8831 if (NULL == pMousePoint) { // need a new point
8832 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8833 "", wxEmptyString);
8834 pMousePoint->SetNameShown(false);
8835
8836 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8837
8838 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8839
8840 if (m_routeState > 1)
8841 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8842 Undo_IsOrphanded, NULL);
8843 }
8844
8845 if (m_pMouseRoute) {
8846 if (m_routeState == 1) {
8847 // First point in the new route.
8848 m_pMouseRoute->AddPoint(pMousePoint);
8849 } else {
8850 if (m_pMouseRoute->m_NextLegGreatCircle) {
8851 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8852 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8853 &rhumbBearing, &rhumbDist);
8854 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8855 rlat, &gcDist, &gcBearing, NULL);
8856 double gcDistNM = gcDist / 1852.0;
8857
8858 // Empirically found expression to get reasonable route segments.
8859 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8860 pow(rhumbDist - gcDistNM - 1, 0.5);
8861
8862 wxString msg;
8863 msg << _("For this leg the Great Circle route is ")
8864 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8865 << _(" shorter than rhumbline.\n\n")
8866 << _("Would you like include the Great Circle routing points "
8867 "for this leg?");
8868
8869 m_FinishRouteOnKillFocus = false;
8870 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8871 // does not fully capture mouse
8872
8873 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8874 wxYES_NO | wxNO_DEFAULT);
8875
8876 m_disable_edge_pan = false;
8877 m_FinishRouteOnKillFocus = true;
8878
8879 if (answer == wxID_YES) {
8880 RoutePoint *gcPoint;
8881 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8882 wxRealPoint gcCoord;
8883
8884 for (int i = 1; i <= segmentCount; i++) {
8885 double fraction = (double)i * (1.0 / (double)segmentCount);
8886 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8887 gcDist * fraction, gcBearing,
8888 &gcCoord.x, &gcCoord.y, NULL);
8889
8890 if (i < segmentCount) {
8891 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8892 wxEmptyString);
8893 gcPoint->SetNameShown(false);
8894 // pConfig->AddNewWayPoint(gcPoint, -1);
8895 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8896
8897 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8898 gcPoint);
8899 } else {
8900 gcPoint = pMousePoint; // Last point, previously exsisting!
8901 }
8902
8903 m_pMouseRoute->AddPoint(gcPoint);
8904 pSelect->AddSelectableRouteSegment(
8905 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8906 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8907 prevGcPoint = gcPoint;
8908 }
8909
8910 undo->CancelUndoableAction(true);
8911
8912 } else {
8913 m_pMouseRoute->AddPoint(pMousePoint);
8914 pSelect->AddSelectableRouteSegment(
8915 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8916 pMousePoint, m_pMouseRoute);
8917 undo->AfterUndoableAction(m_pMouseRoute);
8918 }
8919 } else {
8920 // Ordinary rhumblinesegment.
8921 m_pMouseRoute->AddPoint(pMousePoint);
8922 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8923 rlon, m_prev_pMousePoint,
8924 pMousePoint, m_pMouseRoute);
8925 undo->AfterUndoableAction(m_pMouseRoute);
8926 }
8927 }
8928 }
8929 m_prev_rlat = rlat;
8930 m_prev_rlon = rlon;
8931 m_prev_pMousePoint = pMousePoint;
8932 if (m_pMouseRoute)
8933 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8934
8935 m_routeState++;
8936
8937 if (appending ||
8938 inserting) { // Appending a route or making a new route
8939 int connect = tail->GetIndexOf(pMousePoint);
8940 if (connect == 0) {
8941 inserting = false; // there is nothing to insert
8942 appending = true; // so append
8943 }
8944 int length = tail->GetnPoints();
8945
8946 int i;
8947 int start, stop;
8948 if (appending) {
8949 start = connect + 1;
8950 stop = length;
8951 } else { // inserting
8952 start = 1;
8953 stop = connect + 1;
8954 // Remove the first and only point of the new route
8955 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8956 }
8957 for (i = start; i <= stop; i++) {
8958 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8959 if (m_pMouseRoute)
8960 m_pMouseRoute->m_lastMousePointIndex =
8961 m_pMouseRoute->GetnPoints();
8962 m_routeState++;
8963 top_frame::Get()->RefreshAllCanvas();
8964 ret = true;
8965 }
8966 m_prev_rlat =
8967 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8968 m_prev_rlon =
8969 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8970 m_pMouseRoute->FinalizeForRendering();
8971 }
8972 top_frame::Get()->RefreshAllCanvas();
8973 ret = true;
8974 }
8975
8976 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8977 {
8978 SetCursor(*pCursorPencil);
8979
8980 if (!m_pMeasureRoute) {
8981 m_pMeasureRoute = new Route();
8982 pRouteList->push_back(m_pMeasureRoute);
8983 }
8984
8985 if (m_nMeasureState == 1) {
8986 r_rband.x = x;
8987 r_rband.y = y;
8988 }
8989
8990 if (!g_pRouteMan->IsRouteInList(m_pMeasureRoute)) {
8991 wxLogMessage("Stale measure route");
8992 CancelMeasureRoute();
8993 top_frame::Get()->RefreshAllCanvas();
8994 ret = true;
8995 } else {
8996 RoutePoint *pMousePoint =
8997 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8998 wxEmptyString, wxEmptyString);
8999 pMousePoint->m_bShowName = false;
9000 pMousePoint->SetShowWaypointRangeRings(false);
9001
9002 m_pMeasureRoute->AddPoint(pMousePoint);
9003
9004 m_prev_rlat = m_cursor_lat;
9005 m_prev_rlon = m_cursor_lon;
9006 m_prev_pMousePoint = pMousePoint;
9007 m_pMeasureRoute->m_lastMousePointIndex =
9008 m_pMeasureRoute->GetnPoints();
9009
9010 m_nMeasureState++;
9011 top_frame::Get()->RefreshAllCanvas();
9012 ret = true;
9013 }
9014 }
9015
9016 else {
9017 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
9018 }
9019 } // !g_btouch
9020 else { // g_btouch
9021 m_last_touch_down_pos = event.GetPosition();
9022
9023 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
9024 // if near screen edge, pan with injection
9025 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
9026 // return;
9027 // }
9028 }
9029 }
9030
9031 if (ret) return true;
9032 }
9033
9034 if (event.Dragging()) {
9035 // The route/mark edit target is a raw RoutePoint* that can be freed out
9036 // from under us by a delete (context menu, Routes panel, or a plugin)
9037 // between the press that armed the edit and this drag event. The canvas is
9038 // not notified, so the pointer dangles. Validate it against the Select list
9039 // before any use below; if it is gone, drop the stale edit state, otherwise
9040 // the drag logic dereferences freed memory (e.g. GetIconName()) -> crash.
9041 if (m_pRoutePointEditTarget &&
9042 !pSelect->IsSelectableRoutePointValid(m_pRoutePointEditTarget)) {
9043 m_pRoutePointEditTarget = NULL;
9044 m_lastRoutePointEditTarget = NULL;
9045 m_pFoundPoint = NULL;
9046 m_bRouteEditing = false;
9047 m_bMarkEditing = false;
9048 return false;
9049 }
9050
9051 // in touch screen mode ensure the finger/cursor is on the selected point's
9052 // radius to allow dragging
9053 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9054 if (g_btouch) {
9055 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
9056 SelectItem *pFind = NULL;
9057 SelectableItemList SelList = pSelect->FindSelectionList(
9058 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
9059 for (SelectItem *pFind : SelList) {
9060 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
9061 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
9062 }
9063 }
9064
9065 // Check for use of dragHandle
9066 if (m_pRoutePointEditTarget &&
9067 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
9068 SelectItem *pFind = NULL;
9069 SelectableItemList SelList = pSelect->FindSelectionList(
9070 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
9071 for (SelectItem *pFind : SelList) {
9072 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
9073 if (m_pRoutePointEditTarget == frp) {
9074 m_bIsInRadius = true;
9075 break;
9076 }
9077 }
9078
9079 if (!m_dragoffsetSet) {
9080 RoutePointGui(*m_pRoutePointEditTarget)
9081 .PresetDragOffset(this, mouse_x, mouse_y);
9082 m_dragoffsetSet = true;
9083 }
9084 }
9085 }
9086
9087 if (m_bRouteEditing && m_pRoutePointEditTarget) {
9088 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9089
9090 if (NULL == g_pMarkInfoDialog) {
9091 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9092 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9093 DraggingAllowed = false;
9094
9095 if (m_pRoutePointEditTarget &&
9096 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9097 DraggingAllowed = false;
9098
9099 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9100
9101 if (DraggingAllowed) {
9102 if (!undo->InUndoableAction()) {
9103 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9104 Undo_NeedsCopy, m_pFoundPoint);
9105 }
9106
9107 // Get the update rectangle for the union of the un-edited routes
9108 wxRect pre_rect;
9109
9110 if (!g_bopengl && m_pEditRouteArray) {
9111 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9112 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9113 // Need to validate route pointer
9114 // Route may be gone due to drgging close to ownship with
9115 // "Delete On Arrival" state set, as in the case of
9116 // navigating to an isolated waypoint on a temporary route
9117 if (g_pRouteMan->IsRouteValid(pr)) {
9118 wxRect route_rect;
9119 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9120 pre_rect.Union(route_rect);
9121 }
9122 }
9123 }
9124
9125 double new_cursor_lat = m_cursor_lat;
9126 double new_cursor_lon = m_cursor_lon;
9127
9128 if (CheckEdgePan(x, y, true, 5, 2))
9129 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9130
9131 // update the point itself
9132 if (g_btouch) {
9133 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9134 // new_cursor_lat, new_cursor_lon);
9135 RoutePointGui(*m_pRoutePointEditTarget)
9136 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9137 // update the Drag Handle entry in the pSelect list
9138 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9139 m_pRoutePointEditTarget,
9140 SELTYPE_DRAGHANDLE);
9141 m_pFoundPoint->m_slat =
9142 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9143 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9144 } else {
9145 m_pRoutePointEditTarget->m_lat =
9146 new_cursor_lat; // update the RoutePoint entry
9147 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9148 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9149 m_pFoundPoint->m_slat =
9150 new_cursor_lat; // update the SelectList entry
9151 m_pFoundPoint->m_slon = new_cursor_lon;
9152 }
9153
9154 // Update the MarkProperties Dialog, if currently shown
9155 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9156 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9157 g_pMarkInfoDialog->UpdateProperties(true);
9158 }
9159
9160 if (g_bopengl) {
9161 // InvalidateGL();
9162 Refresh(false);
9163 } else {
9164 // Get the update rectangle for the edited route
9165 wxRect post_rect;
9166
9167 if (m_pEditRouteArray) {
9168 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9169 ir++) {
9170 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9171 if (g_pRouteMan->IsRouteValid(pr)) {
9172 wxRect route_rect;
9173 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9174 post_rect.Union(route_rect);
9175 }
9176 }
9177 }
9178
9179 // Invalidate the union region
9180 pre_rect.Union(post_rect);
9181 RefreshRect(pre_rect, false);
9182 }
9183 top_frame::Get()->RefreshCanvasOther(this);
9184 m_bRoutePoinDragging = true;
9185 }
9186 ret = true;
9187 } // if Route Editing
9188
9189 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9190 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9191
9192 if (NULL == g_pMarkInfoDialog) {
9193 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9194 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9195 DraggingAllowed = false;
9196
9197 if (m_pRoutePointEditTarget &&
9198 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9199 DraggingAllowed = false;
9200
9201 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9202
9203 if (DraggingAllowed) {
9204 if (!undo->InUndoableAction()) {
9205 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9206 Undo_NeedsCopy, m_pFoundPoint);
9207 }
9208
9209 // The mark may be an anchorwatch
9210 double lpp1 = 0.;
9211 double lpp2 = 0.;
9212 double lppmax;
9213
9214 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9215 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9216 }
9217 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9218 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9219 }
9220 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9221
9222 // Get the update rectangle for the un-edited mark
9223 wxRect pre_rect;
9224 if (!g_bopengl) {
9225 RoutePointGui(*m_pRoutePointEditTarget)
9226 .CalculateDCRect(m_dc_route, this, &pre_rect);
9227 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9228 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9229 (int)(lppmax - (pre_rect.height / 2)));
9230 }
9231
9232 // update the point itself
9233 if (g_btouch) {
9234 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9235 // m_cursor_lat, m_cursor_lon);
9236 RoutePointGui(*m_pRoutePointEditTarget)
9237 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9238 // update the Drag Handle entry in the pSelect list
9239 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9240 m_pRoutePointEditTarget,
9241 SELTYPE_DRAGHANDLE);
9242 m_pFoundPoint->m_slat =
9243 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9244 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9245 } else {
9246 m_pRoutePointEditTarget->m_lat =
9247 m_cursor_lat; // update the RoutePoint entry
9248 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9249 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9250 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9251 m_pFoundPoint->m_slon = m_cursor_lon;
9252 }
9253
9254 // Update the MarkProperties Dialog, if currently shown
9255 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9256 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9257 g_pMarkInfoDialog->UpdateProperties(true);
9258 }
9259
9260 // Invalidate the union region
9261 if (g_bopengl) {
9262 if (!g_btouch) InvalidateGL();
9263 Refresh(false);
9264 } else {
9265 // Get the update rectangle for the edited mark
9266 wxRect post_rect;
9267 RoutePointGui(*m_pRoutePointEditTarget)
9268 .CalculateDCRect(m_dc_route, this, &post_rect);
9269 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9270 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9271 (int)(lppmax - (post_rect.height / 2)));
9272
9273 // Invalidate the union region
9274 pre_rect.Union(post_rect);
9275 RefreshRect(pre_rect, false);
9276 }
9277 top_frame::Get()->RefreshCanvasOther(this);
9278 m_bRoutePoinDragging = true;
9279 }
9280 ret = g_btouch ? m_bRoutePoinDragging : true;
9281 }
9282
9283 if (ret) return true;
9284 } // dragging
9285
9286 if (event.LeftUp()) {
9287 bool b_startedit_route = false;
9288 m_dragoffsetSet = false;
9289
9290 if (g_btouch) {
9291 m_bChartDragging = false;
9292 m_bIsInRadius = false;
9293
9294 if (m_routeState) // creating route?
9295 {
9296 // Check displacement from touchdown to distinguish taps from pans.
9297 // On touchscreens, even a "tap" can have several pixels of jitter,
9298 // so use a generous threshold (20px) to avoid false positives.
9299 int dx = x - m_touchdownPos.x;
9300 int dy = y - m_touchdownPos.y;
9301 int dist2 = dx * dx + dy * dy;
9302 bool wasPan = (dist2 > 20 * 20);
9303
9304 if (wasPan) {
9305 return false;
9306 }
9307
9308 if (m_bedge_pan) {
9309 m_bedge_pan = false;
9310 return false;
9311 }
9312
9313 double rlat, rlon;
9314 bool appending = false;
9315 bool inserting = false;
9316 Route *tail = 0;
9317
9318 rlat = m_cursor_lat;
9319 rlon = m_cursor_lon;
9320
9321 if (m_pRoutePointEditTarget) {
9322 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9323 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9324 if (!g_bopengl) {
9325 wxRect wp_rect;
9326 RoutePointGui(*m_pRoutePointEditTarget)
9327 .CalculateDCRect(m_dc_route, this, &wp_rect);
9328 RefreshRect(wp_rect, true);
9329 }
9330 m_pRoutePointEditTarget = NULL;
9331 }
9332 m_bRouteEditing = true;
9333
9334 if (m_routeState == 1) {
9335 m_pMouseRoute = new Route();
9336 m_pMouseRoute->SetHiLite(50);
9337 pRouteList->push_back(m_pMouseRoute);
9338 r_rband.x = x;
9339 r_rband.y = y;
9340 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9341 }
9342
9343 // Check to see if there is a nearby point which may be reused
9344 RoutePoint *pMousePoint = NULL;
9345
9346 // Calculate meaningful SelectRadius
9347 double nearby_radius_meters =
9348 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9349
9350 RoutePoint *pNearbyPoint =
9351 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9352 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9353 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9354 int dlg_return;
9355#ifndef __WXOSX__
9356 m_FinishRouteOnKillFocus =
9357 false; // Avoid route finish on focus change for message dialog
9358 dlg_return = OCPNMessageBox(
9359 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9360 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9361 m_FinishRouteOnKillFocus = true;
9362#else
9363 dlg_return = wxID_YES;
9364#endif
9365 if (dlg_return == wxID_YES) {
9366 pMousePoint = pNearbyPoint;
9367
9368 // Using existing waypoint, so nothing to delete for undo.
9369 if (m_routeState > 1)
9370 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9371 Undo_HasParent, NULL);
9372 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9373
9374 bool procede = false;
9375 if (tail) {
9376 procede = true;
9377 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9378 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9379 procede = false;
9380 }
9381
9382 if (procede) {
9383 int dlg_return;
9384 m_FinishRouteOnKillFocus = false;
9385 if (m_routeState == 1) { // first point in new route, preceeding
9386 // route to be added? touch case
9387
9388 wxString dmsg =
9389 _("Insert first part of this route in the new route?");
9390 if (tail->GetIndexOf(pMousePoint) ==
9391 tail->GetnPoints()) // Starting on last point of another
9392 // route?
9393 dmsg = _("Insert this route in the new route?");
9394
9395 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9396 dlg_return =
9397 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9398 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9399 m_FinishRouteOnKillFocus = true;
9400
9401 if (dlg_return == wxID_YES) {
9402 inserting = true; // part of the other route will be
9403 // preceeding the new route
9404 }
9405 }
9406 } else {
9407 wxString dmsg =
9408 _("Append last part of this route to the new route?");
9409 if (tail->GetIndexOf(pMousePoint) == 1)
9410 dmsg = _(
9411 "Append this route to the new route?"); // Picking the
9412 // first point of
9413 // another route?
9414
9415 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9416 dlg_return =
9417 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9418 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9419 m_FinishRouteOnKillFocus = true;
9420
9421 if (dlg_return == wxID_YES) {
9422 appending = true; // part of the other route will be
9423 // appended to the new route
9424 }
9425 }
9426 }
9427 }
9428
9429 // check all other routes to see if this point appears in any other
9430 // route If it appears in NO other route, then it should e
9431 // considered an isolated mark
9432 if (!FindRouteContainingWaypoint(pMousePoint))
9433 pMousePoint->SetShared(true);
9434 }
9435 }
9436
9437 if (NULL == pMousePoint) { // need a new point
9438 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9439 "", wxEmptyString);
9440 pMousePoint->SetNameShown(false);
9441
9442 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9443
9444 if (m_routeState > 1)
9445 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9446 Undo_IsOrphanded, NULL);
9447 }
9448
9449 if (m_routeState == 1) {
9450 // First point in the route.
9451 m_pMouseRoute->AddPoint(pMousePoint);
9452 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9453
9454 } else {
9455 if (m_pMouseRoute->m_NextLegGreatCircle) {
9456 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9457 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9458 &rhumbBearing, &rhumbDist);
9459 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9460 &gcDist, &gcBearing, NULL);
9461 double gcDistNM = gcDist / 1852.0;
9462
9463 // Empirically found expression to get reasonable route segments.
9464 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9465 pow(rhumbDist - gcDistNM - 1, 0.5);
9466
9467 wxString msg;
9468 msg << _("For this leg the Great Circle route is ")
9469 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9470 << _(" shorter than rhumbline.\n\n")
9471 << _("Would you like include the Great Circle routing points "
9472 "for this leg?");
9473
9474#ifndef __WXOSX__
9475 m_FinishRouteOnKillFocus = false;
9476 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9477 wxYES_NO | wxNO_DEFAULT);
9478 m_FinishRouteOnKillFocus = true;
9479#else
9480 int answer = wxID_NO;
9481#endif
9482
9483 if (answer == wxID_YES) {
9484 RoutePoint *gcPoint;
9485 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9486 wxRealPoint gcCoord;
9487
9488 for (int i = 1; i <= segmentCount; i++) {
9489 double fraction = (double)i * (1.0 / (double)segmentCount);
9490 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9491 gcDist * fraction, gcBearing,
9492 &gcCoord.x, &gcCoord.y, NULL);
9493
9494 if (i < segmentCount) {
9495 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9496 wxEmptyString);
9497 gcPoint->SetNameShown(false);
9498 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9499 gcPoint);
9500 } else {
9501 gcPoint = pMousePoint; // Last point, previously exsisting!
9502 }
9503
9504 m_pMouseRoute->AddPoint(gcPoint);
9505 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9506
9507 pSelect->AddSelectableRouteSegment(
9508 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9509 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9510 prevGcPoint = gcPoint;
9511 }
9512
9513 undo->CancelUndoableAction(true);
9514
9515 } else {
9516 m_pMouseRoute->AddPoint(pMousePoint);
9517 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9518 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9519 rlon, m_prev_pMousePoint,
9520 pMousePoint, m_pMouseRoute);
9521 undo->AfterUndoableAction(m_pMouseRoute);
9522 }
9523 } else {
9524 // Ordinary rhumblinesegment.
9525 m_pMouseRoute->AddPoint(pMousePoint);
9526 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9527
9528 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9529 rlon, m_prev_pMousePoint,
9530 pMousePoint, m_pMouseRoute);
9531 undo->AfterUndoableAction(m_pMouseRoute);
9532 }
9533 }
9534
9535 m_prev_rlat = rlat;
9536 m_prev_rlon = rlon;
9537 m_prev_pMousePoint = pMousePoint;
9538 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9539
9540 m_routeState++;
9541
9542 if (appending ||
9543 inserting) { // Appending a route or making a new route
9544 int connect = tail->GetIndexOf(pMousePoint);
9545 if (connect == 0) {
9546 inserting = false; // there is nothing to insert
9547 appending = true; // so append
9548 }
9549 int length = tail->GetnPoints();
9550
9551 int i;
9552 int start, stop;
9553 if (appending) {
9554 start = connect;
9555 stop = length;
9556 } else { // inserting
9557 start = 0;
9558 stop = connect + 1;
9559 m_pMouseRoute->RemovePoint(
9560 m_pMouseRoute
9561 ->GetLastPoint()); // Remove the first and only point
9562 }
9563 for (i = start; i < stop; i++) {
9564 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i + 1), false);
9565 if (m_pMouseRoute)
9566 m_pMouseRoute->m_lastMousePointIndex =
9567 m_pMouseRoute->GetnPoints();
9568 m_routeState++;
9569 top_frame::Get()->RefreshAllCanvas();
9570 ret = true;
9571 }
9572 g_pRouteMan->DeleteRoute(tail);
9573
9574 m_prev_rlat =
9575 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9576 m_prev_rlon =
9577 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9578 m_pMouseRoute->FinalizeForRendering();
9579 }
9580
9581 Refresh(true);
9582 ret = true;
9583 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9584 {
9585 if (m_bedge_pan) {
9586 m_bedge_pan = false;
9587 return false;
9588 }
9589
9590 // Skip if user panned (same logic as route handler above)
9591 {
9592 int mdx = x - m_touchdownPos.x;
9593 int mdy = y - m_touchdownPos.y;
9594 if (mdx * mdx + mdy * mdy > 20 * 20) return false;
9595 }
9596
9597 if (m_nMeasureState == 1) {
9598 m_pMeasureRoute = new Route();
9599 pRouteList->push_back(m_pMeasureRoute);
9600 r_rband.x = x;
9601 r_rband.y = y;
9602 }
9603
9604 if (!g_pRouteMan->IsRouteInList(m_pMeasureRoute)) {
9605 wxLogMessage("Stale measure route");
9606 CancelMeasureRoute();
9607 top_frame::Get()->RefreshAllCanvas();
9608 ret = true;
9609 } else {
9610 if (m_pMeasureRoute) {
9611 RoutePoint *pMousePoint =
9612 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9613 wxEmptyString, wxEmptyString);
9614 pMousePoint->m_bShowName = false;
9615
9616 m_pMeasureRoute->AddPoint(pMousePoint);
9617
9618 m_prev_rlat = m_cursor_lat;
9619 m_prev_rlon = m_cursor_lon;
9620 m_prev_pMousePoint = pMousePoint;
9621 m_pMeasureRoute->m_lastMousePointIndex =
9622 m_pMeasureRoute->GetnPoints();
9623
9624 m_nMeasureState++;
9625 } else {
9626 CancelMeasureRoute();
9627 }
9628 }
9629 Refresh(true);
9630 ret = true;
9631 } else {
9632 bool bSelectAllowed = true;
9633 if (NULL == g_pMarkInfoDialog) {
9634 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9635 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9636 bSelectAllowed = false;
9637
9638 // Avoid accidental selection of routepoint if last touchdown started
9639 // a significant chart drag operation
9640 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9641 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9642 significant_drag) ||
9643 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9644 significant_drag)) {
9645 bSelectAllowed = false;
9646 }
9647
9648 /*if this left up happens at the end of a route point dragging and if
9649 the cursor/thumb is on the draghandle icon, not on the point iself a new
9650 selection will select nothing and the drag will never be ended, so the
9651 legs around this point never selectable. At this step we don't need a
9652 new selection, just keep the previoulsly selected and dragged point */
9653 if (m_bRoutePoinDragging) bSelectAllowed = false;
9654
9655 if (bSelectAllowed) {
9656 bool b_was_editing_mark = m_bMarkEditing;
9657 bool b_was_editing_route = m_bRouteEditing;
9658 FindRoutePointsAtCursor(SelectRadius,
9659 true); // Possibly selecting a point in a
9660 // route for later dragging
9661
9662 /*route and a mark points in layer can't be dragged so should't be
9663 * selected and no draghandle icon*/
9664 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9665 m_pRoutePointEditTarget = NULL;
9666
9667 if (!b_was_editing_route) {
9668 if (m_pEditRouteArray) {
9669 b_startedit_route = true;
9670
9671 // Hide the track and route rollover during route point edit, not
9672 // needed, and may be confusing
9673 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9674 m_pTrackRolloverWin->IsActive(false);
9675 }
9676 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9677 m_pRouteRolloverWin->IsActive(false);
9678 }
9679
9680 wxRect pre_rect;
9681 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9682 ir++) {
9683 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9684 // Need to validate route pointer
9685 // Route may be gone due to drgging close to ownship with
9686 // "Delete On Arrival" state set, as in the case of
9687 // navigating to an isolated waypoint on a temporary route
9688 if (g_pRouteMan->IsRouteValid(pr)) {
9689 // pr->SetHiLite(50);
9690 wxRect route_rect;
9691 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9692 pre_rect.Union(route_rect);
9693 }
9694 }
9695 RefreshRect(pre_rect, true);
9696 }
9697 } else {
9698 b_startedit_route = false;
9699 }
9700
9701 // Mark editing in touch mode, left-up event.
9702 if (m_pRoutePointEditTarget) {
9703 if (b_was_editing_mark ||
9704 b_was_editing_route) { // kill previous hilight
9705 if (m_lastRoutePointEditTarget) {
9706 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9707 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9708 RoutePointGui(*m_lastRoutePointEditTarget)
9709 .EnableDragHandle(false);
9710 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9711 SELTYPE_DRAGHANDLE);
9712 }
9713 }
9714
9715 if (m_pRoutePointEditTarget) {
9716 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9717 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9718 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9719 wxPoint2DDouble dragHandlePoint =
9720 RoutePointGui(*m_pRoutePointEditTarget)
9721 .GetDragHandlePoint(this);
9722 pSelect->AddSelectablePoint(
9723 dragHandlePoint.m_y, dragHandlePoint.m_x,
9724 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9725 }
9726 } else { // Deselect everything
9727 if (m_lastRoutePointEditTarget) {
9728 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9729 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9730 RoutePointGui(*m_lastRoutePointEditTarget)
9731 .EnableDragHandle(false);
9732 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9733 SELTYPE_DRAGHANDLE);
9734
9735 // Clear any routes being edited, probably orphans
9736 wxArrayPtrVoid *lastEditRouteArray =
9738 m_lastRoutePointEditTarget);
9739 if (lastEditRouteArray) {
9740 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9741 ir++) {
9742 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9743 if (g_pRouteMan->IsRouteValid(pr)) {
9744 pr->m_bIsBeingEdited = false;
9745 }
9746 }
9747 delete lastEditRouteArray;
9748 }
9749 }
9750 }
9751
9752 // Do the refresh
9753
9754 if (g_bopengl) {
9755 InvalidateGL();
9756 Refresh(false);
9757 } else {
9758 if (m_lastRoutePointEditTarget) {
9759 wxRect wp_rect;
9760 RoutePointGui(*m_lastRoutePointEditTarget)
9761 .CalculateDCRect(m_dc_route, this, &wp_rect);
9762 RefreshRect(wp_rect, true);
9763 }
9764
9765 if (m_pRoutePointEditTarget) {
9766 wxRect wp_rect;
9767 RoutePointGui(*m_pRoutePointEditTarget)
9768 .CalculateDCRect(m_dc_route, this, &wp_rect);
9769 RefreshRect(wp_rect, true);
9770 }
9771 }
9772 }
9773 } // bSelectAllowed
9774
9775 // Check to see if there is a route or AIS target under the cursor
9776 // If so, start the rollover timer which creates the popup
9777 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9778 bool b_start_rollover = false;
9779 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9780 SelectItem *pFind = pSelectAIS->FindSelection(
9781 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9782 if (pFind) b_start_rollover = true;
9783 }
9784
9785 if (!b_start_rollover && !b_startedit_route) {
9786 SelectableItemList SelList = pSelect->FindSelectionList(
9787 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9788 for (SelectItem *pFindSel : SelList) {
9789 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9790 if (pr && pr->IsVisible()) {
9791 b_start_rollover = true;
9792 break;
9793 }
9794 } // while
9795 }
9796
9797 if (!b_start_rollover && !b_startedit_route) {
9798 SelectableItemList SelList = pSelect->FindSelectionList(
9799 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9800 for (SelectItem *pFindSel : SelList) {
9801 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9802 if (tr && tr->IsVisible()) {
9803 b_start_rollover = true;
9804 break;
9805 }
9806 } // while
9807 }
9808
9809 if (b_start_rollover)
9810 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9811 wxTIMER_ONE_SHOT);
9812 Route *tail = 0;
9813 Route *current = 0;
9814 bool appending = false;
9815 bool inserting = false;
9816 int connect = 0;
9817 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9818 // drag
9819 if (m_pRoutePointEditTarget) {
9820 // Check to see if there is a nearby point which may replace the
9821 // dragged one
9822 RoutePoint *pMousePoint = NULL;
9823
9824 int index_last;
9825 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9826 double nearby_radius_meters =
9827 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9828 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9829 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9830 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9831 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9832 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9833 bool duplicate =
9834 false; // ensure we won't create duplicate point in routes
9835 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9836 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9837 ir++) {
9838 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9839 if (pr && pr->pRoutePointList) {
9840 auto *list = pr->pRoutePointList;
9841 auto pos =
9842 std::find(list->begin(), list->end(), pNearbyPoint);
9843 if (pos != list->end()) {
9844 duplicate = true;
9845 break;
9846 }
9847 }
9848 }
9849 }
9850
9851 // Special case:
9852 // Allow "re-use" of a route's waypoints iff it is a simple
9853 // isolated route. This allows, for instance, creation of a closed
9854 // polygon route
9855 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9856
9857 if (!duplicate) {
9858 int dlg_return;
9859 dlg_return =
9860 OCPNMessageBox(this,
9861 _("Replace this RoutePoint by the nearby "
9862 "Waypoint?"),
9863 _("OpenCPN RoutePoint change"),
9864 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9865 if (dlg_return == wxID_YES) {
9866 /*double confirmation if the dragged point has been manually
9867 * created which can be important and could be deleted
9868 * unintentionally*/
9869
9870 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9871 pNearbyPoint);
9872 current =
9873 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9874
9875 if (tail && current && (tail != current)) {
9876 int dlg_return1;
9877 connect = tail->GetIndexOf(pNearbyPoint);
9878 int index_current_route =
9879 current->GetIndexOf(m_pRoutePointEditTarget);
9880 index_last = current->GetIndexOf(current->GetLastPoint());
9881 dlg_return1 = wxID_NO;
9882 if (index_last ==
9883 index_current_route) { // we are dragging the last
9884 // point of the route
9885 if (connect != tail->GetnPoints()) { // anything to do?
9886
9887 wxString dmsg(
9888 _("Last part of route to be appended to dragged "
9889 "route?"));
9890 if (connect == 0)
9891 dmsg =
9892 _("Full route to be appended to dragged route?");
9893
9894 dlg_return1 = OCPNMessageBox(
9895 this, dmsg, _("OpenCPN Route Create"),
9896 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9897 if (dlg_return1 == wxID_YES) {
9898 appending = true;
9899 }
9900 }
9901 } else if (index_current_route ==
9902 0) { // dragging the first point of the route
9903 if (connect != 0) { // anything to do?
9904
9905 wxString dmsg(
9906 _("First part of route to be inserted into dragged "
9907 "route?"));
9908 if (connect == tail->GetnPoints())
9909 dmsg = _(
9910 "Full route to be inserted into dragged route?");
9911
9912 dlg_return1 = OCPNMessageBox(
9913 this, dmsg, _("OpenCPN Route Create"),
9914 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9915 if (dlg_return1 == wxID_YES) {
9916 inserting = true;
9917 }
9918 }
9919 }
9920 }
9921
9922 if (m_pRoutePointEditTarget->IsShared()) {
9923 // dlg_return = wxID_NO;
9924 dlg_return = OCPNMessageBox(
9925 this,
9926 _("Do you really want to delete and replace this "
9927 "WayPoint") +
9928 "\n" + _("which has been created manually?"),
9929 ("OpenCPN RoutePoint warning"),
9930 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9931 }
9932 }
9933 if (dlg_return == wxID_YES) {
9934 pMousePoint = pNearbyPoint;
9935 if (pMousePoint->m_bIsolatedMark) {
9936 pMousePoint->SetShared(true);
9937 }
9938 pMousePoint->m_bIsolatedMark =
9939 false; // definitely no longer isolated
9940 pMousePoint->m_bIsInRoute = true;
9941 }
9942 }
9943 }
9944 }
9945 if (!pMousePoint)
9946 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9947
9948 if (m_pEditRouteArray) {
9949 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9950 ir++) {
9951 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9952 if (g_pRouteMan->IsRouteValid(pr)) {
9953 if (pMousePoint) { // remove the dragged point and insert the
9954 // nearby
9955 auto *list = pr->pRoutePointList;
9956 auto pos = std::find(list->begin(), list->end(),
9957 m_pRoutePointEditTarget);
9958
9959 pSelect->DeleteAllSelectableRoutePoints(pr);
9960 pSelect->DeleteAllSelectableRouteSegments(pr);
9961
9962 pr->pRoutePointList->insert(pos, pMousePoint);
9963 pos = std::find(list->begin(), list->end(),
9964 m_pRoutePointEditTarget);
9965 pr->pRoutePointList->erase(pos);
9966
9967 pSelect->AddAllSelectableRouteSegments(pr);
9968 pSelect->AddAllSelectableRoutePoints(pr);
9969 }
9970 pr->FinalizeForRendering();
9971 pr->UpdateSegmentDistances();
9972 if (m_bRoutePoinDragging) {
9973 // pConfig->UpdateRoute(pr);
9974 NavObj_dB::GetInstance().UpdateRoute(pr);
9975 }
9976 }
9977 }
9978 }
9979
9980 // Update the RouteProperties Dialog, if currently shown
9981 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9982 if (m_pEditRouteArray) {
9983 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9984 ir++) {
9985 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9986 if (g_pRouteMan->IsRouteValid(pr)) {
9987 if (pRoutePropDialog->GetRoute() == pr) {
9988 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9989 }
9990 /* cannot edit track points anyway
9991 else if ( ( NULL !=
9992 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9993 pTrackPropDialog->m_pTrack == pr ) {
9994 pTrackPropDialog->SetTrackAndUpdate(
9995 pr );
9996 }
9997 */
9998 }
9999 }
10000 }
10001 }
10002 if (pMousePoint) { // clear all about the dragged point
10003 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10004 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10005 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10006 // Hide mark properties dialog if open on the replaced point
10007 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10008 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10009 g_pMarkInfoDialog->Hide();
10010
10011 delete m_pRoutePointEditTarget;
10012 m_lastRoutePointEditTarget = NULL;
10013 m_pRoutePointEditTarget = NULL;
10014 undo->AfterUndoableAction(pMousePoint);
10015 undo->InvalidateUndo();
10016 }
10017 }
10018 }
10019
10020 else if (m_bMarkEditing) { // End of way point drag
10021 if (m_pRoutePointEditTarget)
10022 if (m_bRoutePoinDragging) {
10023 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10024 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10025 }
10026 }
10027
10028 if (m_pRoutePointEditTarget)
10029 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10030
10031 if (!m_pRoutePointEditTarget) {
10032 delete m_pEditRouteArray;
10033 m_pEditRouteArray = NULL;
10034 m_bRouteEditing = false;
10035 }
10036 m_bRoutePoinDragging = false;
10037
10038 if (appending) { // Appending to the route of which the last point is
10039 // dragged onto another route
10040
10041 // copy tail from connect until length to end of current after dragging
10042
10043 int length = tail->GetnPoints();
10044 for (int i = connect + 1; i <= length; i++) {
10045 current->AddPointAndSegment(tail->GetPoint(i), false);
10046 if (current) current->m_lastMousePointIndex = current->GetnPoints();
10047 m_routeState++;
10048 top_frame::Get()->RefreshAllCanvas();
10049 ret = true;
10050 }
10051 current->FinalizeForRendering();
10052 current->m_bIsBeingEdited = false;
10053 FinishRoute();
10054 g_pRouteMan->DeleteRoute(tail);
10055 NavObj_dB::GetInstance().UpdateRoute(current);
10056 }
10057 if (inserting) {
10058 pSelect->DeleteAllSelectableRoutePoints(current);
10059 pSelect->DeleteAllSelectableRouteSegments(current);
10060 for (int i = 1; i < connect + 1; i++) { // numbering in the tail route
10061 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10062 }
10063 pSelect->AddAllSelectableRouteSegments(current);
10064 pSelect->AddAllSelectableRoutePoints(current);
10065 current->FinalizeForRendering();
10066 current->m_bIsBeingEdited = false;
10067 g_pRouteMan->DeleteRoute(tail);
10068 NavObj_dB::GetInstance().UpdateRoute(current);
10069 }
10070
10071 // Update the RouteProperties Dialog, if currently shown
10072 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10073 if (m_pEditRouteArray) {
10074 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
10075 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10076 if (g_pRouteMan->IsRouteValid(pr)) {
10077 if (pRoutePropDialog->GetRoute() == pr) {
10078 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10079 }
10080 }
10081 }
10082 }
10083 }
10084
10085 } // g_btouch
10086
10087 else { // !g_btouch
10088 if (m_bRouteEditing) { // End of RoutePoint drag
10089 Route *tail = 0;
10090 Route *current = 0;
10091 bool appending = false;
10092 bool inserting = false;
10093 int connect = 0;
10094 int index_last;
10095 if (m_pRoutePointEditTarget) {
10096 m_pRoutePointEditTarget->m_bBlink = false;
10097 // Check to see if there is a nearby point which may replace the
10098 // dragged one
10099 RoutePoint *pMousePoint = NULL;
10100 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
10101 double nearby_radius_meters =
10102 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
10103 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
10104 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
10105 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
10106 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
10107 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
10108 bool duplicate = false; // don't create duplicate point in routes
10109 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
10110 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10111 ir++) {
10112 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10113 if (pr && pr->pRoutePointList) {
10114 auto *list = pr->pRoutePointList;
10115 auto pos =
10116 std::find(list->begin(), list->end(), pNearbyPoint);
10117 if (pos != list->end()) {
10118 duplicate = true;
10119 break;
10120 }
10121 }
10122 }
10123 }
10124
10125 // Special case:
10126 // Allow "re-use" of a route's waypoints iff it is a simple
10127 // isolated route. This allows, for instance, creation of a closed
10128 // polygon route
10129 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
10130
10131 if (!duplicate) {
10132 int dlg_return;
10133 dlg_return =
10134 OCPNMessageBox(this,
10135 _("Replace this RoutePoint by the nearby "
10136 "Waypoint?"),
10137 _("OpenCPN RoutePoint change"),
10138 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10139 if (dlg_return == wxID_YES) {
10140 /*double confirmation if the dragged point has been manually
10141 * created which can be important and could be deleted
10142 * unintentionally*/
10143 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10144 pNearbyPoint);
10145 current =
10146 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10147
10148 if (tail && current && (tail != current)) {
10149 int dlg_return1;
10150 connect = tail->GetIndexOf(pNearbyPoint);
10151 int index_current_route =
10152 current->GetIndexOf(m_pRoutePointEditTarget);
10153 index_last = current->GetIndexOf(current->GetLastPoint());
10154 dlg_return1 = wxID_NO;
10155 if (index_last ==
10156 index_current_route) { // we are dragging the last
10157 // point of the route
10158 if (connect != tail->GetnPoints()) { // anything to do?
10159
10160 wxString dmsg(
10161 _("Last part of route to be appended to dragged "
10162 "route?"));
10163 if (connect == 0)
10164 dmsg =
10165 _("Full route to be appended to dragged route?");
10166
10167 dlg_return1 = OCPNMessageBox(
10168 this, dmsg, _("OpenCPN Route Create"),
10169 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10170 if (dlg_return1 == wxID_YES) {
10171 appending = true;
10172 }
10173 }
10174 } else if (index_current_route ==
10175 0) { // dragging the first point of the route
10176 if (connect != 0) { // anything to do?
10177
10178 wxString dmsg(
10179 _("First part of route to be inserted into dragged "
10180 "route?"));
10181 if (connect == tail->GetnPoints())
10182 dmsg = _(
10183 "Full route to be inserted into dragged route?");
10184
10185 dlg_return1 = OCPNMessageBox(
10186 this, dmsg, _("OpenCPN Route Create"),
10187 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10188 if (dlg_return1 == wxID_YES) {
10189 inserting = true;
10190 }
10191 }
10192 }
10193 }
10194
10195 if (m_pRoutePointEditTarget->IsShared()) {
10196 dlg_return = wxID_NO;
10197 dlg_return = OCPNMessageBox(
10198 this,
10199 _("Do you really want to delete and replace this "
10200 "WayPoint") +
10201 "\n" + _("which has been created manually?"),
10202 ("OpenCPN RoutePoint warning"),
10203 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10204 }
10205 }
10206 if (dlg_return == wxID_YES) {
10207 pMousePoint = pNearbyPoint;
10208 if (pMousePoint->m_bIsolatedMark) {
10209 pMousePoint->SetShared(true);
10210 }
10211 pMousePoint->m_bIsolatedMark =
10212 false; // definitely no longer isolated
10213 pMousePoint->m_bIsInRoute = true;
10214 }
10215 }
10216 }
10217 }
10218 if (!pMousePoint)
10219 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10220
10221 if (m_pEditRouteArray) {
10222 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10223 ir++) {
10224 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10225 if (g_pRouteMan->IsRouteValid(pr)) {
10226 if (pMousePoint) { // replace dragged point by nearby one
10227 auto *list = pr->pRoutePointList;
10228 auto pos = std::find(list->begin(), list->end(),
10229 m_pRoutePointEditTarget);
10230
10231 pSelect->DeleteAllSelectableRoutePoints(pr);
10232 pSelect->DeleteAllSelectableRouteSegments(pr);
10233
10234 pr->pRoutePointList->insert(pos, pMousePoint);
10235 pos = std::find(list->begin(), list->end(),
10236 m_pRoutePointEditTarget);
10237 if (pos != list->end()) list->erase(pos);
10238 // pr->pRoutePointList->erase(pos + 1);
10239
10240 pSelect->AddAllSelectableRouteSegments(pr);
10241 pSelect->AddAllSelectableRoutePoints(pr);
10242 }
10243 pr->FinalizeForRendering();
10244 pr->UpdateSegmentDistances();
10245 pr->m_bIsBeingEdited = false;
10246
10247 if (m_bRoutePoinDragging) {
10248 // Special case optimization.
10249 // Dragging a single point of a route
10250 // without any point additions or re-ordering
10251 if (!pMousePoint)
10252 NavObj_dB::GetInstance().UpdateRoutePoint(
10253 m_pRoutePointEditTarget);
10254 else
10255 NavObj_dB::GetInstance().UpdateRoute(pr);
10256 }
10257 pr->SetHiLite(0);
10258 }
10259 }
10260 Refresh(false);
10261 }
10262
10263 if (appending) {
10264 // copy tail from connect until length to end of current after
10265 // dragging
10266
10267 int length = tail->GetnPoints();
10268 for (int i = connect + 1; i <= length; i++) {
10269 current->AddPointAndSegment(tail->GetPoint(i), false);
10270 if (current)
10271 current->m_lastMousePointIndex = current->GetnPoints();
10272 m_routeState++;
10273 top_frame::Get()->RefreshAllCanvas();
10274 ret = true;
10275 }
10276 current->FinalizeForRendering();
10277 current->m_bIsBeingEdited = false;
10278 FinishRoute();
10279 g_pRouteMan->DeleteRoute(tail);
10280 NavObj_dB::GetInstance().UpdateRoute(current);
10281 }
10282 if (inserting) {
10283 pSelect->DeleteAllSelectableRoutePoints(current);
10284 pSelect->DeleteAllSelectableRouteSegments(current);
10285 for (int i = 1; i < connect + 1;
10286 i++) { // numbering in the tail route
10287 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10288 }
10289
10290 pSelect->AddAllSelectableRouteSegments(current);
10291 pSelect->AddAllSelectableRoutePoints(current);
10292 current->FinalizeForRendering();
10293 current->m_bIsBeingEdited = false;
10294 g_pRouteMan->DeleteRoute(tail);
10295 NavObj_dB::GetInstance().UpdateRoute(current);
10296 }
10297
10298 // Update the RouteProperties Dialog, if currently shown
10299 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10300 if (m_pEditRouteArray) {
10301 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10302 ir++) {
10303 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10304 if (g_pRouteMan->IsRouteValid(pr)) {
10305 if (pRoutePropDialog->GetRoute() == pr) {
10306 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10307 }
10308 }
10309 }
10310 }
10311 }
10312
10313 if (pMousePoint) {
10314 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10315 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10316 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10317 // Hide mark properties dialog if open on the replaced point
10318 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10319 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10320 g_pMarkInfoDialog->Hide();
10321
10322 delete m_pRoutePointEditTarget;
10323 m_lastRoutePointEditTarget = NULL;
10324 undo->AfterUndoableAction(pMousePoint);
10325 undo->InvalidateUndo();
10326 } else {
10327 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10328 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10329
10330 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10331 }
10332
10333 delete m_pEditRouteArray;
10334 m_pEditRouteArray = NULL;
10335 }
10336
10337 InvalidateGL();
10338 m_bRouteEditing = false;
10339 m_pRoutePointEditTarget = NULL;
10340
10341 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10342 ret = true;
10343 }
10344
10345 else if (m_bMarkEditing) { // end of Waypoint drag
10346 if (m_pRoutePointEditTarget) {
10347 if (m_bRoutePoinDragging) {
10348 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10349 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10350 }
10351 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10352 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10353 if (!g_bopengl) {
10354 wxRect wp_rect;
10355 RoutePointGui(*m_pRoutePointEditTarget)
10356 .CalculateDCRect(m_dc_route, this, &wp_rect);
10357 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10358 RefreshRect(wp_rect, true);
10359 }
10360 }
10361 m_pRoutePointEditTarget = NULL;
10362 m_bMarkEditing = false;
10363 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10364 ret = true;
10365 }
10366
10367 else if (leftIsDown) { // left click for chart center
10368 leftIsDown = false;
10369 ret = false;
10370
10371 if (!g_btouch) {
10372 if (!m_bChartDragging && !m_bMeasure_Active) {
10373 } else {
10374 m_bChartDragging = false;
10375 }
10376 }
10377 }
10378 m_bRoutePoinDragging = false;
10379 } // !btouch
10380
10381 if (ret) return true;
10382 } // left up
10383
10384 if (event.RightDown()) {
10385 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10386 last_drag.x = mx;
10387 last_drag.y = my;
10388
10389 if (g_btouch) {
10390 // if( m_pRoutePointEditTarget )
10391 // return false;
10392 }
10393
10394 ret = true;
10395 m_FinishRouteOnKillFocus = false;
10396 CallPopupMenu(mx, my);
10397 m_FinishRouteOnKillFocus = true;
10398 } // Right down
10399
10400 return ret;
10401}
10402
10403bool panleftIsDown;
10404bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10405 // Skip all mouse processing if shift is held.
10406 // This allows plugins to implement shift+drag behaviors.
10407 if (event.ShiftDown()) {
10408 return false;
10409 }
10410 int x, y;
10411 event.GetPosition(&x, &y);
10412
10413 x *= m_displayScale;
10414 y *= m_displayScale;
10415
10416 // Check for wheel rotation
10417 // ideally, should be just longer than the time between
10418 // processing accumulated mouse events from the event queue
10419 // as would happen during screen redraws.
10420 int wheel_dir = event.GetWheelRotation();
10421
10422 if (wheel_dir) {
10423 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10424 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10425
10426 double factor = g_mouse_zoom_sensitivity;
10427 if (wheel_dir < 0) factor = 1 / factor;
10428
10429 if (g_bsmoothpanzoom) {
10430 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10431 if (wheel_dir == m_last_wheel_dir) {
10432 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10433 // m_zoom_target /= factor;
10434 } else
10435 StopMovement();
10436 } else {
10437 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10438 m_wheelstopwatch.Start(0);
10439 // m_zoom_target = VPoint.chart_scale / factor;
10440 }
10441 }
10442
10443 m_last_wheel_dir = wheel_dir;
10444
10445 ZoomCanvas(factor, true, false);
10446 }
10447
10448 if (event.LeftDown()) {
10449 // Skip the first left click if it will cause a canvas focus shift
10450 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10451 return false;
10452 }
10453
10454 last_drag.x = x, last_drag.y = y;
10455 m_touchdownPos = wxPoint(x, y);
10456 panleftIsDown = true;
10457 }
10458
10459 if (event.LeftUp()) {
10460 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10461 // seen here.
10462 panleftIsDown = false;
10463
10464 if (!g_btouch) {
10465 if (!m_bChartDragging && !m_bMeasure_Active) {
10466 switch (cursor_region) {
10467 case MID_RIGHT: {
10468 PanCanvas(100, 0);
10469 break;
10470 }
10471
10472 case MID_LEFT: {
10473 PanCanvas(-100, 0);
10474 break;
10475 }
10476
10477 case MID_TOP: {
10478 PanCanvas(0, 100);
10479 break;
10480 }
10481
10482 case MID_BOT: {
10483 PanCanvas(0, -100);
10484 break;
10485 }
10486
10487 case CENTER: {
10488 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10489 break;
10490 }
10491 }
10492 } else {
10493 m_bChartDragging = false;
10494 }
10495 }
10496 }
10497 }
10498
10499 if (event.Dragging() && event.LeftIsDown()) {
10500 /*
10501 * fixed dragging.
10502 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10503 * before the drag event. Hence, as there is no mouse down event, last_drag
10504 * is not reset before the drag. And that results in one single drag
10505 * session, meaning you cannot drag the map a few miles north, lift your
10506 * finger, and the go even further north. Instead, the map resets itself
10507 * always to the very first drag start (since there is not reset of
10508 * last_drag).
10509 *
10510 * Besides, should not left down and dragging be enough of a situation to
10511 * start a drag procedure?
10512 *
10513 * Anyways, guarded it to be active in touch situations only.
10514 */
10515 if (g_btouch && !m_inPinch) {
10516 struct timespec now;
10517 clock_gettime(CLOCK_MONOTONIC, &now);
10518 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10519
10520 bool trigger_hold = false;
10521 if (false == m_bChartDragging) {
10522 if (m_DragTrigger < 0) {
10523 // printf("\ntrigger1\n");
10524 m_DragTrigger = 0;
10525 m_DragTriggerStartTime = tnow;
10526 trigger_hold = true;
10527 } else {
10528 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10529 m_DragTrigger = -1; // Reset trigger
10530 // printf("trigger fired\n");
10531 }
10532 }
10533 }
10534 if (trigger_hold) return true;
10535
10536 if (false == m_bChartDragging) {
10537 // printf("starting drag\n");
10538 // Reset drag calculation members
10539 last_drag.x = x - 1, last_drag.y = y - 1;
10540 m_bChartDragging = true;
10541 m_chart_drag_total_time = 0;
10542 m_chart_drag_total_x = 0;
10543 m_chart_drag_total_y = 0;
10544 m_inertia_last_drag_x = x;
10545 m_inertia_last_drag_y = y;
10546 m_drag_vec_x.clear();
10547 m_drag_vec_y.clear();
10548 m_drag_vec_t.clear();
10549 m_last_drag_time = tnow;
10550 }
10551
10552 // Calculate and store drag dynamics.
10553 uint64_t delta_t = tnow - m_last_drag_time;
10554 double delta_tf = delta_t / 1e9;
10555
10556 m_chart_drag_total_time += delta_tf;
10557 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10558 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10559
10560 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10561 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10562 m_drag_vec_t.push_back(delta_tf);
10563
10564 m_inertia_last_drag_x = x;
10565 m_inertia_last_drag_y = y;
10566 m_last_drag_time = tnow;
10567
10568 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10569 m_bChartDragging = true;
10570 StartTimedMovement();
10571 m_pan_drag.x += last_drag.x - x;
10572 m_pan_drag.y += last_drag.y - y;
10573 last_drag.x = x, last_drag.y = y;
10574 }
10575 } else if (!g_btouch) {
10576 if ((last_drag.x != x) || (last_drag.y != y)) {
10577 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10578 // dragging on route create.
10579 // github #2994
10580 m_bChartDragging = true;
10581 StartTimedMovement();
10582 m_pan_drag.x += last_drag.x - x;
10583 m_pan_drag.y += last_drag.y - y;
10584 last_drag.x = x, last_drag.y = y;
10585 }
10586 }
10587 }
10588
10589 // Handle some special cases
10590 if (g_btouch) {
10591 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10592 // LeftUp handler uses displacement check to distinguish taps from pans.
10593 m_DoubleClickTimer->Start();
10594 singleClickEventIsValid = false;
10595 }
10596 }
10597 }
10598
10599 return true;
10600}
10601
10602void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10603 if (MouseEventOverlayWindows(event)) return;
10604
10605 if (MouseEventSetup(event)) return; // handled, no further action required
10606
10607 bool nm = MouseEventProcessObjects(event);
10608 if (!nm) MouseEventProcessCanvas(event);
10609}
10610
10611void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10612 // Switch to the appropriate cursor on mouse movement
10613
10614 wxCursor *ptarget_cursor = pCursorArrow;
10615 if (!pPlugIn_Cursor) {
10616 ptarget_cursor = pCursorArrow;
10617 if ((!m_routeState) &&
10618 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10619 if (cursor_region == MID_RIGHT) {
10620 ptarget_cursor = pCursorRight;
10621 } else if (cursor_region == MID_LEFT) {
10622 ptarget_cursor = pCursorLeft;
10623 } else if (cursor_region == MID_TOP) {
10624 ptarget_cursor = pCursorDown;
10625 } else if (cursor_region == MID_BOT) {
10626 ptarget_cursor = pCursorUp;
10627 } else {
10628 ptarget_cursor = pCursorArrow;
10629 }
10630 } else if (m_bMeasure_Active ||
10631 m_routeState) // If Measure tool use Pencil Cursor
10632 ptarget_cursor = pCursorPencil;
10633 } else {
10634 ptarget_cursor = pPlugIn_Cursor;
10635 }
10636
10637 SetCursor(*ptarget_cursor);
10638}
10639
10640void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10641 SetCursor(*pCursorArrow);
10642}
10643
10644void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10645 ChartPlugInWrapper *target_plugin_chart = NULL;
10646 s57chart *Chs57 = NULL;
10647 wxFileName file;
10648 wxArrayString files;
10649
10650 ChartBase *target_chart = GetChartAtCursor();
10651 if (target_chart) {
10652 file.Assign(target_chart->GetFullPath());
10653 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10654 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10655 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10656 else
10657 Chs57 = dynamic_cast<s57chart *>(target_chart);
10658 } else { // target_chart = null, might be mbtiles
10659 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10660 unsigned int im = stackIndexArray.size();
10661 int scale = 2147483647; // max 32b integer
10662 if (VPoint.b_quilt && im > 0) {
10663 for (unsigned int is = 0; is < im; is++) {
10664 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10665 CHART_TYPE_MBTILES) {
10666 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10667 double lat, lon;
10668 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10669 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10670 .GetBBox()
10671 .Contains(lat, lon)) {
10672 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10673 scale) {
10674 scale =
10675 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10676 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10677 }
10678 }
10679 }
10680 }
10681 }
10682 }
10683
10684 std::vector<Ais8_001_22 *> area_notices;
10685
10686 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10687 float vp_scale = GetVPScale();
10688
10689 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10690 auto target_data = target.second;
10691 if (!target_data->area_notices.empty()) {
10692 for (auto &ani : target_data->area_notices) {
10693 Ais8_001_22 &area_notice = ani.second;
10694
10695 BoundingBox bbox;
10696
10697 for (Ais8_001_22_SubAreaList::iterator sa =
10698 area_notice.sub_areas.begin();
10699 sa != area_notice.sub_areas.end(); ++sa) {
10700 switch (sa->shape) {
10701 case AIS8_001_22_SHAPE_CIRCLE: {
10702 wxPoint target_point;
10703 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10704 bbox.Expand(target_point);
10705 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10706 break;
10707 }
10708 case AIS8_001_22_SHAPE_RECT: {
10709 wxPoint target_point;
10710 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10711 bbox.Expand(target_point);
10712 if (sa->e_dim_m > sa->n_dim_m)
10713 bbox.EnLarge(sa->e_dim_m * vp_scale);
10714 else
10715 bbox.EnLarge(sa->n_dim_m * vp_scale);
10716 break;
10717 }
10718 case AIS8_001_22_SHAPE_POLYGON:
10719 case AIS8_001_22_SHAPE_POLYLINE: {
10720 for (int i = 0; i < 4; ++i) {
10721 double lat = sa->latitude;
10722 double lon = sa->longitude;
10723 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10724 &lat, &lon);
10725 wxPoint target_point;
10726 GetCanvasPointPix(lat, lon, &target_point);
10727 bbox.Expand(target_point);
10728 }
10729 break;
10730 }
10731 case AIS8_001_22_SHAPE_SECTOR: {
10732 double lat1 = sa->latitude;
10733 double lon1 = sa->longitude;
10734 double lat, lon;
10735 wxPoint target_point;
10736 GetCanvasPointPix(lat1, lon1, &target_point);
10737 bbox.Expand(target_point);
10738 for (int i = 0; i < 18; ++i) {
10739 ll_gc_ll(
10740 lat1, lon1,
10741 sa->left_bound_deg +
10742 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10743 sa->radius_m / 1852.0, &lat, &lon);
10744 GetCanvasPointPix(lat, lon, &target_point);
10745 bbox.Expand(target_point);
10746 }
10747 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10748 &lat, &lon);
10749 GetCanvasPointPix(lat, lon, &target_point);
10750 bbox.Expand(target_point);
10751 break;
10752 }
10753 }
10754 }
10755
10756 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10757 area_notices.push_back(&area_notice);
10758 }
10759 }
10760 }
10761 }
10762 }
10763
10764 if (target_chart || !area_notices.empty() || file.HasName()) {
10765 // Go get the array of all objects at the cursor lat/lon
10766 int sel_rad_pix = 5;
10767 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10768
10769 // Make sure we always get the lights from an object, even if we are
10770 // currently not displaying lights on the chart.
10771
10772 SetCursor(wxCURSOR_WAIT);
10773 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10774 if (!lightsVis) SetShowENCLights(true);
10775 ;
10776
10777 ListOfObjRazRules *rule_list = NULL;
10778 ListOfPI_S57Obj *pi_rule_list = NULL;
10779 if (Chs57)
10780 rule_list =
10781 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10782 else if (target_plugin_chart)
10783 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10784 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10785
10786 ListOfObjRazRules *overlay_rule_list = NULL;
10787 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10788 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10789
10790 if (CHs57_Overlay) {
10791 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10792 zlat, zlon, SelectRadius, &GetVP());
10793 }
10794
10795 if (!lightsVis) SetShowENCLights(false);
10796
10797 wxString objText;
10798 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10799 wxString face = dFont->GetFaceName();
10800
10801 if (NULL == g_pObjectQueryDialog) {
10803 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10804 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10805 }
10806
10807 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10808 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10809
10810#ifdef __WXOSX__
10811 // Auto Adjustment for dark mode
10812 fg = g_pObjectQueryDialog->GetForegroundColour();
10813#endif
10814
10815 objText.Printf(
10816 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10817 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10818
10819#ifdef __WXOSX__
10820 int points = dFont->GetPointSize();
10821#else
10822 int points = dFont->GetPointSize() + 1;
10823#endif
10824
10825 int sizes[7];
10826 for (int i = -2; i < 5; i++) {
10827 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10828 }
10829 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10830
10831 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10832
10833 if (overlay_rule_list && CHs57_Overlay) {
10834 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10835 objText << "<hr noshade>";
10836 }
10837
10838 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10839 an != area_notices.end(); ++an) {
10840 objText << "<b>AIS Area Notice:</b> ";
10841 objText << ais8_001_22_notice_names[(*an)->notice_type];
10842 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10843 (*an)->sub_areas.begin();
10844 sa != (*an)->sub_areas.end(); ++sa)
10845 if (!sa->text.empty()) objText << sa->text;
10846 objText << "<br>expires: " << (*an)->expiry_time.Format();
10847 objText << "<hr noshade>";
10848 }
10849
10850 if (Chs57)
10851 objText << Chs57->CreateObjDescriptions(rule_list);
10852 else if (target_plugin_chart)
10853 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10854 pi_rule_list);
10855
10856 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10857
10858 // Add the additional info files
10859 wxString AddFiles, filenameOK;
10860 int filecount = 0;
10861 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10862 // plugin
10863
10864 AddFiles = wxString::Format(
10865 "<hr noshade><br><b>Additional info files attached to: </b> "
10866 "<font "
10867 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10868 "cellpadding=3>",
10869 file.GetFullName());
10870 file.Normalize();
10871 file.Assign(file.GetPath(), "");
10872 wxDir dir(file.GetFullPath());
10873 wxString filename;
10874 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10875 while (cont) {
10876 file.Assign(dir.GetNameWithSep().append(filename));
10877 wxString FormatString =
10878 "<td valign=top><font size=-2><a "
10879 "href=\"%s\">%s</a></font></td>";
10880 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10881 filenameOK = file.GetFullPath(); // remember last valid name
10882 // we are making a 3 columns table. New row only every third file
10883 if (3 * ((int)filecount / 3) == filecount)
10884 FormatString.Prepend("<tr>"); // new row
10885 else
10886 FormatString.Prepend(
10887 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10888 // spacer column
10889
10890 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10891 file.GetFullName());
10892 filecount++;
10893 }
10894 cont = dir.GetNext(&filename);
10895 }
10896 objText << AddFiles << "</table>";
10897 }
10898 objText << "</font>";
10899 objText << "</body></html>";
10900
10901 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10902 g_pObjectQueryDialog->SetHTMLPage(objText);
10903 g_pObjectQueryDialog->Show();
10904 }
10905 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10906 // generate an event to avoid double code
10907 wxHtmlLinkInfo hli(filenameOK);
10908 wxHtmlLinkEvent hle(1, hli);
10909 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10910 }
10911
10912 if (rule_list) rule_list->Clear();
10913 delete rule_list;
10914
10915 if (overlay_rule_list) overlay_rule_list->Clear();
10916 delete overlay_rule_list;
10917
10918 if (pi_rule_list) pi_rule_list->Clear();
10919 delete pi_rule_list;
10920
10921 SetCursor(wxCURSOR_ARROW);
10922 }
10923}
10924
10925void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10926 bool bNew = false;
10927 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10928 // Dialog
10929 g_pMarkInfoDialog = new MarkInfoDlg(this);
10930 bNew = true;
10931 }
10932
10933 if (1 /*g_bresponsive*/) {
10934 wxSize canvas_size = GetSize();
10935
10936 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10937 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10938
10939 g_pMarkInfoDialog->Layout();
10940
10941 wxPoint canvas_pos = GetPosition();
10942 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10943
10944 bool newFit = false;
10945 if (canvas_size.x < fitted_size.x) {
10946 fitted_size.x = canvas_size.x - 40;
10947 if (canvas_size.y < fitted_size.y)
10948 fitted_size.y -= 40; // scrollbar added
10949 }
10950 if (canvas_size.y < fitted_size.y) {
10951 fitted_size.y = canvas_size.y - 40;
10952 if (canvas_size.x < fitted_size.x)
10953 fitted_size.x -= 40; // scrollbar added
10954 }
10955
10956 if (newFit) {
10957 g_pMarkInfoDialog->SetSize(fitted_size);
10958 g_pMarkInfoDialog->Centre();
10959 }
10960 }
10961
10962 markPoint->m_bRPIsBeingEdited = false;
10963
10964 wxString title_base = _("Mark Properties");
10965 if (markPoint->m_bIsInRoute) {
10966 title_base = _("Waypoint Properties");
10967 }
10968 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10969 g_pMarkInfoDialog->UpdateProperties();
10970 if (markPoint->m_bIsInLayer) {
10971 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10972 GetLayerName(markPoint->m_LayerID)));
10973 g_pMarkInfoDialog->SetDialogTitle(caption);
10974 } else
10975 g_pMarkInfoDialog->SetDialogTitle(title_base);
10976
10977 g_pMarkInfoDialog->Show();
10978 g_pMarkInfoDialog->Raise();
10979 g_pMarkInfoDialog->InitialFocus();
10980 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10981}
10982
10983void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10984 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10985 pRoutePropDialog->SetRouteAndUpdate(selected);
10986 // pNew->UpdateProperties();
10987 pRoutePropDialog->Show();
10988 pRoutePropDialog->Raise();
10989 return;
10990 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10991 this); // There is one global instance of the RouteProp Dialog
10992
10993 if (g_bresponsive) {
10994 wxSize canvas_size = GetSize();
10995 wxPoint canvas_pos = GetPosition();
10996 wxSize fitted_size = pRoutePropDialog->GetSize();
10997 ;
10998
10999 if (canvas_size.x < fitted_size.x) {
11000 fitted_size.x = canvas_size.x;
11001 if (canvas_size.y < fitted_size.y)
11002 fitted_size.y -= 20; // scrollbar added
11003 }
11004 if (canvas_size.y < fitted_size.y) {
11005 fitted_size.y = canvas_size.y;
11006 if (canvas_size.x < fitted_size.x)
11007 fitted_size.x -= 20; // scrollbar added
11008 }
11009
11010 pRoutePropDialog->SetSize(fitted_size);
11011 pRoutePropDialog->Centre();
11012
11013 // int xp = (canvas_size.x - fitted_size.x)/2;
11014 // int yp = (canvas_size.y - fitted_size.y)/2;
11015
11016 wxPoint xxp = ClientToScreen(canvas_pos);
11017 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
11018 }
11019
11020 pRoutePropDialog->SetRouteAndUpdate(selected);
11021
11022 pRoutePropDialog->Show();
11023
11024 Refresh(false);
11025}
11026
11027void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
11028 pTrackPropDialog = TrackPropDlg::getInstance(
11029 this); // There is one global instance of the RouteProp Dialog
11030
11031 pTrackPropDialog->SetTrackAndUpdate(selected);
11033
11034 pTrackPropDialog->Show();
11035
11036 Refresh(false);
11037}
11038
11039void pupHandler_PasteWaypoint() {
11040 Kml kml;
11041
11042 int pasteBuffer = kml.ParsePasteBuffer();
11043 RoutePoint *pasted = kml.GetParsedRoutePoint();
11044 if (!pasted) return;
11045
11046 double nearby_radius_meters =
11047 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
11048
11049 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
11050 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
11051
11052 int answer = wxID_NO;
11053 if (nearPoint && !nearPoint->m_bIsInLayer) {
11054 wxString msg;
11055 msg << _(
11056 "There is an existing waypoint at the same location as the one you are "
11057 "pasting. Would you like to merge the pasted data with it?\n\n");
11058 msg << _("Answering 'No' will create a new waypoint at the same location.");
11059 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
11060 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
11061 }
11062
11063 if (answer == wxID_YES) {
11064 nearPoint->SetName(pasted->GetName());
11065 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
11066 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11067 pRouteManagerDialog->UpdateWptListCtrl();
11068 }
11069
11070 if (answer == wxID_NO) {
11071 RoutePoint *newPoint = new RoutePoint(pasted);
11072 newPoint->m_bIsolatedMark = true;
11073 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11074 newPoint);
11075 // pConfig->AddNewWayPoint(newPoint, -1);
11076 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11077
11078 pWayPointMan->AddRoutePoint(newPoint);
11079 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11080 pRouteManagerDialog->UpdateWptListCtrl();
11081 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11082 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11083 }
11084
11085 top_frame::Get()->InvalidateAllGL();
11086 top_frame::Get()->RefreshAllCanvas(false);
11087}
11088
11089void pupHandler_PasteRoute() {
11090 Kml kml;
11091
11092 int pasteBuffer = kml.ParsePasteBuffer();
11093 Route *pasted = kml.GetParsedRoute();
11094 if (!pasted) return;
11095
11096 double nearby_radius_meters =
11097 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
11098
11099 RoutePoint *curPoint;
11100 RoutePoint *nearPoint;
11101 RoutePoint *prevPoint = NULL;
11102
11103 bool mergepoints = false;
11104 bool createNewRoute = true;
11105 int existingWaypointCounter = 0;
11106
11107 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11108 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
11109 nearPoint = pWayPointMan->GetNearbyWaypoint(
11110 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11111 if (nearPoint) {
11112 mergepoints = true;
11113 existingWaypointCounter++;
11114 // Small hack here to avoid both extending RoutePoint and repeating all
11115 // the GetNearbyWaypoint calculations. Use existin data field in
11116 // RoutePoint as temporary storage.
11117 curPoint->m_bPtIsSelected = true;
11118 }
11119 }
11120
11121 int answer = wxID_NO;
11122 if (mergepoints) {
11123 wxString msg;
11124 msg << _(
11125 "There are existing waypoints at the same location as some of the ones "
11126 "you are pasting. Would you like to just merge the pasted data into "
11127 "them?\n\n");
11128 msg << _("Answering 'No' will create all new waypoints for this route.");
11129 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
11130 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
11131
11132 if (answer == wxID_CANCEL) {
11133 return;
11134 }
11135 }
11136
11137 // If all waypoints exist since before, and a route with the same name, we
11138 // don't create a new route.
11139 if (mergepoints && answer == wxID_YES &&
11140 existingWaypointCounter == pasted->GetnPoints()) {
11141 for (Route *proute : *pRouteList) {
11142 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11143 createNewRoute = false;
11144 break;
11145 }
11146 }
11147 }
11148
11149 Route *newRoute = 0;
11150 RoutePoint *newPoint = 0;
11151
11152 if (createNewRoute) {
11153 newRoute = new Route();
11154 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11155 }
11156
11157 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11158 curPoint = pasted->GetPoint(i);
11159 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11160 curPoint->m_bPtIsSelected = false;
11161 newPoint = pWayPointMan->GetNearbyWaypoint(
11162 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11163 newPoint->SetName(curPoint->GetName());
11164 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11165
11166 if (createNewRoute) newRoute->AddPoint(newPoint);
11167 } else {
11168 curPoint->m_bPtIsSelected = false;
11169
11170 newPoint = new RoutePoint(curPoint);
11171 newPoint->m_bIsolatedMark = false;
11172 newPoint->SetIconName("circle");
11173 newPoint->m_bIsVisible = true;
11174 newPoint->m_bShowName = false;
11175 newPoint->SetShared(false);
11176
11177 newRoute->AddPoint(newPoint);
11178 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11179 newPoint);
11180 // pConfig->AddNewWayPoint(newPoint, -1);
11181 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11182 pWayPointMan->AddRoutePoint(newPoint);
11183 }
11184 if (i > 1 && createNewRoute)
11185 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11186 curPoint->m_lat, curPoint->m_lon,
11187 prevPoint, newPoint, newRoute);
11188 prevPoint = newPoint;
11189 }
11190
11191 if (createNewRoute) {
11192 pRouteList->push_back(newRoute);
11193 // pConfig->AddNewRoute(newRoute); // use auto next num
11194 NavObj_dB::GetInstance().InsertRoute(newRoute);
11195
11196 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11197 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11198 }
11199
11200 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11201 pRouteManagerDialog->UpdateRouteListCtrl();
11202 pRouteManagerDialog->UpdateWptListCtrl();
11203 }
11204 top_frame::Get()->InvalidateAllGL();
11205 top_frame::Get()->RefreshAllCanvas(false);
11206 }
11207 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11208 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11209}
11210
11211void pupHandler_PasteTrack() {
11212 Kml kml;
11213
11214 int pasteBuffer = kml.ParsePasteBuffer();
11215 Track *pasted = kml.GetParsedTrack();
11216 if (!pasted) return;
11217
11218 TrackPoint *curPoint;
11219
11220 Track *newTrack = new Track();
11221 TrackPoint *newPoint;
11222 TrackPoint *prevPoint = NULL;
11223
11224 newTrack->SetName(pasted->GetName());
11225
11226 for (int i = 0; i < pasted->GetnPoints(); i++) {
11227 curPoint = pasted->GetPoint(i);
11228
11229 newPoint = new TrackPoint(curPoint);
11230
11231 wxDateTime now = wxDateTime::Now();
11232 newPoint->SetCreateTime(curPoint->GetCreateTime());
11233
11234 newTrack->AddPoint(newPoint);
11235
11236 if (prevPoint)
11237 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11238 newPoint->m_lat, newPoint->m_lon,
11239 prevPoint, newPoint, newTrack);
11240
11241 prevPoint = newPoint;
11242 }
11243
11244 g_TrackList.push_back(newTrack);
11245 // pConfig->AddNewTrack(newTrack);
11246 NavObj_dB::GetInstance().InsertTrack(newTrack);
11247
11248 top_frame::Get()->InvalidateAllGL();
11249 top_frame::Get()->RefreshAllCanvas(false);
11250}
11251
11252bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11253 nlohmann::json v;
11254 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11255 v["CursorPosition_x"] = x;
11256 v["CursorPosition_y"] = y;
11257 // Send a limited set of selection types depending on what is
11258 // found under the mouse point.
11259 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11260 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11261 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11262
11263 std::string out = v.dump();
11264 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11265
11266 json_msg.Notify(std::make_shared<nlohmann::json>(v), "OCPN_CONTEXT_CLICK");
11267
11268#if 0
11269#define SELTYPE_UNKNOWN 0x0001
11270#define SELTYPE_ROUTEPOINT 0x0002
11271#define SELTYPE_ROUTESEGMENT 0x0004
11272#define SELTYPE_TIDEPOINT 0x0008
11273#define SELTYPE_CURRENTPOINT 0x0010
11274#define SELTYPE_ROUTECREATE 0x0020
11275#define SELTYPE_AISTARGET 0x0040
11276#define SELTYPE_MARKPOINT 0x0080
11277#define SELTYPE_TRACKSEGMENT 0x0100
11278#define SELTYPE_DRAGHANDLE 0x0200
11279#endif
11280
11281 if (g_bhide_context_menus) return true;
11282 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11283 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11284 m_pIDXCandidate, m_nmea_log);
11285
11286 Connect(
11287 wxEVT_COMMAND_MENU_SELECTED,
11288 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11289
11290#ifdef __WXGTK__
11291 // Funny requirement here for gtk, to clear the menu trigger event
11292 // TODO
11293 // Causes a slight "flasH" of the menu,
11294 if (m_inLongPress) {
11295 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11296 m_inLongPress = false;
11297 }
11298#endif
11299
11300 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11301
11302 Disconnect(
11303 wxEVT_COMMAND_MENU_SELECTED,
11304 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11305
11306 delete m_canvasMenu;
11307 m_canvasMenu = NULL;
11308
11309#ifdef __WXQT__
11310 // gFrame->SurfaceToolbar();
11311 // g_MainToolbar->Raise();
11312#endif
11313
11314 return true;
11315}
11316
11317void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11318 // Pass menu events from the canvas to the menu handler
11319 // This is necessarily in ChartCanvas since that is the menu's parent.
11320 if (m_canvasMenu) {
11321 m_canvasMenu->PopupMenuHandler(event);
11322 }
11323 return;
11324}
11325
11326void ChartCanvas::StartRoute() {
11327 // Do not allow more than one canvas to create a route at one time.
11328 if (g_brouteCreating) return;
11329
11330 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11331
11332 g_brouteCreating = true;
11333 m_routeState = 1;
11334 m_bDrawingRoute = false;
11335 SetCursor(*pCursorPencil);
11336 // SetCanvasToolbarItemState(ID_ROUTE, true);
11337 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11338
11339 HideGlobalToolbar();
11340
11341#ifdef __ANDROID__
11342 androidSetRouteAnnunciator(true);
11343#endif
11344}
11345
11346wxString ChartCanvas::FinishRoute() {
11347 m_routeState = 0;
11348 m_prev_pMousePoint = NULL;
11349 m_bDrawingRoute = false;
11350 wxString rv = "";
11351 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11352
11353 // SetCanvasToolbarItemState(ID_ROUTE, false);
11354 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11355#ifdef __ANDROID__
11356 androidSetRouteAnnunciator(false);
11357#endif
11358
11359 SetCursor(*pCursorArrow);
11360
11361 if (m_pMouseRoute) {
11362 if (m_bAppendingRoute) {
11363 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11364 } else {
11365 if (m_pMouseRoute->GetnPoints() > 1) {
11366 // pConfig->AddNewRoute(m_pMouseRoute);
11367 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11368 } else {
11369 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11370 m_pMouseRoute = NULL;
11371 }
11372 }
11373 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11374
11375 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11376 (pRoutePropDialog->IsShown())) {
11377 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11378 }
11379
11380 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11381 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11382 pRouteManagerDialog->UpdateRouteListCtrl();
11383 }
11384 }
11385 m_bAppendingRoute = false;
11386 m_pMouseRoute = NULL;
11387
11388 m_pSelectedRoute = NULL;
11389
11390 undo->InvalidateUndo();
11391 top_frame::Get()->RefreshAllCanvas(true);
11392
11393 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11394
11395 ShowGlobalToolbar();
11396
11397 g_brouteCreating = false;
11398
11399 return rv;
11400}
11401
11402void ChartCanvas::HideGlobalToolbar() {
11403 if (m_canvasIndex == 0) {
11404 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11405 }
11406}
11407
11408void ChartCanvas::ShowGlobalToolbar() {
11409 if (m_canvasIndex == 0) {
11410 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11411 }
11412}
11413
11414void ChartCanvas::ShowAISTargetList() {
11415 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11416 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11417 }
11418
11419 g_pAISTargetList->UpdateAISTargetList();
11420}
11421
11422void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11423 if (!m_bShowOutlines) return;
11424
11425 if (!ChartData) return;
11426
11427 int nEntry = ChartData->GetChartTableEntries();
11428
11429 for (int i = 0; i < nEntry; i++) {
11430 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11431
11432 // Check to see if the candidate chart is in the currently active group
11433 bool b_group_draw = false;
11434 if (m_groupIndex > 0) {
11435 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11436 int index = pt->GetGroupArray()[ig];
11437 if (m_groupIndex == index) {
11438 b_group_draw = true;
11439 break;
11440 }
11441 }
11442 } else
11443 b_group_draw = true;
11444
11445 // Do not render chart outlines if chart identifies as a "basemap"
11446 if (pt->IsBasemap()) continue;
11447
11448 if (b_group_draw) RenderChartOutline(dc, i, vp);
11449 }
11450
11451 // On CM93 Composite Charts, draw the outlines of the next smaller
11452 // scale cell
11453 cm93compchart *pcm93 = NULL;
11454 if (VPoint.b_quilt) {
11455 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11456 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11457 pcm93 = (cm93compchart *)pch;
11458 break;
11459 }
11460 } else if (m_singleChart &&
11461 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11462 pcm93 = (cm93compchart *)m_singleChart;
11463
11464 if (pcm93) {
11465 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11466 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11467
11468 if (zoom_factor > 8.0) {
11469 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11470 dc.SetPen(mPen);
11471 } else {
11472 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11473 dc.SetPen(mPen);
11474 }
11475
11476 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11477 }
11478}
11479
11480void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11481#ifdef ocpnUSE_GL
11482 if (g_bopengl && m_glcc) {
11483 /* opengl version specially optimized */
11484 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11485 return;
11486 }
11487#endif
11488
11489 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11490 if (!ChartData->IsChartAvailable(dbIndex)) return;
11491 }
11492
11493 float plylat, plylon;
11494 float plylat1, plylon1;
11495
11496 int pixx, pixy, pixx1, pixy1;
11497
11498 LLBBox box;
11499 ChartData->GetDBBoundingBox(dbIndex, box);
11500
11501 // Don't draw an outline in the case where the chart covers the entire world
11502 // */
11503 if (box.GetLonRange() == 360) return;
11504
11505 double lon_bias = 0;
11506 // chart is outside of viewport lat/lon bounding box
11507 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11508
11509 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11510
11511 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11512 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11513
11514 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11515 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11516
11517 else
11518 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11519
11520 // Are there any aux ply entries?
11521 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11522 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11523 {
11524 wxPoint r, r1;
11525
11526 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11527 plylon += lon_bias;
11528
11529 GetCanvasPointPix(plylat, plylon, &r);
11530 pixx = r.x;
11531 pixy = r.y;
11532
11533 for (int i = 0; i < nPly - 1; i++) {
11534 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11535 plylon1 += lon_bias;
11536
11537 GetCanvasPointPix(plylat1, plylon1, &r1);
11538 pixx1 = r1.x;
11539 pixy1 = r1.y;
11540
11541 int pixxs1 = pixx1;
11542 int pixys1 = pixy1;
11543
11544 bool b_skip = false;
11545
11546 if (vp.chart_scale > 5e7) {
11547 // calculate projected distance between these two points in meters
11548 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11549 pow((double)(pixy1 - pixy), 2)) /
11550 vp.view_scale_ppm;
11551
11552 if (dist > 0.0) {
11553 // calculate GC distance between these two points in meters
11554 double distgc =
11555 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11556
11557 // If the distances are nonsense, it means that the scale is very
11558 // small and the segment wrapped the world So skip it....
11559 // TODO improve this to draw two segments
11560 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11561 b_skip = true;
11562 } else
11563 b_skip = true;
11564 }
11565
11566 ClipResult res = cohen_sutherland_line_clip_i(
11567 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11568 if (res != Invisible && !b_skip)
11569 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11570
11571 plylat = plylat1;
11572 plylon = plylon1;
11573 pixx = pixxs1;
11574 pixy = pixys1;
11575 }
11576
11577 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11578 plylon1 += lon_bias;
11579
11580 GetCanvasPointPix(plylat1, plylon1, &r1);
11581 pixx1 = r1.x;
11582 pixy1 = r1.y;
11583
11584 ClipResult res = cohen_sutherland_line_clip_i(
11585 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11586 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11587 }
11588
11589 else // Use Aux PlyPoints
11590 {
11591 wxPoint r, r1;
11592
11593 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11594 for (int j = 0; j < nAuxPlyEntries; j++) {
11595 int nAuxPly =
11596 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11597 GetCanvasPointPix(plylat, plylon, &r);
11598 pixx = r.x;
11599 pixy = r.y;
11600
11601 for (int i = 0; i < nAuxPly - 1; i++) {
11602 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11603
11604 GetCanvasPointPix(plylat1, plylon1, &r1);
11605 pixx1 = r1.x;
11606 pixy1 = r1.y;
11607
11608 int pixxs1 = pixx1;
11609 int pixys1 = pixy1;
11610
11611 bool b_skip = false;
11612
11613 if (vp.chart_scale > 5e7) {
11614 // calculate projected distance between these two points in meters
11615 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11616 ((pixy1 - pixy) * (pixy1 - pixy))) /
11617 vp.view_scale_ppm;
11618 if (dist > 0.0) {
11619 // calculate GC distance between these two points in meters
11620 double distgc =
11621 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11622
11623 // If the distances are nonsense, it means that the scale is very
11624 // small and the segment wrapped the world So skip it....
11625 // TODO improve this to draw two segments
11626 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11627 b_skip = true;
11628 } else
11629 b_skip = true;
11630 }
11631
11632 ClipResult res = cohen_sutherland_line_clip_i(
11633 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11634 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11635
11636 plylat = plylat1;
11637 plylon = plylon1;
11638 pixx = pixxs1;
11639 pixy = pixys1;
11640 }
11641
11642 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11643 GetCanvasPointPix(plylat1, plylon1, &r1);
11644 pixx1 = r1.x;
11645 pixy1 = r1.y;
11646
11647 ClipResult res = cohen_sutherland_line_clip_i(
11648 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11649 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11650 }
11651 }
11652}
11653
11654static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11655 const wxArrayString &legend) {
11656 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11657
11658 int pointsize = dFont->GetPointSize();
11659 pointsize /= OCPN_GetWinDIPScaleFactor();
11660
11661 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11662 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11663 false, dFont->GetFaceName());
11664
11665 dc.SetFont(*psRLI_font);
11666
11667 int h = 0;
11668 int w = 0;
11669 int hl, wl;
11670
11671 int xp, yp;
11672 int hilite_offset = 3;
11673
11674 for (wxString line : legend) {
11675#ifdef __WXMAC__
11676 wxScreenDC sdc;
11677 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11678#else
11679 dc.GetTextExtent(line, &wl, &hl);
11680 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11681 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11682#endif
11683 h += hl;
11684 w = wxMax(w, wl);
11685 }
11686 w += (hl / 2); // Add a little right pad
11687
11688 xp = ref_point.x - w;
11689 yp = ref_point.y;
11690 yp += hilite_offset;
11691
11692 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11693
11694 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11695 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11696
11697 for (wxString line : legend) {
11698 dc.DrawText(line, xp, yp);
11699 yp += hl;
11700 }
11701}
11702
11703void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11704 if (!g_bAllowShipToActive) return;
11705
11706 Route *rt = g_pRouteMan->GetpActiveRoute();
11707 if (!rt) return;
11708
11709 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11710 wxPoint2DDouble pa, pb;
11712 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11713
11714 // set pen
11715 int width =
11716 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11717 if (rt->m_width != wxPENSTYLE_INVALID)
11718 width = rt->m_width; // set route pen style if any
11719 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11720 g_shipToActiveStyle, 5)]; // get setting pen style
11721 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11722 wxColour color =
11723 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11724 : // set setting route pen color
11725 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11726 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11727
11728 dc.SetPen(*mypen);
11729 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11730
11731 if (!Use_Opengl)
11732 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11733 (int)pb.m_y, GetVP(), true);
11734
11735#ifdef ocpnUSE_GL
11736 else {
11737#ifdef USE_ANDROID_GLES2
11738 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11739#else
11740 if (style != wxPENSTYLE_SOLID) {
11741 if (glChartCanvas::dash_map.find(style) !=
11742 glChartCanvas::dash_map.end()) {
11743 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11744 dc.SetPen(*mypen);
11745 }
11746 }
11747 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11748#endif
11749
11750 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11751 (int)pb.m_x, (int)pb.m_y, GetVP());
11752 }
11753#endif
11754 }
11755}
11756
11757void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11758 Route *route = 0;
11759 if (m_routeState >= 2) route = m_pMouseRoute;
11760 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11761 route = m_pMeasureRoute;
11762
11763 if (!route) return;
11764
11765 // Validate route pointer
11766 if (!g_pRouteMan->IsRouteValid(route)) return;
11767
11768 double render_lat = m_cursor_lat;
11769 double render_lon = m_cursor_lon;
11770
11771 int np = route->GetnPoints();
11772 if (np) {
11773 if (g_btouch && (np > 1)) np--;
11774 RoutePoint rp = route->GetPoint(np);
11775 render_lat = rp.m_lat;
11776 render_lon = rp.m_lon;
11777 }
11778
11779 double rhumbBearing, rhumbDist;
11780 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11781 &rhumbBearing, &rhumbDist);
11782 double brg = rhumbBearing;
11783 double dist = rhumbDist;
11784
11785 // Skip GreatCircle rubberbanding on touch devices.
11786 if (!g_btouch) {
11787 double gcBearing, gcBearing2, gcDist;
11788 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11789 m_cursor_lat, &gcDist, &gcBearing,
11790 &gcBearing2);
11791 double gcDistm = gcDist / 1852.0;
11792
11793 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11794 rhumbBearing = 90.;
11795
11796 wxPoint destPoint, lastPoint;
11797
11798 route->m_NextLegGreatCircle = false;
11799 int milesDiff = rhumbDist - gcDistm;
11800 if (milesDiff > 1) {
11801 brg = gcBearing;
11802 dist = gcDistm;
11803 route->m_NextLegGreatCircle = true;
11804 }
11805
11806 // FIXME (MacOS, the first segment is rendered wrong)
11807 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11808 &lastPoint);
11809
11810 if (route->m_NextLegGreatCircle) {
11811 for (int i = 1; i <= milesDiff; i++) {
11812 double p = (double)i * (1.0 / (double)milesDiff);
11813 double pLat, pLon;
11814 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11815 &pLon, &pLat, &gcBearing2);
11816 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11817 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11818 false);
11819 lastPoint = destPoint;
11820 }
11821 } else {
11822 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11823 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11824 false);
11825 if (m_bMeasure_DistCircle) {
11826 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11827 powf((float)(r_rband.y - lastPoint.y), 2));
11828
11829 dc.SetPen(*g_pRouteMan->GetRoutePen());
11830 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11831 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11832 }
11833 }
11834 }
11835 }
11836
11837 wxString routeInfo;
11838 wxArrayString infoArray;
11839 double varBrg = 0;
11840 if (g_bShowTrue)
11841 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11842 0x00B0);
11843
11844 if (g_bShowMag) {
11845 double latAverage = (m_cursor_lat + render_lat) / 2;
11846 double lonAverage = (m_cursor_lon + render_lon) / 2;
11847 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11848
11849 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11850 (int)varBrg, 0x00B0);
11851 }
11852 routeInfo << " " << FormatDistanceAdaptive(dist);
11853 infoArray.Add(routeInfo);
11854 routeInfo.Clear();
11855
11856 // To make it easier to use a route as a bearing on a charted object add for
11857 // the first leg also the reverse bearing.
11858 if (np == 1) {
11859 routeInfo << "Reverse: ";
11860 if (g_bShowTrue)
11861 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11862 (int)(brg + 180.) % 360, 0x00B0);
11863 if (g_bShowMag)
11864 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11865 (int)(varBrg + 180.) % 360, 0x00B0);
11866 infoArray.Add(routeInfo);
11867 routeInfo.Clear();
11868 }
11869
11870 wxString s0;
11871 if (!route->m_bIsInLayer)
11872 s0.Append(_("Route") + ": ");
11873 else
11874 s0.Append(_("Layer Route: "));
11875
11876 double disp_length = route->m_route_length;
11877 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11878 s0 += FormatDistanceAdaptive(disp_length);
11879
11880 infoArray.Add(s0);
11881 routeInfo.Clear();
11882
11883 RouteLegInfo(dc, r_rband, infoArray);
11884
11885 m_brepaint_piano = true;
11886}
11887
11888void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11889 if (!m_bShowVisibleSectors) return;
11890
11891 if (g_bDeferredInitDone) {
11892 // need to re-evaluate sectors?
11893 double rhumbBearing, rhumbDist;
11894 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11895 &rhumbBearing, &rhumbDist);
11896
11897 if (rhumbDist > 0.05) // miles
11898 {
11899 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11900 m_sectorlegsVisible);
11901 m_sector_glat = gLat;
11902 m_sector_glon = gLon;
11903 }
11904 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11905 }
11906}
11907
11908void ChartCanvas::WarpPointerDeferred(int x, int y) {
11909 warp_x = x;
11910 warp_y = y;
11911 warp_flag = true;
11912}
11913
11914int s_msg;
11915
11916void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11917 if (!ps52plib) return;
11918
11919 if (VPoint.b_quilt) { // quilted
11920 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11921
11922 if (m_pQuilt->IsQuiltVector()) {
11923 if (ps52plib->GetStateHash() != m_s52StateHash) {
11924 UpdateS52State();
11925 m_s52StateHash = ps52plib->GetStateHash();
11926 }
11927 }
11928 } else {
11929 if (ps52plib->GetStateHash() != m_s52StateHash) {
11930 UpdateS52State();
11931 m_s52StateHash = ps52plib->GetStateHash();
11932 }
11933 }
11934
11935 // Plugin charts
11936 bool bSendPlibState = true;
11937 if (VPoint.b_quilt) { // quilted
11938 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11939 }
11940
11941 if (bSendPlibState) {
11942 nlohmann::json v;
11943 v["OpenCPN Version Major"] = VERSION_MAJOR;
11944 v["OpenCPN Version Minor"] = VERSION_MINOR;
11945 v["OpenCPN Version Patch"] = VERSION_PATCH;
11946 v["OpenCPN Version Date"] = VERSION_DATE;
11947 v["OpenCPN Version Full"] = VERSION_FULL;
11948
11949 // S52PLIB state
11950 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11951 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11952 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11953 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11954 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11955 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11956 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11957
11958 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11959
11960 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11961 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11962
11963 // Global S52 options
11964
11965 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11966 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11967 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11968 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11969 ps52plib->m_bShowS57ImportantTextOnly;
11970 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11971 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11972 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11973 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11974 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11975
11976 // Some global GUI parameters, for completeness
11977 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11978 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11979 v["OpenCPN Scale Factor Exp"] =
11980 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11981 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11982
11983 const std::string out = v.dump();
11984
11985 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11986 SendMessageToAllPlugins("OpenCPN Config", out);
11987 g_lastS52PLIBPluginMessage = out;
11988 }
11989 }
11990}
11991int spaint;
11992int s_in_update;
11993void ChartCanvas::OnPaint(wxPaintEvent &event) {
11994 wxPaintDC dc(this);
11995
11996 // GetToolbar()->Show( m_bToolbarEnable );
11997
11998 // Paint updates may have been externally disabled (temporarily, to avoid
11999 // Yield() recursion performance loss) It is important that the wxPaintDC is
12000 // built, even if we elect to not process this paint message. Otherwise, the
12001 // paint message may not be removed from the message queue, esp on Windows.
12002 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
12003
12004 if (!m_b_paint_enable) {
12005 return;
12006 }
12007
12008 // If necessary, reconfigure the S52 PLIB
12010
12011#ifdef ocpnUSE_GL
12012 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
12013
12014 if (m_glcc && g_bopengl) {
12015 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
12016 s_in_update++;
12017 m_glcc->Update();
12018 s_in_update--;
12019 }
12020
12021 return;
12022 }
12023#endif
12024
12025 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
12026
12027 wxRegion ru = GetUpdateRegion();
12028
12029 int rx, ry, rwidth, rheight;
12030 ru.GetBox(rx, ry, rwidth, rheight);
12031
12032#ifdef ocpnUSE_DIBSECTION
12033 ocpnMemDC temp_dc;
12034#else
12035 wxMemoryDC temp_dc;
12036#endif
12037
12038 long height = GetVP().pix_height;
12039
12040#ifdef __WXMAC__
12041 // On OS X we have to explicitly extend the region for the piano area
12042 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12043 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
12044 height += m_Piano->GetHeight();
12045#endif // __WXMAC__
12046 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
12047
12048 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
12049 if (pthumbwin) {
12050 int thumbx, thumby, thumbsx, thumbsy;
12051 pthumbwin->GetPosition(&thumbx, &thumby);
12052 pthumbwin->GetSize(&thumbsx, &thumbsy);
12053 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
12054
12055 if (pthumbwin->IsShown()) {
12056 rgn_chart.Subtract(rgn_thumbwin);
12057 ru.Subtract(rgn_thumbwin);
12058 }
12059 }
12060
12061 // subtract the chart bar if it isn't transparent, and determine if we need to
12062 // paint it
12063 wxRegion rgn_blit = ru;
12064 if (g_bShowChartBar) {
12065 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
12066 GetClientSize().x, m_Piano->GetHeight());
12067
12068 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12069 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
12070 if (style->chartStatusWindowTransparent)
12071 m_brepaint_piano = true;
12072 else
12073 ru.Subtract(chart_bar_rect);
12074 }
12075 }
12076
12077 if (m_Compass && m_Compass->IsShown()) {
12078 wxRect compassRect = m_Compass->GetRect();
12079 if (ru.Contains(compassRect) != wxOutRegion) {
12080 ru.Subtract(compassRect);
12081 }
12082 }
12083
12084 if (m_notification_button) {
12085 wxRect noteRect = m_notification_button->GetRect();
12086 if (ru.Contains(noteRect) != wxOutRegion) {
12087 ru.Subtract(noteRect);
12088 }
12089 }
12090
12091 // Is this viewpoint the same as the previously painted one?
12092 bool b_newview = true;
12093
12094 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12095 (m_cache_vp.rotation == VPoint.rotation) &&
12096 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
12097 m_cache_vp.IsValid()) {
12098 b_newview = false;
12099 }
12100
12101 // If the ViewPort is skewed or rotated, we may be able to use the cached
12102 // rotated bitmap.
12103 bool b_rcache_ok = false;
12104 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
12105 b_rcache_ok = !b_newview;
12106
12107 // Make a special VP
12108 if (VPoint.b_MercatorProjectionOverride)
12109 VPoint.SetProjectionType(PROJECTION_MERCATOR);
12110 ViewPort svp = VPoint;
12111
12112 svp.pix_width = svp.rv_rect.width;
12113 svp.pix_height = svp.rv_rect.height;
12114
12115 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
12116 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
12117 // VPoint.rv_rect.height);
12118
12119 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
12120
12121 // If we are going to use the cached rotated image, there is no need to fetch
12122 // any chart data and this will do it...
12123 if (b_rcache_ok) chart_get_region.Clear();
12124
12125 // Blit pan acceleration
12126 if (VPoint.b_quilt) // quilted
12127 {
12128 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12129
12130 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12131
12132 bool busy = false;
12133 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12134 m_cache_vp.rotation != VPoint.rotation)) {
12135 AbstractPlatform::ShowBusySpinner();
12136 busy = true;
12137 }
12138
12139 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12140 (m_working_bm.GetHeight() != svp.pix_height))
12141 m_working_bm.Create(svp.pix_width, svp.pix_height,
12142 -1); // make sure the target is big enoug
12143
12144 if (fabs(VPoint.rotation) < 0.01) {
12145 bool b_save = true;
12146
12147 if (g_SencThreadManager) {
12148 if (g_SencThreadManager->GetJobCount()) {
12149 b_save = false;
12150 m_cache_vp.Invalidate();
12151 }
12152 }
12153
12154 // If the saved wxBitmap from last OnPaint is useable
12155 // calculate the blit parameters
12156
12157 // We can only do screen blit painting if subsequent ViewPorts differ by
12158 // whole pixels So, in small scale bFollow mode, force the full screen
12159 // render. This seems a hack....There may be better logic here.....
12160
12161 // if(m_bFollow)
12162 // b_save = false;
12163
12164 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12165 if (b_newview) {
12166 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12167 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12168
12169 int dy = c_new.y - c_old.y;
12170 int dx = c_new.x - c_old.x;
12171
12172 // printf("In OnPaint Trying Blit dx: %d
12173 // dy:%d\n\n", dx, dy);
12174
12175 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12176 if (dx || dy) {
12177 // Blit the reuseable portion of the cached wxBitmap to a working
12178 // bitmap
12179 temp_dc.SelectObject(m_working_bm);
12180
12181 wxMemoryDC cache_dc;
12182 cache_dc.SelectObject(m_cached_chart_bm);
12183
12184 if (dy > 0) {
12185 if (dx > 0) {
12186 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12187 VPoint.pix_height - dy, &cache_dc, dx, dy);
12188 } else {
12189 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12190 VPoint.pix_height - dy, &cache_dc, 0, dy);
12191 }
12192
12193 } else {
12194 if (dx > 0) {
12195 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12196 VPoint.pix_height + dy, &cache_dc, dx, 0);
12197 } else {
12198 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12199 VPoint.pix_height + dy, &cache_dc, 0, 0);
12200 }
12201 }
12202
12203 OCPNRegion update_region;
12204 if (dy) {
12205 if (dy > 0)
12206 update_region.Union(
12207 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12208 else
12209 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12210 }
12211
12212 if (dx) {
12213 if (dx > 0)
12214 update_region.Union(
12215 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12216 else
12217 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12218 }
12219
12220 // Render the new region
12221 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12222 update_region);
12223 cache_dc.SelectObject(wxNullBitmap);
12224 } else {
12225 // No sensible (dx, dy) change in the view, so use the cached
12226 // member bitmap
12227 temp_dc.SelectObject(m_cached_chart_bm);
12228 b_save = false;
12229 }
12230 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12231
12232 } else // not blitable
12233 {
12234 temp_dc.SelectObject(m_working_bm);
12235 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12236 chart_get_region);
12237 }
12238 } else {
12239 // No change in the view, so use the cached member bitmap2
12240 temp_dc.SelectObject(m_cached_chart_bm);
12241 b_save = false;
12242 }
12243 } else // cached bitmap is not yet valid
12244 {
12245 temp_dc.SelectObject(m_working_bm);
12246 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12247 chart_get_region);
12248 }
12249
12250 // Save the fully rendered quilt image as a wxBitmap member of this class
12251 if (b_save) {
12252 // if((m_cached_chart_bm.GetWidth() !=
12253 // svp.pix_width) ||
12254 // (m_cached_chart_bm.GetHeight() !=
12255 // svp.pix_height))
12256 // m_cached_chart_bm.Create(svp.pix_width,
12257 // svp.pix_height, -1); // target wxBitmap
12258 // is big enough
12259 wxMemoryDC scratch_dc_0;
12260 scratch_dc_0.SelectObject(m_cached_chart_bm);
12261 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12262
12263 scratch_dc_0.SelectObject(wxNullBitmap);
12264
12265 m_bm_cache_vp =
12266 VPoint; // save the ViewPort associated with the cached wxBitmap
12267 }
12268 }
12269
12270 else // quilted, rotated
12271 {
12272 temp_dc.SelectObject(m_working_bm);
12273 OCPNRegion chart_get_all_region(
12274 wxRect(0, 0, svp.pix_width, svp.pix_height));
12275 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12276 chart_get_all_region);
12277 }
12278
12279 AbstractPlatform::HideBusySpinner();
12280
12281 }
12282
12283 else // not quilted
12284 {
12285 if (!m_singleChart) {
12286 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12287 dc.Clear();
12288 return;
12289 }
12290
12291 if (!chart_get_region.IsEmpty()) {
12292 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12293 }
12294 }
12295
12296 if (temp_dc.IsOk()) {
12297 // Arrange to render the World Chart vector data behind the rendered
12298 // current chart so that uncovered canvas areas show at least the world
12299 // chart.
12300 OCPNRegion chartValidRegion;
12301 if (!VPoint.b_quilt) {
12302 // Make a region covering the current chart on the canvas
12303
12304 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12305 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12306 else {
12307 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12308 // require that the viewport passed here have pix_width and pix_height
12309 // set to the actual display, not the virtual (rv_rect) sizes
12310 // (the vector calculations require the virtual sizes in svp)
12311
12312 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12313 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12314 }
12315 } else
12316 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12317
12318 temp_dc.DestroyClippingRegion();
12319
12320 // Copy current chart region
12321 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12322
12323 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12324
12325 if (!backgroundRegion.IsEmpty()) {
12326 // Draw the Background Chart only in the areas NOT covered by the
12327 // current chart view
12328
12329 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12330 clipping regions with more than 1 rectangle so... */
12331 wxColour water = pWorldBackgroundChart->water;
12332 if (water.IsOk()) {
12333 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12334 temp_dc.SetBrush(wxBrush(water));
12335 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12336 while (upd.HaveRects()) {
12337 wxRect rect = upd.GetRect();
12338 temp_dc.DrawRectangle(rect);
12339 upd.NextRect();
12340 }
12341 }
12342 // Associate with temp_dc
12343 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12344 temp_dc.SetDeviceClippingRegion(*clip_region);
12345 delete clip_region;
12346
12347 ocpnDC bgdc(temp_dc);
12348 double r = VPoint.rotation;
12349 SetVPRotation(VPoint.skew);
12350
12351 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12352 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12353
12354 SetVPRotation(r);
12355 }
12356 } // temp_dc.IsOk();
12357
12358 wxMemoryDC *pChartDC = &temp_dc;
12359 wxMemoryDC rotd_dc;
12360
12361 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12362 // Can we use the current rotated image cache?
12363 if (!b_rcache_ok) {
12364#ifdef __WXMSW__
12365 wxMemoryDC tbase_dc;
12366 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12367 tbase_dc.SelectObject(bm_base);
12368 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12369 tbase_dc.SelectObject(wxNullBitmap);
12370#else
12371 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12372#endif
12373
12374 wxImage base_image;
12375 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12376
12377 // Use a local static image rotator to improve wxWidgets code profile
12378 // Especially, on GTK the wxRound and wxRealPoint functions are very
12379 // expensive.....
12380
12381 double angle = GetVP().skew - GetVP().rotation;
12382 wxImage ri;
12383 bool b_rot_ok = false;
12384 if (base_image.IsOk()) {
12385 ViewPort rot_vp = GetVP();
12386
12387 m_b_rot_hidef = false;
12388
12389 ri = Image_Rotate(
12390 base_image, angle,
12391 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12392 m_b_rot_hidef, &m_roffset);
12393
12394 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12395 (rot_vp.rotation == VPoint.rotation) &&
12396 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12397 rot_vp.IsValid() && (ri.IsOk())) {
12398 b_rot_ok = true;
12399 }
12400 }
12401
12402 if (b_rot_ok) {
12403 delete m_prot_bm;
12404 m_prot_bm = new wxBitmap(ri);
12405 }
12406
12407 m_roffset.x += VPoint.rv_rect.x;
12408 m_roffset.y += VPoint.rv_rect.y;
12409 }
12410
12411 if (m_prot_bm && m_prot_bm->IsOk()) {
12412 rotd_dc.SelectObject(*m_prot_bm);
12413 pChartDC = &rotd_dc;
12414 } else {
12415 pChartDC = &temp_dc;
12416 m_roffset = wxPoint(0, 0);
12417 }
12418 } else { // unrotated
12419 pChartDC = &temp_dc;
12420 m_roffset = wxPoint(0, 0);
12421 }
12422
12423 wxPoint offset = m_roffset;
12424
12425 // Save the PixelCache viewpoint for next time
12426 m_cache_vp = VPoint;
12427
12428 // Set up a scratch DC for overlay objects
12429 wxMemoryDC mscratch_dc;
12430 mscratch_dc.SelectObject(*pscratch_bm);
12431
12432 mscratch_dc.ResetBoundingBox();
12433 mscratch_dc.DestroyClippingRegion();
12434 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12435
12436 // Blit the externally invalidated areas of the chart onto the scratch dc
12437 wxRegionIterator upd(rgn_blit); // get the update rect list
12438 while (upd) {
12439 wxRect rect = upd.GetRect();
12440
12441 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12442 rect.x - offset.x, rect.y - offset.y);
12443 upd++;
12444 }
12445
12446 // If multi-canvas, indicate which canvas has keyboard focus
12447 // by drawing a simple blue bar at the top.
12448 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12449 if (this == wxWindow::FindFocus()) {
12450 g_focusCanvas = this;
12451
12452 wxColour colour = GetGlobalColor("BLUE4");
12453 mscratch_dc.SetPen(wxPen(colour));
12454 mscratch_dc.SetBrush(wxBrush(colour));
12455
12456 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12457 mscratch_dc.DrawRectangle(activeRect);
12458 }
12459 }
12460
12461 // Any MBtiles?
12462 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12463 unsigned int im = stackIndexArray.size();
12464 if (VPoint.b_quilt && im > 0) {
12465 std::vector<int> tiles_to_show;
12466 for (unsigned int is = 0; is < im; is++) {
12467 const ChartTableEntry &cte =
12468 ChartData->GetChartTableEntry(stackIndexArray[is]);
12469 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12470 continue;
12471 }
12472 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12473 tiles_to_show.push_back(stackIndexArray[is]);
12474 }
12475 }
12476
12477 if (tiles_to_show.size())
12478 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12479 }
12480
12481 // May get an unexpected OnPaint call while switching display modes
12482 // Guard for that.
12483 if (!g_bopengl) {
12484 ocpnDC scratch_dc(mscratch_dc);
12485 RenderAlertMessage(mscratch_dc, GetVP());
12486 }
12487
12488#if 0
12489 // quiting?
12490 if (g_bquiting) {
12491#ifdef ocpnUSE_DIBSECTION
12492 ocpnMemDC q_dc;
12493#else
12494 wxMemoryDC q_dc;
12495#endif
12496 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12497 q_dc.SelectObject(qbm);
12498
12499 // Get a copy of the screen
12500 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12501
12502 // Draw a rectangle over the screen with a stipple brush
12503 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12504 q_dc.SetBrush(qbr);
12505 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12506
12507 // Blit back into source
12508 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12509 wxCOPY);
12510
12511 q_dc.SelectObject(wxNullBitmap);
12512 }
12513#endif
12514
12515#if 0
12516 // It is possible that this two-step method may be reuired for some platforms.
12517 // So, retain in the code base to aid recovery if necessary
12518
12519 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12520 if( VPoint.b_quilt ) {
12521 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12522 ChartBase *chart = m_pQuilt->GetRefChart();
12523 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12524
12525 // Clear the text Global declutter list
12526 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12527 if(ChPI)
12528 ChPI->ClearPLIBTextList();
12529 else{
12530 if(ps52plib)
12531 ps52plib->ClearTextList();
12532 }
12533
12534 wxMemoryDC t_dc;
12535 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12536
12537 wxColor maskBackground = wxColour(1,0,0);
12538 t_dc.SelectObject( qbm );
12539 t_dc.SetBackground(wxBrush(maskBackground));
12540 t_dc.Clear();
12541
12542 // Copy the scratch DC into the new bitmap
12543 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12544
12545 // Render the text to the new bitmap
12546 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12547 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12548
12549 // Copy the new bitmap back to the scratch dc
12550 wxRegionIterator upd_final( ru );
12551 while( upd_final ) {
12552 wxRect rect = upd_final.GetRect();
12553 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12554 upd_final++;
12555 }
12556
12557 t_dc.SelectObject( wxNullBitmap );
12558 }
12559 }
12560 }
12561#endif
12562 // Direct rendering model...
12563 if (VPoint.b_quilt) {
12564 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12565 ChartBase *chart = m_pQuilt->GetRefChart();
12566 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12567 // Clear the text Global declutter list
12568 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12569 if (ChPI)
12570 ChPI->ClearPLIBTextList();
12571 else {
12572 if (ps52plib) ps52plib->ClearTextList();
12573 }
12574
12575 // Render the text directly to the scratch bitmap
12576 OCPNRegion chart_all_text_region(
12577 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12578
12579 if (g_bShowChartBar && m_Piano) {
12580 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12581 GetVP().pix_width, m_Piano->GetHeight());
12582
12583 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12584 if (!style->chartStatusWindowTransparent)
12585 chart_all_text_region.Subtract(chart_bar_rect);
12586 }
12587
12588 if (m_Compass && m_Compass->IsShown()) {
12589 wxRect compassRect = m_Compass->GetRect();
12590 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12591 chart_all_text_region.Subtract(compassRect);
12592 }
12593 }
12594
12595 mscratch_dc.DestroyClippingRegion();
12596
12597 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12598 chart_all_text_region);
12599 }
12600 }
12601 }
12602
12603 // Now that charts are fully rendered, apply the overlay objects as decals.
12604 ocpnDC scratch_dc(mscratch_dc);
12605 DrawOverlayObjects(scratch_dc, ru);
12606
12607 // And finally, blit the scratch dc onto the physical dc
12608 wxRegionIterator upd_final(rgn_blit);
12609 while (upd_final) {
12610 wxRect rect = upd_final.GetRect();
12611 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12612 rect.y);
12613 upd_final++;
12614 }
12615
12616 // Deselect the chart bitmap from the temp_dc, so that it will not be
12617 // destroyed in the temp_dc dtor
12618 temp_dc.SelectObject(wxNullBitmap);
12619 // And for the scratch bitmap
12620 mscratch_dc.SelectObject(wxNullBitmap);
12621
12622 dc.DestroyClippingRegion();
12623
12624 PaintCleanup();
12625}
12626
12627void ChartCanvas::PaintCleanup() {
12628 // Handle the current graphic window, if present
12629 if (m_inPinch) return;
12630
12631 if (pCwin) {
12632 pCwin->Show();
12633 if (m_bTCupdate) {
12634 pCwin->Refresh();
12635 pCwin->Update();
12636 }
12637 }
12638
12639 // And set flags for next time
12640 m_bTCupdate = false;
12641
12642 // Handle deferred WarpPointer
12643 if (warp_flag) {
12644 WarpPointer(warp_x, warp_y);
12645 warp_flag = false;
12646 }
12647
12648 // Start movement timers, this runs nearly immediately.
12649 // the reason we cannot simply call it directly is the
12650 // refresh events it emits may be blocked from this paint event
12651 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12652 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12653}
12654
12655#if 0
12656wxColour GetErrorGraphicColor(double val)
12657{
12658 /*
12659 double valm = wxMin(val_max, val);
12660
12661 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12662 unsigned char red = (unsigned char)(255 * (valm/val_max));
12663
12664 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12665
12666 hv.saturation = 1.0;
12667 hv.value = 1.0;
12668
12669 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12670 return wxColour(rv.red, rv.green, rv.blue);
12671 */
12672
12673 // HTML colors taken from NOAA WW3 Web representation
12674 wxColour c;
12675 if((val > 0) && (val < 1)) c.Set("#002ad9");
12676 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12677 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12678 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12679 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12680 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12681 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12682 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12683 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12684 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12685 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12686 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12687 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12688 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12689 else if((val >= 30) && (val < 36)) c.Set("#870000");
12690 else if((val >= 36) && (val < 42)) c.Set("#690000");
12691 else if((val >= 42) && (val < 48)) c.Set("#550000");
12692 else if( val >= 48) c.Set("#410000");
12693
12694 return c;
12695}
12696
12697void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12698{
12699 wxImage gr_image(vp->pix_width, vp->pix_height);
12700 gr_image.InitAlpha();
12701
12702 double maxval = -10000;
12703 double minval = 10000;
12704
12705 double rlat, rlon;
12706 double glat, glon;
12707
12708 GetCanvasPixPoint(0, 0, rlat, rlon);
12709
12710 for(int i=1; i < vp->pix_height-1; i++)
12711 {
12712 for(int j=0; j < vp->pix_width; j++)
12713 {
12714 // Reference mercator value
12715// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12716
12717 // Georef value
12718 GetCanvasPixPoint(j, i, glat, glon);
12719
12720 maxval = wxMax(maxval, (glat - rlat));
12721 minval = wxMin(minval, (glat - rlat));
12722
12723 }
12724 rlat = glat;
12725 }
12726
12727 GetCanvasPixPoint(0, 0, rlat, rlon);
12728 for(int i=1; i < vp->pix_height-1; i++)
12729 {
12730 for(int j=0; j < vp->pix_width; j++)
12731 {
12732 // Reference mercator value
12733// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12734
12735 // Georef value
12736 GetCanvasPixPoint(j, i, glat, glon);
12737
12738 double f = ((glat - rlat)-minval)/(maxval - minval);
12739
12740 double dy = (f * 40);
12741
12742 wxColour c = GetErrorGraphicColor(dy);
12743 unsigned char r = c.Red();
12744 unsigned char g = c.Green();
12745 unsigned char b = c.Blue();
12746
12747 gr_image.SetRGB(j, i, r,g,b);
12748 if((glat - rlat )!= 0)
12749 gr_image.SetAlpha(j, i, 128);
12750 else
12751 gr_image.SetAlpha(j, i, 255);
12752
12753 }
12754 rlat = glat;
12755 }
12756
12757 // Create a Bitmap
12758 wxBitmap *pbm = new wxBitmap(gr_image);
12759 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12760 pbm->SetMask(gr_mask);
12761
12762 pmdc->DrawBitmap(*pbm, 0,0);
12763
12764 delete pbm;
12765
12766}
12767
12768#endif
12769
12770void ChartCanvas::CancelMouseRoute() {
12771 m_routeState = 0;
12772 m_pMouseRoute = NULL;
12773 m_bDrawingRoute = false;
12774}
12775
12776int ChartCanvas::GetNextContextMenuId() {
12777 return CanvasMenuHandler::GetNextContextMenuId();
12778}
12779
12780bool ChartCanvas::SetCursor(const wxCursor &c) {
12781#ifdef ocpnUSE_GL
12782 if (g_bopengl && m_glcc)
12783 return m_glcc->SetCursor(c);
12784 else
12785#endif
12786 return wxWindow::SetCursor(c);
12787}
12788
12789void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12790 if (g_bquiting) return;
12791 // Keep the mouse position members up to date
12792 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12793
12794 // Retrigger the route leg popup timer
12795 // This handles the case when the chart is moving in auto-follow mode,
12796 // but no user mouse input is made. The timer handler may Hide() the
12797 // popup if the chart moved enough n.b. We use slightly longer oneshot
12798 // value to allow this method's Refresh() to complete before potentially
12799 // getting another Refresh() in the popup timer handler.
12800 if (!m_RolloverPopupTimer.IsRunning() &&
12801 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12802 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12803 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12804 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12805
12806#ifdef ocpnUSE_GL
12807 if (m_glcc && g_bopengl) {
12808 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12809 // overlay objects.
12810 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12811
12812 m_glcc->Refresh(eraseBackground,
12813 NULL); // We always are going to render the entire screen
12814 // anyway, so make
12815 // sure that the window managers understand the invalid area
12816 // is actually the entire client area.
12817
12818 // We need to selectively Refresh some child windows, if they are visible.
12819 // Note that some children are refreshed elsewhere on timer ticks, so don't
12820 // need attention here.
12821
12822 // Thumbnail chart
12823 if (pthumbwin && pthumbwin->IsShown()) {
12824 pthumbwin->Raise();
12825 pthumbwin->Refresh(false);
12826 }
12827
12828 // ChartInfo window
12829 if (m_pCIWin && m_pCIWin->IsShown()) {
12830 m_pCIWin->Raise();
12831 m_pCIWin->Refresh(false);
12832 }
12833
12834 // if(g_MainToolbar)
12835 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12836
12837 } else
12838#endif
12839 wxWindow::Refresh(eraseBackground, rect);
12840}
12841
12842void ChartCanvas::Update() {
12843 if (m_glcc && g_bopengl) {
12844#ifdef ocpnUSE_GL
12845 m_glcc->Update();
12846#endif
12847 } else
12848 wxWindow::Update();
12849}
12850
12851void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12852 if (!pemboss) return;
12853 int x = pemboss->x, y = pemboss->y;
12854 const double factor = 200;
12855
12856 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12857 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12858 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12859
12860 // Grab a snipped image out of the chart
12861 wxMemoryDC snip_dc;
12862 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12863 snip_dc.SelectObject(snip_bmp);
12864
12865 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12866 snip_dc.SelectObject(wxNullBitmap);
12867
12868 wxImage snip_img = snip_bmp.ConvertToImage();
12869
12870 // Apply Emboss map to the snip image
12871 unsigned char *pdata = snip_img.GetData();
12872 if (pdata) {
12873 for (int y = 0; y < pemboss->height; y++) {
12874 int map_index = (y * pemboss->width);
12875 for (int x = 0; x < pemboss->width; x++) {
12876 double val = (pemboss->pmap[map_index] * factor) / 256.;
12877
12878 int nred = (int)((*pdata) + val);
12879 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12880 *pdata++ = (unsigned char)nred;
12881
12882 int ngreen = (int)((*pdata) + val);
12883 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12884 *pdata++ = (unsigned char)ngreen;
12885
12886 int nblue = (int)((*pdata) + val);
12887 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12888 *pdata++ = (unsigned char)nblue;
12889
12890 map_index++;
12891 }
12892 }
12893 }
12894
12895 // Convert embossed snip to a bitmap
12896 wxBitmap emb_bmp(snip_img);
12897
12898 // Map to another memoryDC
12899 wxMemoryDC result_dc;
12900 result_dc.SelectObject(emb_bmp);
12901
12902 // Blit to target
12903 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12904
12905 result_dc.SelectObject(wxNullBitmap);
12906}
12907
12908emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12909 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12910
12911 if (GetQuiltMode()) {
12912 // disable Overzoom indicator for MBTiles
12913 int refIndex = GetQuiltRefChartdbIndex();
12914 if (refIndex >= 0) {
12915 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12916 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12917 if (current_type == CHART_TYPE_MBTILES) {
12918 ChartBase *pChart = m_pQuilt->GetRefChart();
12919 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12920 if (ptc) {
12921 zoom_factor = ptc->GetZoomFactor();
12922 }
12923 }
12924 }
12925
12926 if (zoom_factor <= 3.9) return NULL;
12927 } else {
12928 if (m_singleChart) {
12929 if (zoom_factor <= 3.9) return NULL;
12930 } else
12931 return NULL;
12932 }
12933
12934 if (m_pEM_OverZoom) {
12935 m_pEM_OverZoom->x = 4;
12936 m_pEM_OverZoom->y = 0;
12937 if (g_MainToolbar && IsPrimaryCanvas()) {
12938 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12939 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12940 }
12941 }
12942 return m_pEM_OverZoom;
12943}
12944
12945void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12946 GridDraw(dc);
12947
12948 // bool pluginOverlayRender = true;
12949 //
12950 // if(g_canvasConfig > 0){ // Multi canvas
12951 // if(IsPrimaryCanvas())
12952 // pluginOverlayRender = false;
12953 // }
12954
12955 g_overlayCanvas = this;
12956
12957 if (g_pi_manager) {
12958 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12959 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12961 }
12962
12963 AISDrawAreaNotices(dc, GetVP(), this);
12964
12965 wxDC *pdc = dc.GetDC();
12966 if (pdc) {
12967 pdc->DestroyClippingRegion();
12968 wxDCClipper(*pdc, ru);
12969 }
12970
12971 if (m_bShowNavobjects) {
12972 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12973 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12974 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12975 DrawAnchorWatchPoints(dc);
12976 } else {
12977 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12978 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12979 }
12980
12981 AISDraw(dc, GetVP(), this);
12982 ShipDraw(dc);
12983 AlertDraw(dc);
12984
12985 RenderVisibleSectorLights(dc);
12986
12987 RenderAllChartOutlines(dc, GetVP());
12988 RenderRouteLegs(dc);
12989 RenderShipToActive(dc, false);
12990 ScaleBarDraw(dc);
12991 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12992 if (g_pi_manager) {
12993 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12995 }
12996
12997 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12998 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12999
13000 if (g_pi_manager) {
13001 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
13003 }
13004
13005 if (m_bShowTide) {
13006 RebuildTideSelectList(GetVP().GetBBox());
13007 DrawAllTidesInBBox(dc, GetVP().GetBBox());
13008 }
13009
13010 if (m_bShowCurrent) {
13011 RebuildCurrentSelectList(GetVP().GetBBox());
13012 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
13013 }
13014
13015 if (!g_PrintingInProgress) {
13016 if (IsPrimaryCanvas()) {
13017 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
13018 }
13019
13020 if (IsPrimaryCanvas()) {
13021 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
13022 }
13023
13024 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
13025
13026 if (m_pTrackRolloverWin) {
13027 m_pTrackRolloverWin->Draw(dc);
13028 m_brepaint_piano = true;
13029 }
13030
13031 if (m_pRouteRolloverWin) {
13032 m_pRouteRolloverWin->Draw(dc);
13033 m_brepaint_piano = true;
13034 }
13035
13036 if (m_pAISRolloverWin) {
13037 m_pAISRolloverWin->Draw(dc);
13038 m_brepaint_piano = true;
13039 }
13040 if (m_brepaint_piano && g_bShowChartBar) {
13041 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
13042 }
13043
13044 if (m_Compass) m_Compass->Paint(dc);
13045
13046 if (!g_CanvasHideNotificationIcon) {
13047 if (IsPrimaryCanvas()) {
13048 auto &noteman = NotificationManager::GetInstance();
13049 if (m_notification_button) {
13050 if (noteman.GetNotificationCount()) {
13051 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
13052 if (m_notification_button->UpdateStatus()) Refresh();
13053 m_notification_button->Show(true);
13054 m_notification_button->Paint(dc);
13055 } else {
13056 m_notification_button->Show(false);
13057 }
13058 }
13059 }
13060 }
13061 }
13062 if (g_pi_manager) {
13063 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
13065 }
13066}
13067
13068emboss_data *ChartCanvas::EmbossDepthScale() {
13069 if (!m_bShowDepthUnits) return NULL;
13070
13071 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
13072
13073 if (GetQuiltMode()) {
13074 wxString s = m_pQuilt->GetQuiltDepthUnit();
13075 s.MakeUpper();
13076 if (s == "FEET")
13077 depth_unit_type = DEPTH_UNIT_FEET;
13078 else if (s.StartsWith("FATHOMS"))
13079 depth_unit_type = DEPTH_UNIT_FATHOMS;
13080 else if (s.StartsWith("METERS"))
13081 depth_unit_type = DEPTH_UNIT_METERS;
13082 else if (s.StartsWith("METRES"))
13083 depth_unit_type = DEPTH_UNIT_METERS;
13084 else if (s.StartsWith("METRIC"))
13085 depth_unit_type = DEPTH_UNIT_METERS;
13086 else if (s.StartsWith("METER"))
13087 depth_unit_type = DEPTH_UNIT_METERS;
13088
13089 } else {
13090 if (m_singleChart) {
13091 depth_unit_type = m_singleChart->GetDepthUnitType();
13092 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
13093 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
13094 }
13095 }
13096
13097 emboss_data *ped = NULL;
13098 switch (depth_unit_type) {
13099 case DEPTH_UNIT_FEET:
13100 ped = m_pEM_Feet;
13101 break;
13102 case DEPTH_UNIT_METERS:
13103 ped = m_pEM_Meters;
13104 break;
13105 case DEPTH_UNIT_FATHOMS:
13106 ped = m_pEM_Fathoms;
13107 break;
13108 default:
13109 return NULL;
13110 }
13111
13112 ped->x = (GetVP().pix_width - ped->width);
13113
13114 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
13115 wxRect r = m_Compass->GetRect();
13116 ped->y = r.y + r.height;
13117 } else {
13118 ped->y = 40;
13119 }
13120 return ped;
13121}
13122
13123void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
13124 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13125 wxFont font;
13126 if (style->embossFont == wxEmptyString) {
13127 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13128 font = *dFont;
13129 font.SetPointSize(60);
13130 font.SetWeight(wxFONTWEIGHT_BOLD);
13131 } else
13132 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13133 wxFONTWEIGHT_BOLD, false, style->embossFont);
13134
13135 int emboss_width = 500;
13136 int emboss_height = 200;
13137
13138 // Free any existing emboss maps
13139 delete m_pEM_Feet;
13140 delete m_pEM_Meters;
13141 delete m_pEM_Fathoms;
13142
13143 // Create the 3 DepthUnit emboss map structures
13144 m_pEM_Feet =
13145 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13146 m_pEM_Meters =
13147 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13148 m_pEM_Fathoms =
13149 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13150}
13151
13152#define OVERZOOM_TEXT _("OverZoom")
13153
13154void ChartCanvas::SetOverzoomFont() {
13155 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13156 int w, h;
13157
13158 wxFont font;
13159 if (style->embossFont == wxEmptyString) {
13160 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13161 font = *dFont;
13162 font.SetPointSize(40);
13163 font.SetWeight(wxFONTWEIGHT_BOLD);
13164 } else
13165 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13166 wxFONTWEIGHT_BOLD, false, style->embossFont);
13167
13168 wxClientDC dc(this);
13169 dc.SetFont(font);
13170 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13171
13172 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13173 font.SetPointSize(font.GetPointSize() - 1);
13174 dc.SetFont(font);
13175 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13176 }
13177 m_overzoomFont = font;
13178 m_overzoomTextWidth = w;
13179 m_overzoomTextHeight = h;
13180}
13181
13182void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13183 delete m_pEM_OverZoom;
13184
13185 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13186 m_pEM_OverZoom =
13187 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13188 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13189}
13190
13191emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13192 int height, const wxString &str,
13193 ColorScheme cs) {
13194 int *pmap;
13195
13196 // Create a temporary bitmap
13197 wxBitmap bmp(width, height, -1);
13198
13199 // Create a memory DC
13200 wxMemoryDC temp_dc;
13201 temp_dc.SelectObject(bmp);
13202
13203 // Paint on it
13204 temp_dc.SetBackground(*wxWHITE_BRUSH);
13205 temp_dc.SetTextBackground(*wxWHITE);
13206 temp_dc.SetTextForeground(*wxBLACK);
13207
13208 temp_dc.Clear();
13209
13210 temp_dc.SetFont(font);
13211
13212 int str_w, str_h;
13213 temp_dc.GetTextExtent(str, &str_w, &str_h);
13214 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13215 temp_dc.DrawText(str, 1, 1);
13216
13217 // Deselect the bitmap
13218 temp_dc.SelectObject(wxNullBitmap);
13219
13220 // Convert bitmap the wxImage for manipulation
13221 wxImage img = bmp.ConvertToImage();
13222
13223 int image_width = str_w * 105 / 100;
13224 int image_height = str_h * 105 / 100;
13225 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13226 wxMin(image_height, img.GetHeight()));
13227 wxImage imgs = img.GetSubImage(r);
13228
13229 double val_factor;
13230 switch (cs) {
13231 case GLOBAL_COLOR_SCHEME_DAY:
13232 case GLOBAL_COLOR_SCHEME_DAY_HICON:
13233 default:
13234 val_factor = 1;
13235 break;
13236 case GLOBAL_COLOR_SCHEME_DUSK:
13237 case GLOBAL_COLOR_SCHEME_NIGHT_HICON:
13238 val_factor = .5;
13239 break;
13240 case GLOBAL_COLOR_SCHEME_NIGHT:
13241 val_factor = .25;
13242 break;
13243 }
13244
13245 int val;
13246 int index;
13247 const int w = imgs.GetWidth();
13248 const int h = imgs.GetHeight();
13249 pmap = (int *)calloc(w * h * sizeof(int), 1);
13250 // Create emboss map by differentiating the emboss image
13251 // and storing integer results in pmap
13252 // n.b. since the image is B/W, it is sufficient to check
13253 // one channel (i.e. red) only
13254 for (int y = 1; y < h - 1; y++) {
13255 for (int x = 1; x < w - 1; x++) {
13256 val =
13257 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13258 val = (int)(val * val_factor);
13259 index = (y * w) + x;
13260 pmap[index] = val;
13261 }
13262 }
13263
13264 emboss_data *pret = new emboss_data;
13265 pret->pmap = pmap;
13266 pret->width = w;
13267 pret->height = h;
13268
13269 return pret;
13270}
13271
13272void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13273 Track *active_track = NULL;
13274 for (Track *pTrackDraw : g_TrackList) {
13275 if (g_pActiveTrack == pTrackDraw) {
13276 active_track = pTrackDraw;
13277 continue;
13278 }
13279
13280 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13281 }
13282
13283 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13284}
13285
13286void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13287 Track *active_track = NULL;
13288 for (Track *pTrackDraw : g_TrackList) {
13289 if (g_pActiveTrack == pTrackDraw) {
13290 active_track = pTrackDraw;
13291 break;
13292 }
13293 }
13294 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13295}
13296
13297void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13298 Route *active_route = NULL;
13299 for (Route *pRouteDraw : *pRouteList) {
13300 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13301 active_route = pRouteDraw;
13302 continue;
13303 }
13304
13305 // if(m_canvasIndex == 1)
13306 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13307 }
13308
13309 // Draw any active or selected route (or track) last, so that is is always on
13310 // top
13311 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13312}
13313
13314void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13315 Route *active_route = NULL;
13316
13317 for (Route *pRouteDraw : *pRouteList) {
13318 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13319 active_route = pRouteDraw;
13320 break;
13321 }
13322 }
13323 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13324}
13325
13326void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13327 if (!pWayPointMan) return;
13328
13329 auto node = pWayPointMan->GetWaypointList()->begin();
13330
13331 while (node != pWayPointMan->GetWaypointList()->end()) {
13332 RoutePoint *pWP = *node;
13333 if (pWP) {
13334 if (pWP->m_bIsInRoute) {
13335 ++node;
13336 continue;
13337 }
13338
13339 /* technically incorrect... waypoint has bounding box */
13340 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13341 RoutePointGui(*pWP).Draw(dc, this, NULL);
13342 else {
13343 // Are Range Rings enabled?
13344 if (pWP->GetShowWaypointRangeRings() &&
13345 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13346 double factor = 1.00;
13347 if (pWP->GetWaypointRangeRingsStepUnits() ==
13348 1) // convert kilometers to NMi
13349 factor = 1 / 1.852;
13350
13351 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13352 pWP->GetWaypointRangeRingsStep() / 60.;
13353 radius *= 2; // Fudge factor
13354
13355 LLBBox radar_box;
13356 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13357 pWP->m_lat + radius, pWP->m_lon + radius);
13358 if (!BltBBox.IntersectOut(radar_box)) {
13359 RoutePointGui(*pWP).Draw(dc, this, NULL);
13360 }
13361 }
13362 }
13363 }
13364
13365 ++node;
13366 }
13367}
13368
13369void ChartCanvas::DrawBlinkObjects() {
13370 // All RoutePoints
13371 wxRect update_rect;
13372
13373 if (!pWayPointMan) return;
13374
13375 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13376 if (pWP) {
13377 if (pWP->m_bBlink) {
13378 update_rect.Union(pWP->CurrentRect_in_DC);
13379 }
13380 }
13381 }
13382 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13383}
13384
13385void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13386 // draw anchor watch rings, if activated
13387
13389 wxPoint r1, r2;
13390 wxPoint lAnchorPoint1, lAnchorPoint2;
13391 double lpp1 = 0.0;
13392 double lpp2 = 0.0;
13393 if (pAnchorWatchPoint1) {
13394 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13396 &lAnchorPoint1);
13397 }
13398 if (pAnchorWatchPoint2) {
13399 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13401 &lAnchorPoint2);
13402 }
13403
13404 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13405 wxPen ppPenr(GetGlobalColor("URED"), 2);
13406
13407 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13408 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13409 dc.SetBrush(*ppBrush);
13410
13411 if (lpp1 > 0) {
13412 dc.SetPen(ppPeng);
13413 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13414 }
13415
13416 if (lpp2 > 0) {
13417 dc.SetPen(ppPeng);
13418 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13419 }
13420
13421 if (lpp1 < 0) {
13422 dc.SetPen(ppPenr);
13423 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13424 }
13425
13426 if (lpp2 < 0) {
13427 dc.SetPen(ppPenr);
13428 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13429 }
13430 }
13431}
13432
13433double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13434 double lpp = 0.;
13435 wxPoint r1;
13436 wxPoint lAnchorPoint;
13437 double d1 = 0.0;
13438 double dabs;
13439 double tlat1, tlon1;
13440
13441 if (pAnchorWatchPoint) {
13442 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13443 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13444 dabs = fabs(d1 / 1852.);
13445 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13446 &tlat1, &tlon1);
13447 GetCanvasPointPix(tlat1, tlon1, &r1);
13448 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13449 &lAnchorPoint);
13450 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13451 pow((double)(lAnchorPoint.y - r1.y), 2));
13452
13453 // This is an entry watch
13454 if (d1 < 0) lpp = -lpp;
13455 }
13456 return lpp;
13457}
13458
13459//------------------------------------------------------------------------------------------
13460// Tides Support
13461//------------------------------------------------------------------------------------------
13462void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13463 if (!ptcmgr) return;
13464 if (this != wxWindow::FindFocus()) return;
13465
13466 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13467
13468 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13469 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13470 double lon = pIDX->IDX_lon;
13471 double lat = pIDX->IDX_lat;
13472
13473 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13474 if ((type == 't') || (type == 'T')) {
13475 if (BBox.Contains(lat, lon)) {
13476 // Manage the point selection list
13477 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13478 }
13479 }
13480 }
13481}
13482
13483void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13484 if (!ptcmgr) return;
13485
13486 wxDateTime this_now = gTimeSource;
13487 bool cur_time = !gTimeSource.IsValid();
13488 if (cur_time) this_now = wxDateTime::Now();
13489 time_t t_this_now = this_now.GetTicks();
13490
13491 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13492 wxPENSTYLE_SOLID);
13493 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13494 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13495 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13496 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13497
13498 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13499 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13500 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13501 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13502 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13503 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13504
13505 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13506 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13507 int font_size = wxMax(10, dFont->GetPointSize());
13508 font_size /= g_Platform->GetDisplayDIPMult(this);
13509 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13510 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13511 false, dFont->GetFaceName());
13512
13513 dc.SetPen(*pblack_pen);
13514 dc.SetBrush(*pgreen_brush);
13515
13516 wxBitmap bm;
13517 switch (m_cs) {
13518 case GLOBAL_COLOR_SCHEME_DAY:
13519 case GLOBAL_COLOR_SCHEME_DAY_HICON:
13520 bm = m_bmTideDay;
13521 break;
13522 case GLOBAL_COLOR_SCHEME_DUSK:
13523 case GLOBAL_COLOR_SCHEME_NIGHT_HICON:
13524 bm = m_bmTideDusk;
13525 break;
13526 case GLOBAL_COLOR_SCHEME_NIGHT:
13527 bm = m_bmTideNight;
13528 break;
13529 default:
13530 bm = m_bmTideDay;
13531 break;
13532 }
13533
13534 int bmw = bm.GetWidth();
13535 int bmh = bm.GetHeight();
13536
13537 float scale_factor = 1.0;
13538
13539 // Set the onscreen size of the symbol
13540 // Compensate for various display resolutions
13541 float icon_pixelRefDim = 45;
13542
13543 // Tidal report graphic is scaled by the text size of the label in use
13544 wxScreenDC sdc;
13545 int height;
13546 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13547 height *= g_Platform->GetDisplayDIPMult(this);
13548 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13549
13550 scale_factor *= pix_factor;
13551
13552 float user_scale_factor = g_ChartScaleFactorExp;
13553 if (g_ChartScaleFactorExp > 1.0)
13554 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13555 1.2; // soften the scale factor a bit
13556
13557 scale_factor *= user_scale_factor;
13558 scale_factor *= GetContentScaleFactor();
13559
13560 {
13561 double marge = 0.05;
13562 std::vector<LLBBox> drawn_boxes;
13563 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13564 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13565
13566 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13567 if ((type == 't') || (type == 'T')) // only Tides
13568 {
13569 double lon = pIDX->IDX_lon;
13570 double lat = pIDX->IDX_lat;
13571
13572 if (BBox.ContainsMarge(lat, lon, marge)) {
13573 // Avoid drawing detailed graphic for duplicate tide stations
13574 if (GetVP().chart_scale < 500000) {
13575 bool bdrawn = false;
13576 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13577 if (drawn_boxes[i].Contains(lat, lon)) {
13578 bdrawn = true;
13579 break;
13580 }
13581 }
13582 if (bdrawn) continue; // the station loop
13583
13584 LLBBox this_box;
13585 this_box.Set(lat, lon, lat, lon);
13586 this_box.EnLarge(.005);
13587 drawn_boxes.push_back(this_box);
13588 }
13589
13590 wxPoint r;
13591 GetCanvasPointPix(lat, lon, &r);
13592 // draw standard icons
13593 if (GetVP().chart_scale > 500000) {
13594 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13595 }
13596 // draw "extended" icons
13597 else {
13598 dc.SetFont(*plabelFont);
13599 {
13600 {
13601 float val, nowlev;
13602 float ltleve = 0.;
13603 float htleve = 0.;
13604 time_t tctime;
13605 time_t lttime = 0;
13606 time_t httime = 0;
13607 bool wt;
13608 // define if flood or ebb in the last ten minutes and verify if
13609 // data are useable
13610 if (ptcmgr->GetTideFlowSens(
13611 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13612 pIDX->IDX_rec_num, nowlev, val, wt)) {
13613 // search forward the first HW or LW near "now" ( starting at
13614 // "now" - ten minutes )
13615 ptcmgr->GetHightOrLowTide(
13616 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13617 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13618 wt, pIDX->IDX_rec_num, val, tctime);
13619 if (wt) {
13620 httime = tctime;
13621 htleve = val;
13622 } else {
13623 lttime = tctime;
13624 ltleve = val;
13625 }
13626 wt = !wt;
13627
13628 // then search opposite tide near "now"
13629 if (tctime > t_this_now) // search backward
13630 ptcmgr->GetHightOrLowTide(
13631 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13632 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13633 pIDX->IDX_rec_num, val, tctime);
13634 else
13635 // or search forward
13636 ptcmgr->GetHightOrLowTide(
13637 t_this_now, FORWARD_TEN_MINUTES_STEP,
13638 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13639 val, tctime);
13640 if (wt) {
13641 httime = tctime;
13642 htleve = val;
13643 } else {
13644 lttime = tctime;
13645 ltleve = val;
13646 }
13647
13648 // draw the tide rectangle:
13649
13650 // tide icon rectangle has default pre-scaled width = 12 ,
13651 // height = 45
13652 int width = (int)(12 * scale_factor + 0.5);
13653 int height = (int)(45 * scale_factor + 0.5);
13654 int linew = wxMax(1, (int)(scale_factor));
13655 int xDraw = r.x - (width / 2);
13656 int yDraw = r.y - (height / 2);
13657
13658 // process tide state ( %height and flow sens )
13659 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13660 int hs = (httime > lttime) ? -4 : 4;
13661 hs *= (int)(scale_factor + 0.5);
13662 if (ts > 0.995 || ts < 0.005) hs = 0;
13663 int ht_y = (int)(height * ts);
13664
13665 // draw yellow tide rectangle outlined in black
13666 pblack_pen->SetWidth(linew);
13667 dc.SetPen(*pblack_pen);
13668 dc.SetBrush(*pyelo_brush);
13669 dc.DrawRectangle(xDraw, yDraw, width, height);
13670
13671 // draw blue rectangle as water height, smaller in width than
13672 // yellow rectangle
13673 dc.SetPen(*pblue_pen);
13674 dc.SetBrush(*pblue_brush);
13675 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13676 (width - (4 * linew)), height - ht_y);
13677
13678 // draw sens arrows (ensure they are not "under-drawn" by top
13679 // line of blue rectangle )
13680 int hl;
13681 wxPoint arrow[3];
13682 arrow[0].x = xDraw + 2 * linew;
13683 arrow[1].x = xDraw + width / 2;
13684 arrow[2].x = xDraw + width - 2 * linew;
13685 pyelo_pen->SetWidth(linew);
13686 pblue_pen->SetWidth(linew);
13687 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13688 {
13689 hl = (int)(height * 0.25) + yDraw;
13690 arrow[0].y = hl;
13691 arrow[1].y = hl + hs;
13692 arrow[2].y = hl;
13693 if (ts < 0.15)
13694 dc.SetPen(*pyelo_pen);
13695 else
13696 dc.SetPen(*pblue_pen);
13697 dc.DrawLines(3, arrow);
13698 }
13699 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13700 {
13701 hl = (int)(height * 0.5) + yDraw;
13702 arrow[0].y = hl;
13703 arrow[1].y = hl + hs;
13704 arrow[2].y = hl;
13705 if (ts < 0.40)
13706 dc.SetPen(*pyelo_pen);
13707 else
13708 dc.SetPen(*pblue_pen);
13709 dc.DrawLines(3, arrow);
13710 }
13711 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13712 {
13713 hl = (int)(height * 0.75) + yDraw;
13714 arrow[0].y = hl;
13715 arrow[1].y = hl + hs;
13716 arrow[2].y = hl;
13717 if (ts < 0.65)
13718 dc.SetPen(*pyelo_pen);
13719 else
13720 dc.SetPen(*pblue_pen);
13721 dc.DrawLines(3, arrow);
13722 }
13723 // Convert nowlev to preferred height units (it comes from
13724 // GetHightOrLowTide in station units)
13725 double nowlev_converted = nowlev;
13726 Station_Data *pmsd = pIDX->pref_sta_data;
13727 if (pmsd) {
13728 // Convert from station units to meters first
13729 int unit_c = TCDataFactory::findunit(pmsd->unit);
13730 if (unit_c >= 0) {
13731 nowlev_converted =
13732 nowlev_converted *
13733 TCDataFactory::known_units[unit_c].conv_factor;
13734 }
13735 // Now convert from meters to preferred height units
13736 nowlev_converted = toUsrHeight(nowlev_converted);
13737 }
13738 // draw tide level text
13739 wxString s;
13740 s.Printf("%3.1f", nowlev_converted);
13741 // Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13742 if (pmsd) s.Append(getUsrHeightUnit());
13743 int wx1;
13744 dc.GetTextExtent(s, &wx1, NULL);
13745 wx1 *= g_Platform->GetDisplayDIPMult(this);
13746 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13747 }
13748 }
13749 }
13750 }
13751 }
13752 }
13753 }
13754 }
13755}
13756
13757//------------------------------------------------------------------------------------------
13758// Currents Support
13759//------------------------------------------------------------------------------------------
13760
13761void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13762 if (!ptcmgr) return;
13763
13764 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13765
13766 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13767 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13768 double lon = pIDX->IDX_lon;
13769 double lat = pIDX->IDX_lat;
13770
13771 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13772 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13773 if ((BBox.Contains(lat, lon))) {
13774 // Manage the point selection list
13775 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13776 }
13777 }
13778 }
13779}
13780
13781void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13782 if (!ptcmgr) return;
13783
13784 float tcvalue, dir;
13785 bool bnew_val;
13786 char sbuf[20];
13787 wxFont *pTCFont;
13788 double lon_last = 0.;
13789 double lat_last = 0.;
13790 // arrow size for Raz Blanchard : 12 knots north
13791 double marge = 0.2;
13792 bool cur_time = !gTimeSource.IsValid();
13793
13794 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13795 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13796
13797 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13798 wxPENSTYLE_SOLID);
13799 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13800 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13801 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13802 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13803 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13804 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13805 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13806 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13807
13808 double skew_angle = GetVPRotation();
13809
13810 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13811 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13812 int font_size = wxMax(10, dFont->GetPointSize());
13813 font_size /= g_Platform->GetDisplayDIPMult(this);
13814 pTCFont = FontMgr::Get().FindOrCreateFont(
13815 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13816 false, dFont->GetFaceName());
13817
13818 float scale_factor = 1.0;
13819
13820 // Set the onscreen size of the symbol
13821 // Current report graphic is scaled by the text size of the label in use
13822 wxScreenDC sdc;
13823 int height;
13824 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13825 height *= g_Platform->GetDisplayDIPMult(this);
13826 float nominal_icon_size_pixels = 15;
13827 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13828
13829 scale_factor *= pix_factor;
13830
13831 float user_scale_factor = g_ChartScaleFactorExp;
13832 if (g_ChartScaleFactorExp > 1.0)
13833 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13834 1.2; // soften the scale factor a bit
13835
13836 scale_factor *= user_scale_factor;
13837
13838 scale_factor *= GetContentScaleFactor();
13839
13840 {
13841 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13842 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13843 double lon = pIDX->IDX_lon;
13844 double lat = pIDX->IDX_lat;
13845
13846 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13847 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13848 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13849 wxPoint r;
13850 GetCanvasPointPix(lat, lon, &r);
13851
13852 wxPoint d[4]; // points of a diamond at the current station location
13853 int dd = (int)(5.0 * scale_factor + 0.5);
13854 d[0].x = r.x;
13855 d[0].y = r.y + dd;
13856 d[1].x = r.x + dd;
13857 d[1].y = r.y;
13858 d[2].x = r.x;
13859 d[2].y = r.y - dd;
13860 d[3].x = r.x - dd;
13861 d[3].y = r.y;
13862
13863 if (1) {
13864 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13865 dc.SetPen(*pblack_pen);
13866 dc.SetBrush(*porange_brush);
13867 dc.DrawPolygon(4, d);
13868
13869 if (type == 'C') {
13870 dc.SetBrush(*pblack_brush);
13871 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13872 }
13873
13874 if (GetVP().chart_scale < 1000000) {
13875 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13876 continue;
13877 } else
13878 continue;
13879
13880 if (1 /*type == 'c'*/) {
13881 {
13882 // Get the display pixel location of the current station
13883 int pixxc, pixyc;
13884 pixxc = r.x;
13885 pixyc = r.y;
13886
13887 // Adjust drawing size using logarithmic scale. tcvalue is
13888 // current in knots
13889 double a1 = fabs(tcvalue) * 10.;
13890 // Current values <= 0.1 knot will have no arrow
13891 a1 = wxMax(1.0, a1);
13892 double a2 = log10(a1);
13893
13894 float cscale = scale_factor * a2 * 0.3;
13895
13896 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13897 dc.SetPen(*porange_pen);
13898 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13899 cscale);
13900 // Draw text, if enabled
13901
13902 if (bDrawCurrentValues) {
13903 dc.SetFont(*pTCFont);
13904 tcvalue = toUsrSpeed(tcvalue);
13905 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13906 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13907 }
13908 }
13909 } // scale
13910 }
13911 /* This is useful for debugging the TC database
13912 else
13913 {
13914 dc.SetPen ( *porange_pen );
13915 dc.SetBrush ( *pgray_brush );
13916 dc.DrawPolygon ( 4, d );
13917 }
13918 */
13919 }
13920 lon_last = lon;
13921 lat_last = lat;
13922 }
13923 }
13924 }
13925}
13926
13927void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13928 ShowSingleTideDialog(x, y, pvIDX);
13929}
13930
13931void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13932 if (!pvIDX) return; // Validate input
13933
13934 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13935
13936 // Check if a tide dialog is already open and visible
13937 if (pCwin && pCwin->IsShown()) {
13938 // Same tide station: bring existing dialog to front (preserves user
13939 // context)
13940 if (pCwin->GetCurrentIDX() == pNewIDX) {
13941 pCwin->Raise();
13942 pCwin->SetFocus();
13943
13944 // Provide subtle visual feedback that dialog is already open
13945 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13946 return;
13947 }
13948
13949 // Different tide station: close current dialog before opening new one
13950 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13951 }
13952
13953 if (pCwin) {
13954 // This shouldn't happen but ensures clean state
13955 pCwin->Destroy();
13956 pCwin = NULL;
13957 }
13958
13959 // Create and display new tide dialog
13960 pCwin = new TCWin(this, x, y, pvIDX);
13961
13962 // Ensure the dialog is properly shown and focused
13963 if (pCwin) {
13964 pCwin->Show();
13965 pCwin->Raise();
13966 pCwin->SetFocus();
13967 }
13968}
13969
13970bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13971
13973 if (pCwin) {
13974 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13975 }
13976}
13977
13978#define NUM_CURRENT_ARROW_POINTS 9
13979static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13980 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13981 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13982 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13983
13984void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13985 double scale) {
13986 if (scale > 1e-2) {
13987 float sin_rot = sin(rot_angle * PI / 180.);
13988 float cos_rot = cos(rot_angle * PI / 180.);
13989
13990 // Move to the first point
13991
13992 float xt = CurrentArrowArray[0].x;
13993 float yt = CurrentArrowArray[0].y;
13994
13995 float xp = (xt * cos_rot) - (yt * sin_rot);
13996 float yp = (xt * sin_rot) + (yt * cos_rot);
13997 int x1 = (int)(xp * scale);
13998 int y1 = (int)(yp * scale);
13999
14000 // Walk thru the point list
14001 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
14002 xt = CurrentArrowArray[ip].x;
14003 yt = CurrentArrowArray[ip].y;
14004
14005 float xp = (xt * cos_rot) - (yt * sin_rot);
14006 float yp = (xt * sin_rot) + (yt * cos_rot);
14007 int x2 = (int)(xp * scale);
14008 int y2 = (int)(yp * scale);
14009
14010 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
14011
14012 x1 = x2;
14013 y1 = y2;
14014 }
14015 }
14016}
14017
14018wxString ChartCanvas::FindValidUploadPort() {
14019 wxString port;
14020 // Try to use the saved persistent upload port first
14021 if (!g_uploadConnection.IsEmpty() &&
14022 g_uploadConnection.StartsWith("Serial")) {
14023 port = g_uploadConnection;
14024 }
14025
14026 else {
14027 // If there is no persistent upload port recorded (yet)
14028 // then use the first available serial connection which has output defined.
14029 for (auto *cp : TheConnectionParams()) {
14030 if ((cp->direction != PortDirection::kInput) && cp->type == SERIAL)
14031 port << "Serial:" << cp->serial_port;
14032 }
14033 }
14034 return port;
14035}
14036
14037void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
14038 if (!win) return;
14039
14040 if (NULL == g_pais_query_dialog_active) {
14041 int pos_x = g_ais_query_dialog_x;
14042 int pos_y = g_ais_query_dialog_y;
14043
14044 if (g_pais_query_dialog_active) {
14045 g_pais_query_dialog_active->Destroy();
14046 g_pais_query_dialog_active = new AISTargetQueryDialog();
14047 } else {
14048 g_pais_query_dialog_active = new AISTargetQueryDialog();
14049 }
14050
14051 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
14052 wxPoint(pos_x, pos_y));
14053
14054 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
14055 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
14056 g_pais_query_dialog_active->SetMMSI(mmsi);
14057 g_pais_query_dialog_active->UpdateText();
14058 wxSize sz = g_pais_query_dialog_active->GetSize();
14059
14060 bool b_reset_pos = false;
14061#ifdef __WXMSW__
14062 // Support MultiMonitor setups which an allow negative window positions.
14063 // If the requested window title bar does not intersect any installed
14064 // monitor, then default to simple primary monitor positioning.
14065 RECT frame_title_rect;
14066 frame_title_rect.left = pos_x;
14067 frame_title_rect.top = pos_y;
14068 frame_title_rect.right = pos_x + sz.x;
14069 frame_title_rect.bottom = pos_y + 30;
14070
14071 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
14072 b_reset_pos = true;
14073#else
14074
14075 // Make sure drag bar (title bar) of window intersects wxClient Area of
14076 // screen, with a little slop...
14077 wxRect window_title_rect; // conservative estimate
14078 window_title_rect.x = pos_x;
14079 window_title_rect.y = pos_y;
14080 window_title_rect.width = sz.x;
14081 window_title_rect.height = 30;
14082
14083 wxRect ClientRect = wxGetClientDisplayRect();
14084 ClientRect.Deflate(
14085 60, 60); // Prevent the new window from being too close to the edge
14086 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
14087
14088#endif
14089
14090 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
14091
14092 } else {
14093 g_pais_query_dialog_active->SetMMSI(mmsi);
14094 g_pais_query_dialog_active->UpdateText();
14095 }
14096
14097 g_pais_query_dialog_active->Show();
14098}
14099
14100void ChartCanvas::ToggleCanvasQuiltMode() {
14101 bool cur_mode = GetQuiltMode();
14102
14103 if (!GetQuiltMode())
14104 SetQuiltMode(true);
14105 else if (GetQuiltMode()) {
14106 SetQuiltMode(false);
14107 g_sticky_chart = GetQuiltReferenceChartIndex();
14108 }
14109
14110 if (cur_mode != GetQuiltMode()) {
14111 SetupCanvasQuiltMode();
14112 DoCanvasUpdate();
14113 InvalidateGL();
14114 Refresh();
14115 }
14116 // TODO What to do about this?
14117 // g_bQuiltEnable = GetQuiltMode();
14118
14119 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
14120 if (ps52plib) ps52plib->GenerateStateHash();
14121
14122 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14123 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14124}
14125
14126void ChartCanvas::DoCanvasStackDelta(int direction) {
14127 if (!GetQuiltMode()) {
14128 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
14129 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
14130 if ((current_stack_index + direction) < 0) return;
14131
14132 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
14133 int new_dbIndex =
14134 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
14135
14136 if (IsChartQuiltableRef(new_dbIndex)) {
14137 ToggleCanvasQuiltMode();
14138 SelectQuiltRefdbChart(new_dbIndex);
14139 m_bpersistent_quilt = false;
14140 }
14141 } else {
14142 SelectChartFromStack(current_stack_index + direction);
14143 }
14144 } else {
14145 std::vector<int> piano_chart_index_array =
14146 GetQuiltExtendedStackdbIndexArray();
14147 int refdb = GetQuiltRefChartdbIndex();
14148
14149 // Find the ref chart in the stack
14150 int current_index = -1;
14151 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14152 if (refdb == piano_chart_index_array[i]) {
14153 current_index = i;
14154 break;
14155 }
14156 }
14157 if (current_index == -1) return;
14158
14159 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14160 int target_family = ctet.GetChartFamily();
14161
14162 int new_index = -1;
14163 int check_index = current_index + direction;
14164 bool found = false;
14165 int check_dbIndex = -1;
14166 int new_dbIndex = -1;
14167
14168 // When quilted. switch within the same chart family
14169 while (!found &&
14170 (unsigned int)check_index < piano_chart_index_array.size() &&
14171 (check_index >= 0)) {
14172 check_dbIndex = piano_chart_index_array[check_index];
14173 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14174 if (target_family == cte.GetChartFamily()) {
14175 found = true;
14176 new_index = check_index;
14177 new_dbIndex = check_dbIndex;
14178 break;
14179 }
14180
14181 check_index += direction;
14182 }
14183
14184 if (!found) return;
14185
14186 if (!IsChartQuiltableRef(new_dbIndex)) {
14187 ToggleCanvasQuiltMode();
14188 SelectdbChart(new_dbIndex);
14189 m_bpersistent_quilt = true;
14190 } else {
14191 SelectQuiltRefChart(new_index);
14192 }
14193 }
14194
14195 // update the state of the menu items (checkmarks etc)
14196 top_frame::Get()->UpdateGlobalMenuItems();
14197 SetQuiltChartHiLiteIndex(-1);
14198
14199 ReloadVP();
14200}
14201
14202//--------------------------------------------------------------------------------------------------------
14203//
14204// Toolbar support
14205//
14206//--------------------------------------------------------------------------------------------------------
14207
14208void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14209 // Handle the per-canvas toolbar clicks here
14210
14211 switch (event.GetId()) {
14212 case ID_ZOOMIN: {
14213 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14214 break;
14215 }
14216
14217 case ID_ZOOMOUT: {
14218 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14219 break;
14220 }
14221
14222 case ID_STKUP:
14223 DoCanvasStackDelta(1);
14224 DoCanvasUpdate();
14225 break;
14226
14227 case ID_STKDN:
14228 DoCanvasStackDelta(-1);
14229 DoCanvasUpdate();
14230 break;
14231
14232 case ID_FOLLOW: {
14233 TogglebFollow();
14234 break;
14235 }
14236
14237 case ID_CURRENT: {
14238 ShowCurrents(!GetbShowCurrent());
14239 ReloadVP();
14240 Refresh(false);
14241 break;
14242 }
14243
14244 case ID_TIDE: {
14245 ShowTides(!GetbShowTide());
14246 ReloadVP();
14247 Refresh(false);
14248 break;
14249 }
14250
14251 case ID_ROUTE: {
14252 if (0 == m_routeState) {
14253 StartRoute();
14254 } else {
14255 FinishRoute();
14256 }
14257
14258#ifdef __ANDROID__
14259 androidSetRouteAnnunciator(m_routeState == 1);
14260#endif
14261 break;
14262 }
14263
14264 case ID_AIS: {
14265 SetAISCanvasDisplayStyle(-1);
14266 break;
14267 }
14268
14269 default:
14270 break;
14271 }
14272
14273 // And then let gFrame handle the rest....
14274 event.Skip();
14275}
14276
14277void ChartCanvas::SetShowAIS(bool show) {
14278 m_bShowAIS = show;
14279 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14280 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14281}
14282
14283void ChartCanvas::SetAttenAIS(bool show) {
14284 m_bShowAISScaled = show;
14285 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14286 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14287}
14288
14289void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14290 // make some arrays to hold the dfferences between cycle steps
14291 // show all, scaled, hide all
14292 bool bShowAIS_Array[3] = {true, true, false};
14293 bool bShowScaled_Array[3] = {false, true, true};
14294 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14295 _("Attenuate less critical AIS targets"),
14296 _("Hide AIS Targets")};
14297 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14298 int ArraySize = 3;
14299 int AIS_Toolbar_Switch = 0;
14300 if (StyleIndx == -1) { // -1 means coming from toolbar button
14301 // find current state of switch
14302 for (int i = 1; i < ArraySize; i++) {
14303 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14304 (bShowScaled_Array[i] == m_bShowAISScaled))
14305 AIS_Toolbar_Switch = i;
14306 }
14307 AIS_Toolbar_Switch++; // we did click so continu with next item
14308 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14309 AIS_Toolbar_Switch++;
14310
14311 } else { // coming from menu bar.
14312 AIS_Toolbar_Switch = StyleIndx;
14313 }
14314 // make sure we are not above array
14315 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14316
14317 int AIS_Toolbar_Switch_Next =
14318 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14319 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14320 AIS_Toolbar_Switch_Next++;
14321 if (AIS_Toolbar_Switch_Next >= ArraySize)
14322 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14323
14324 // Set found values to global and member variables
14325 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14326 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14327}
14328
14329void ChartCanvas::TouchAISToolActive() {}
14330
14331void ChartCanvas::UpdateAISTBTool() {}
14332
14333//---------------------------------------------------------------------------------
14334//
14335// Compass/GPS status icon support
14336//
14337//---------------------------------------------------------------------------------
14338
14339void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14340 // Look for change in overlap or positions
14341 bool b_update = false;
14342 int cc1_edge_comp = 2;
14343 wxRect rect = m_Compass->GetRect();
14344 wxSize parent_size = GetSize();
14345
14346 parent_size *= m_displayScale;
14347
14348 // check to see if it would overlap if it was in its home position (upper
14349 // right)
14350 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14351 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14352 wxRect compass_rect(compass_pt, rect.GetSize());
14353
14354 m_Compass->Move(compass_pt);
14355
14356 if (m_Compass && m_Compass->IsShown())
14357 m_Compass->UpdateStatus(b_force_new | b_update);
14358
14359 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14360 scaler = wxMax(scaler, 1.0);
14361 wxPoint note_point = wxPoint(
14362 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14363 if (m_notification_button) {
14364 m_notification_button->Move(note_point);
14365 m_notification_button->UpdateStatus();
14366 }
14367
14368 if (b_force_new | b_update) Refresh();
14369}
14370
14371void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14372 ChartTypeEnum New_Type,
14373 ChartFamilyEnum New_Family) {
14374 if (!GetpCurrentStack()) return;
14375 if (!ChartData) return;
14376
14377 if (index < GetpCurrentStack()->nEntry) {
14378 // Open the new chart
14379 ChartBase *pTentative_Chart;
14380 pTentative_Chart = ChartData->OpenStackChartConditional(
14381 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14382
14383 if (pTentative_Chart) {
14384 if (m_singleChart) m_singleChart->Deactivate();
14385
14386 m_singleChart = pTentative_Chart;
14387 m_singleChart->Activate();
14388
14389 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14390 GetpCurrentStack(), m_singleChart->GetFullPath());
14391 }
14392
14393 // Setup the view
14394 double zLat, zLon;
14395 if (m_bFollow) {
14396 zLat = gLat;
14397 zLon = gLon;
14398 } else {
14399 zLat = m_vLat;
14400 zLon = m_vLon;
14401 }
14402
14403 double best_scale_ppm = GetBestVPScale(m_singleChart);
14404 double rotation = GetVPRotation();
14405 double oldskew = GetVPSkew();
14406 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14407
14408 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14409 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14410 if (fabs(newskew) > 0.0001) rotation = newskew;
14411 }
14412
14413 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14414
14415 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14416 }
14417
14418 // refresh Piano
14419 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14420 if (idx < 0) return;
14421
14422 std::vector<int> piano_active_chart_index_array;
14423 piano_active_chart_index_array.push_back(
14424 GetpCurrentStack()->GetCurrentEntrydbIndex());
14425 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14426}
14427
14428void ChartCanvas::SelectdbChart(int dbindex) {
14429 if (!GetpCurrentStack()) return;
14430 if (!ChartData) return;
14431
14432 if (dbindex >= 0) {
14433 // Open the new chart
14434 ChartBase *pTentative_Chart;
14435 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14436
14437 if (pTentative_Chart) {
14438 if (m_singleChart) m_singleChart->Deactivate();
14439
14440 m_singleChart = pTentative_Chart;
14441 m_singleChart->Activate();
14442
14443 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14444 GetpCurrentStack(), m_singleChart->GetFullPath());
14445 }
14446
14447 // Setup the view
14448 double zLat, zLon;
14449 if (m_bFollow) {
14450 zLat = gLat;
14451 zLon = gLon;
14452 } else {
14453 zLat = m_vLat;
14454 zLon = m_vLon;
14455 }
14456
14457 double best_scale_ppm = GetBestVPScale(m_singleChart);
14458
14459 if (m_singleChart)
14460 SetViewPoint(zLat, zLon, best_scale_ppm,
14461 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14462
14463 // SetChartUpdatePeriod( );
14464
14465 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14466 }
14467
14468 // TODO refresh_Piano();
14469}
14470
14471void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14472 double target_scale = GetVP().view_scale_ppm;
14473
14474 if (!GetQuiltMode()) {
14475 if (GetpCurrentStack()) {
14476 int stack_index = -1;
14477 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14478 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14479 if (check_dbIndex < 0) continue;
14480 const ChartTableEntry &cte =
14481 ChartData->GetChartTableEntry(check_dbIndex);
14482 if (type == cte.GetChartType()) {
14483 stack_index = i;
14484 break;
14485 } else if (family == cte.GetChartFamily()) {
14486 stack_index = i;
14487 break;
14488 }
14489 }
14490
14491 if (stack_index >= 0) {
14492 SelectChartFromStack(stack_index);
14493 }
14494 }
14495 } else {
14496 int sel_dbIndex = -1;
14497 std::vector<int> piano_chart_index_array =
14498 GetQuiltExtendedStackdbIndexArray();
14499 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14500 int check_dbIndex = piano_chart_index_array[i];
14501 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14502 if (type == cte.GetChartType()) {
14503 if (IsChartQuiltableRef(check_dbIndex)) {
14504 sel_dbIndex = check_dbIndex;
14505 break;
14506 }
14507 } else if (family == cte.GetChartFamily()) {
14508 if (IsChartQuiltableRef(check_dbIndex)) {
14509 sel_dbIndex = check_dbIndex;
14510 break;
14511 }
14512 }
14513 }
14514
14515 if (sel_dbIndex >= 0) {
14516 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14517 // Re-qualify the quilt reference chart selection
14518 AdjustQuiltRefChart();
14519 }
14520
14521 // Now reset the scale to the target...
14522 SetVPScale(target_scale);
14523 }
14524
14525 SetQuiltChartHiLiteIndex(-1);
14526
14527 ReloadVP();
14528}
14529
14530bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14531 return std::find(m_tile_yesshow_index_array.begin(),
14532 m_tile_yesshow_index_array.end(),
14533 index) != m_tile_yesshow_index_array.end();
14534}
14535
14536bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14537 return std::find(m_tile_noshow_index_array.begin(),
14538 m_tile_noshow_index_array.end(),
14539 index) != m_tile_noshow_index_array.end();
14540}
14541
14542void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14543 if (std::find(m_tile_noshow_index_array.begin(),
14544 m_tile_noshow_index_array.end(),
14545 index) == m_tile_noshow_index_array.end()) {
14546 m_tile_noshow_index_array.push_back(index);
14547 }
14548}
14549
14550//-------------------------------------------------------------------------------------------------------
14551//
14552// Piano support
14553//
14554//-------------------------------------------------------------------------------------------------------
14555
14556void ChartCanvas::HandlePianoClick(
14557 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14558 if (g_options && g_options->IsShown())
14559 return; // Piano might be invalid due to chartset updates.
14560 if (!m_pCurrentStack) return;
14561 if (!ChartData) return;
14562
14563 // stop movement or on slow computer we may get something like :
14564 // zoom out with the wheel (timer is set)
14565 // quickly click and display a chart, which may zoom in
14566 // but the delayed timer fires first and it zooms out again!
14567 StopMovement();
14568
14569 // When switching by piano key click, we may appoint the new target chart to
14570 // be any chart in the composite array.
14571 // As an improvement to UX, find the chart that is "closest" to the current
14572 // vp,
14573 // and select that chart. This will cause a jump to the centroid of that
14574 // chart
14575
14576 double distance = 25000; // RTW
14577 int closest_index = -1;
14578 for (int chart_index : selected_dbIndex_array) {
14579 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14580 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14581 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14582
14583 // measure distance as Manhattan style
14584 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14585 if (test_distance < distance) {
14586 distance = test_distance;
14587 closest_index = chart_index;
14588 }
14589 }
14590
14591 int selected_dbIndex = selected_dbIndex_array[0];
14592 if (closest_index >= 0) selected_dbIndex = closest_index;
14593
14594 if (!GetQuiltMode()) {
14595 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14596 if (IsChartQuiltableRef(selected_dbIndex)) {
14597 ToggleCanvasQuiltMode();
14598 SelectQuiltRefdbChart(selected_dbIndex);
14599 m_bpersistent_quilt = false;
14600 } else {
14601 SelectChartFromStack(selected_index);
14602 }
14603 } else {
14604 SelectChartFromStack(selected_index);
14605 g_sticky_chart = selected_dbIndex;
14606 }
14607
14608 if (m_singleChart)
14609 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14610 } else {
14611 // Handle MBTiles overlays first
14612 // Left click simply toggles the noshow array index entry
14613 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14614 bool bfound = false;
14615 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14616 if (m_tile_noshow_index_array[i] ==
14617 selected_dbIndex) { // chart is in the noshow list
14618 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14619 i); // erase it
14620 bfound = true;
14621 break;
14622 }
14623 }
14624 if (!bfound) {
14625 m_tile_noshow_index_array.push_back(selected_dbIndex);
14626 }
14627
14628 // If not already present, add this tileset to the "yes_show" array.
14629 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14630 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14631 }
14632
14633 else {
14634 if (IsChartQuiltableRef(selected_dbIndex)) {
14635 // if( ChartData ) ChartData->PurgeCache();
14636
14637 // If the chart is a vector chart, and of very large scale,
14638 // then we had better set the new scale directly to avoid excessive
14639 // underzoom on, eg, Inland ENCs
14640 bool set_scale = false;
14641 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14642 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14643 set_scale = true;
14644 }
14645 }
14646
14647 if (!set_scale) {
14648 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14649 } else {
14650 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14651
14652 // Adjust scale so that the selected chart is underzoomed/overzoomed
14653 // by a controlled amount
14654 ChartBase *pc =
14655 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14656 if (pc) {
14657 double proposed_scale_onscreen =
14659
14660 if (g_bPreserveScaleOnX) {
14661 proposed_scale_onscreen =
14662 wxMin(proposed_scale_onscreen,
14663 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14664 GetCanvasWidth()));
14665 } else {
14666 proposed_scale_onscreen =
14667 wxMin(proposed_scale_onscreen,
14668 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14669 GetCanvasWidth()));
14670
14671 proposed_scale_onscreen =
14672 wxMax(proposed_scale_onscreen,
14673 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14675 }
14676
14677 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14678 }
14679 }
14680 } else {
14681 ToggleCanvasQuiltMode();
14682 SelectdbChart(selected_dbIndex);
14683 m_bpersistent_quilt = true;
14684 }
14685 }
14686 }
14687
14688 SetQuiltChartHiLiteIndex(-1);
14689 // update the state of the menu items (checkmarks etc)
14690 top_frame::Get()->UpdateGlobalMenuItems();
14691 HideChartInfoWindow();
14692 DoCanvasUpdate();
14693 ReloadVP(); // Pick up the new selections
14694}
14695
14696void ChartCanvas::HandlePianoRClick(
14697 int x, int y, int selected_index,
14698 const std::vector<int> &selected_dbIndex_array) {
14699 if (g_options && g_options->IsShown())
14700 return; // Piano might be invalid due to chartset updates.
14701 if (!GetpCurrentStack()) return;
14702
14703 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14704 UpdateCanvasControlBar();
14705
14706 SetQuiltChartHiLiteIndex(-1);
14707}
14708
14709void ChartCanvas::HandlePianoRollover(
14710 int selected_index, const std::vector<int> &selected_dbIndex_array,
14711 int n_charts, int scale) {
14712 if (g_options && g_options->IsShown())
14713 return; // Piano might be invalid due to chartset updates.
14714 if (!GetpCurrentStack()) return;
14715 if (!ChartData) return;
14716
14717 if (ChartData->IsBusy()) return;
14718
14719 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14720
14721 if (!GetQuiltMode()) {
14722 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14723 } else {
14724 // Select the correct vector
14725 std::vector<int> piano_chart_index_array;
14726 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14727 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14728 if ((GetpCurrentStack()->nEntry > 1) ||
14729 (piano_chart_index_array.size() >= 1)) {
14730 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14731
14732 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14733 ReloadVP(false); // no VP adjustment allowed
14734 } else if (GetpCurrentStack()->nEntry == 1) {
14735 const ChartTableEntry &cte =
14736 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14737 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14738 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14739 ReloadVP(false);
14740 } else if ((-1 == selected_index) &&
14741 (0 == selected_dbIndex_array.size())) {
14742 ShowChartInfoWindow(key_location.x, -1);
14743 }
14744 }
14745 } else {
14746 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14747
14748 if ((GetpCurrentStack()->nEntry > 1) ||
14749 (piano_chart_index_array.size() >= 1)) {
14750 if (n_charts > 1)
14751 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14752 selected_dbIndex_array);
14753 else if (n_charts == 1)
14754 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14755
14756 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14757 ReloadVP(false); // no VP adjustment allowed
14758 }
14759 }
14760 }
14761}
14762
14763void ChartCanvas::ClearPianoRollover() {
14764 ClearQuiltChartHiLiteIndexArray();
14765 ShowChartInfoWindow(0, -1);
14766 std::vector<int> vec;
14767 ShowCompositeInfoWindow(0, 0, 0, vec);
14768 ReloadVP(false);
14769}
14770
14771void ChartCanvas::UpdateCanvasControlBar() {
14772 if (m_pianoFrozen) return;
14773
14774 if (!GetpCurrentStack()) return;
14775 if (!ChartData) return;
14776 if (!g_bShowChartBar) return;
14777
14778 int sel_type = -1;
14779 int sel_family = -1;
14780
14781 std::vector<int> piano_chart_index_array;
14782 std::vector<int> empty_piano_chart_index_array;
14783
14784 wxString old_hash = m_Piano->GetStoredHash();
14785
14786 if (GetQuiltMode()) {
14787 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14788 GetQuiltFullScreendbIndexArray());
14789
14790 std::vector<int> piano_active_chart_index_array =
14791 GetQuiltCandidatedbIndexArray();
14792 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14793
14794 std::vector<int> piano_eclipsed_chart_index_array =
14795 GetQuiltEclipsedStackdbIndexArray();
14796 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14797
14798 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14799 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14800
14801 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14802 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14803 } else {
14804 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14805 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14806 // TODO refresh_Piano();
14807
14808 if (m_singleChart) {
14809 sel_type = m_singleChart->GetChartType();
14810 sel_family = m_singleChart->GetChartFamily();
14811 }
14812 }
14813
14814 // Set up the TMerc and Skew arrays
14815 std::vector<int> piano_skew_chart_index_array;
14816 std::vector<int> piano_tmerc_chart_index_array;
14817 std::vector<int> piano_poly_chart_index_array;
14818
14819 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14820 const ChartTableEntry &ctei =
14821 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14822 double skew_norm = ctei.GetChartSkew();
14823 if (skew_norm > 180.) skew_norm -= 360.;
14824
14825 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14826 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14827
14828 // Polyconic skewed charts should show as skewed
14829 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14830 if (fabs(skew_norm) > 1.)
14831 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14832 else
14833 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14834 } else if (fabs(skew_norm) > 1.)
14835 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14836 }
14837 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14838 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14839 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14840
14841 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14842 if (new_hash != old_hash) {
14843 m_Piano->FormatKeys();
14844 HideChartInfoWindow();
14845 m_Piano->ResetRollover();
14846 SetQuiltChartHiLiteIndex(-1);
14847 m_brepaint_piano = true;
14848 }
14849
14850 // Create a bitmask int that describes what Family/Type of charts are shown in
14851 // the bar, and notify the platform.
14852 int mask = 0;
14853 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14854 const ChartTableEntry &ctei =
14855 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14856 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14857 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14858 if (e == CHART_FAMILY_RASTER) mask |= 1;
14859 if (e == CHART_FAMILY_VECTOR) {
14860 if (t == CHART_TYPE_CM93COMP)
14861 mask |= 4;
14862 else
14863 mask |= 2;
14864 }
14865 }
14866
14867 wxString s_indicated;
14868 if (sel_type == CHART_TYPE_CM93COMP)
14869 s_indicated = "cm93";
14870 else {
14871 if (sel_family == CHART_FAMILY_RASTER)
14872 s_indicated = "raster";
14873 else if (sel_family == CHART_FAMILY_VECTOR)
14874 s_indicated = "vector";
14875 }
14876
14877 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14878}
14879
14880void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14881
14882void ChartCanvas::PianoPopupMenu(
14883 int x, int y, int selected_index,
14884 const std::vector<int> &selected_dbIndex_array) {
14885 if (!GetpCurrentStack()) return;
14886
14887 // No context menu if quilting is disabled
14888 if (!GetQuiltMode()) return;
14889
14890 m_piano_ctx_menu = new wxMenu();
14891
14892 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14893 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14894 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14895 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14896 } else {
14897 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14898 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14899 // wxEVT_COMMAND_MENU_SELECTED,
14900 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14901
14902 menu_selected_dbIndex = selected_dbIndex_array[0];
14903 menu_selected_index = selected_index;
14904
14905 // Search the no-show array
14906 bool b_is_in_noshow = false;
14907 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14908 if (m_quilt_noshow_index_array[i] ==
14909 menu_selected_dbIndex) // chart is in the noshow list
14910 {
14911 b_is_in_noshow = true;
14912 break;
14913 }
14914 }
14915
14916 if (b_is_in_noshow) {
14917 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14918 _("Show This Chart"));
14919 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14920 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14921 } else if (GetpCurrentStack()->nEntry > 1) {
14922 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14923 _("Hide This Chart"));
14924 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14925 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14926 }
14927 }
14928
14929 wxPoint pos = wxPoint(x, y - 30);
14930
14931 // Invoke the drop-down menu
14932 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14933 PopupMenu(m_piano_ctx_menu, pos);
14934
14935 delete m_piano_ctx_menu;
14936 m_piano_ctx_menu = NULL;
14937
14938 HideChartInfoWindow();
14939 m_Piano->ResetRollover();
14940
14941 SetQuiltChartHiLiteIndex(-1);
14942 ClearQuiltChartHiLiteIndexArray();
14943
14944 ReloadVP();
14945}
14946
14947void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14948 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14949 if (m_quilt_noshow_index_array[i] ==
14950 menu_selected_dbIndex) // chart is in the noshow list
14951 {
14952 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14953 break;
14954 }
14955 }
14956}
14957
14958void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14959 if (!GetpCurrentStack()) return;
14960 if (!ChartData) return;
14961
14962 RemoveChartFromQuilt(menu_selected_dbIndex);
14963
14964 // It could happen that the chart being disabled is the reference
14965 // chart....
14966 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14967 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14968
14969 int i = menu_selected_index + 1; // select next smaller scale chart
14970 bool b_success = false;
14971 while (i < GetpCurrentStack()->nEntry - 1) {
14972 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14973 if (type == ChartData->GetDBChartType(dbIndex)) {
14974 SelectQuiltRefChart(i);
14975 b_success = true;
14976 break;
14977 }
14978 i++;
14979 }
14980
14981 // If that did not work, try to select the next larger scale compatible
14982 // chart
14983 if (!b_success) {
14984 i = menu_selected_index - 1;
14985 while (i > 0) {
14986 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14987 if (type == ChartData->GetDBChartType(dbIndex)) {
14988 SelectQuiltRefChart(i);
14989 b_success = true;
14990 break;
14991 }
14992 i--;
14993 }
14994 }
14995 }
14996}
14997
14998void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14999 // Remove the item from the list (if it appears) to avoid multiple addition
15000 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
15001 if (m_quilt_noshow_index_array[i] ==
15002 dbIndex) // chart is already in the noshow list
15003 {
15004 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
15005 break;
15006 }
15007 }
15008
15009 m_quilt_noshow_index_array.push_back(dbIndex);
15010}
15011
15012bool ChartCanvas::UpdateS52State() {
15013 bool retval = false;
15014
15015 if (ps52plib) {
15016 ps52plib->SetShowS57Text(m_encShowText);
15017 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
15018 ps52plib->m_bShowSoundg = m_encShowDepth;
15019 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
15020 ps52plib->m_bShowLdisText = m_encShowLightDesc;
15021
15022 // Lights
15023 if (!m_encShowLights) // On, going off
15024 ps52plib->AddObjNoshow("LIGHTS");
15025 else // Off, going on
15026 ps52plib->RemoveObjNoshow("LIGHTS");
15027 ps52plib->SetLightsOff(!m_encShowLights);
15028 ps52plib->m_bExtendLightSectors = true;
15029
15030 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
15031 ps52plib->SetAnchorOn(m_encShowAnchor);
15032 ps52plib->SetQualityOfData(m_encShowDataQual);
15033 }
15034
15035 return retval;
15036}
15037
15038void ChartCanvas::SetShowENCDataQual(bool show) {
15039 m_encShowDataQual = show;
15040 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15041 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15042
15043 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15044}
15045
15046void ChartCanvas::SetShowENCText(bool show) {
15047 m_encShowText = show;
15048 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15049 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15050
15051 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15052}
15053
15054void ChartCanvas::SetENCDisplayCategory(int category) {
15055 m_encDisplayCategory = category;
15056 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15057}
15058
15059void ChartCanvas::SetShowENCDepth(bool show) {
15060 m_encShowDepth = show;
15061 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15062 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15063
15064 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15065}
15066
15067void ChartCanvas::SetShowENCLightDesc(bool show) {
15068 m_encShowLightDesc = show;
15069 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15070 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15071
15072 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15073}
15074
15075void ChartCanvas::SetShowENCBuoyLabels(bool show) {
15076 m_encShowBuoyLabels = show;
15077 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15078}
15079
15080void ChartCanvas::SetShowENCLights(bool show) {
15081 m_encShowLights = show;
15082 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15083 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15084
15085 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15086}
15087
15088void ChartCanvas::SetShowENCAnchor(bool show) {
15089 m_encShowAnchor = show;
15090 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
15091 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
15092
15093 m_s52StateHash = 0; // Force a S52 PLIB re-configure
15094}
15095
15096wxRect ChartCanvas::GetMUIBarRect() {
15097 wxRect rv;
15098 if (m_muiBar) {
15099 rv = m_muiBar->GetRect();
15100 }
15101
15102 return rv;
15103}
15104
15105void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
15106 if (!GetAlertString().IsEmpty()) {
15107 wxFont *pfont = wxTheFontList->FindOrCreateFont(
15108 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
15109
15110 dc.SetFont(*pfont);
15111 dc.SetPen(*wxTRANSPARENT_PEN);
15112
15113 dc.SetBrush(wxColour(243, 229, 47));
15114 int w, h;
15115 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
15116 h += 2;
15117 // int yp = vp.pix_height - 20 - h;
15118
15119 wxRect sbr = GetScaleBarRect();
15120 int xp = sbr.x + sbr.width + 10;
15121 int yp = (sbr.y + sbr.height) - h;
15122
15123 int wdraw = w + 10;
15124 dc.DrawRectangle(xp, yp, wdraw, h);
15125 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
15126 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
15127 }
15128}
15129
15130//--------------------------------------------------------------------------------------------------------
15131// Screen Brightness Control Support Routines
15132//
15133//--------------------------------------------------------------------------------------------------------
15134
15135#ifdef __UNIX__
15136#define BRIGHT_XCALIB
15137#define __OPCPN_USEICC__
15138#endif
15139
15140#ifdef __OPCPN_USEICC__
15141int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15142 double co_green, double co_blue);
15143
15144wxString temp_file_name;
15145#endif
15146
15147#if 0
15148class ocpnCurtain: public wxDialog
15149{
15150 DECLARE_CLASS( ocpnCurtain )
15151 DECLARE_EVENT_TABLE()
15152
15153public:
15154 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15155 ~ocpnCurtain( );
15156 bool ProcessEvent(wxEvent& event);
15157
15158};
15159
15160IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15161
15162BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15163END_EVENT_TABLE()
15164
15165ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15166{
15167 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15168}
15169
15170ocpnCurtain::~ocpnCurtain()
15171{
15172}
15173
15174bool ocpnCurtain::ProcessEvent(wxEvent& event)
15175{
15176 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15177 return GetParent()->GetEventHandler()->ProcessEvent(event);
15178}
15179#endif
15180
15181#ifdef _WIN32
15182#include <windows.h>
15183
15184HMODULE hGDI32DLL;
15185typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15186typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15187SetDeviceGammaRamp_ptr_type
15188 g_pSetDeviceGammaRamp; // the API entry points in the dll
15189GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15190
15191WORD *g_pSavedGammaMap;
15192
15193#endif
15194
15195int InitScreenBrightness() {
15196#ifdef _WIN32
15197#ifdef ocpnUSE_GL
15198 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15199 HDC hDC;
15200 BOOL bbr;
15201
15202 if (NULL == hGDI32DLL) {
15203 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15204
15205 if (NULL != hGDI32DLL) {
15206 // Get the entry points of the required functions
15207 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15208 hGDI32DLL, "SetDeviceGammaRamp");
15209 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15210 hGDI32DLL, "GetDeviceGammaRamp");
15211
15212 // If the functions are not found, unload the DLL and return false
15213 if ((NULL == g_pSetDeviceGammaRamp) ||
15214 (NULL == g_pGetDeviceGammaRamp)) {
15215 FreeLibrary(hGDI32DLL);
15216 hGDI32DLL = NULL;
15217 return 0;
15218 }
15219 }
15220 }
15221
15222 // Interface is ready, so....
15223 // Get some storage
15224 if (!g_pSavedGammaMap) {
15225 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15226
15227 hDC = GetDC(NULL); // Get the full screen DC
15228 bbr = g_pGetDeviceGammaRamp(
15229 hDC, g_pSavedGammaMap); // Get the existing ramp table
15230 ReleaseDC(NULL, hDC); // Release the DC
15231 }
15232
15233 // On Windows hosts, try to adjust the registry to allow full range
15234 // setting of Gamma table This is an undocumented Windows hack.....
15235 wxRegKey *pRegKey = new wxRegKey(
15236 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15237 "NT\\CurrentVersion\\ICM");
15238 if (!pRegKey->Exists()) pRegKey->Create();
15239 pRegKey->SetValue("GdiIcmGammaRange", 256);
15240
15241 g_brightness_init = true;
15242 return 1;
15243 }
15244#endif
15245
15246 {
15247 if (NULL == g_pcurtain) {
15248 if (top_frame::Get()->CanSetTransparent()) {
15249 // Build the curtain window
15250 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15251 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15252 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15253 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15254
15255 // g_pcurtain = new ocpnCurtain(gFrame,
15256 // wxPoint(0,0),::wxGetDisplaySize(),
15257 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15258 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15259
15260 g_pcurtain->Hide();
15261
15262 HWND hWnd = GetHwndOf(g_pcurtain);
15263 SetWindowLong(hWnd, GWL_EXSTYLE,
15264 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15265 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15266 g_pcurtain->SetTransparent(0);
15267
15268 g_pcurtain->Maximize();
15269 g_pcurtain->Show();
15270
15271 // All of this is obtuse, but necessary for Windows...
15272 g_pcurtain->Enable();
15273 g_pcurtain->Disable();
15274
15275 top_frame::Get()->Disable();
15276 top_frame::Get()->Enable();
15277 // SetFocus();
15278 }
15279 }
15280 g_brightness_init = true;
15281
15282 return 1;
15283 }
15284#else
15285 // Look for "xcalib" application
15286 wxString cmd("xcalib -version");
15287
15288 wxArrayString output;
15289 long r = wxExecute(cmd, output);
15290 if (0 != r)
15291 wxLogMessage(
15292 " External application \"xcalib\" not found. Screen brightness "
15293 "not changed.");
15294
15295 g_brightness_init = true;
15296 return 0;
15297#endif
15298}
15299
15300int RestoreScreenBrightness() {
15301#ifdef _WIN32
15302
15303 if (g_pSavedGammaMap) {
15304 HDC hDC = GetDC(NULL); // Get the full screen DC
15305 g_pSetDeviceGammaRamp(hDC,
15306 g_pSavedGammaMap); // Restore the saved ramp table
15307 ReleaseDC(NULL, hDC); // Release the DC
15308
15309 free(g_pSavedGammaMap);
15310 g_pSavedGammaMap = NULL;
15311 }
15312
15313 if (g_pcurtain) {
15314 g_pcurtain->Close();
15315 g_pcurtain->Destroy();
15316 g_pcurtain = NULL;
15317 }
15318
15319 g_brightness_init = false;
15320 return 1;
15321
15322#endif
15323
15324#ifdef BRIGHT_XCALIB
15325 if (g_brightness_init) {
15326 wxString cmd;
15327 cmd = "xcalib -clear";
15328 wxExecute(cmd, wxEXEC_ASYNC);
15329 g_brightness_init = false;
15330 }
15331
15332 return 1;
15333#endif
15334
15335 return 0;
15336}
15337
15338// Set brightness. [0..100]
15339int SetScreenBrightness(int brightness) {
15340#ifdef _WIN32
15341
15342 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15343 // some (most modern?) versions of gdi32.dll Load the required library dll,
15344 // if not already in place
15345#ifdef ocpnUSE_GL
15346 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15347 if (g_pcurtain) {
15348 g_pcurtain->Close();
15349 g_pcurtain->Destroy();
15350 g_pcurtain = NULL;
15351 }
15352
15353 InitScreenBrightness();
15354
15355 if (NULL == hGDI32DLL) {
15356 // Unicode stuff.....
15357 wchar_t wdll_name[80];
15358 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15359 LPCWSTR cstr = wdll_name;
15360
15361 hGDI32DLL = LoadLibrary(cstr);
15362
15363 if (NULL != hGDI32DLL) {
15364 // Get the entry points of the required functions
15365 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15366 hGDI32DLL, "SetDeviceGammaRamp");
15367 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15368 hGDI32DLL, "GetDeviceGammaRamp");
15369
15370 // If the functions are not found, unload the DLL and return false
15371 if ((NULL == g_pSetDeviceGammaRamp) ||
15372 (NULL == g_pGetDeviceGammaRamp)) {
15373 FreeLibrary(hGDI32DLL);
15374 hGDI32DLL = NULL;
15375 return 0;
15376 }
15377 }
15378 }
15379
15380 HDC hDC = GetDC(NULL); // Get the full screen DC
15381
15382 /*
15383 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15384 if (cmcap != CM_GAMMA_RAMP)
15385 {
15386 wxLogMessage(" Video hardware does not support brightness control by
15387 gamma ramp adjustment."); return false;
15388 }
15389 */
15390
15391 int increment = brightness * 256 / 100;
15392
15393 // Build the Gamma Ramp table
15394 WORD GammaTable[3][256];
15395
15396 int table_val = 0;
15397 for (int i = 0; i < 256; i++) {
15398 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15399 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15400 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15401
15402 table_val += increment;
15403
15404 if (table_val > 65535) table_val = 65535;
15405 }
15406
15407 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15408 ReleaseDC(NULL, hDC); // Release the DC
15409
15410 return 1;
15411 }
15412#endif
15413
15414 {
15415 if (g_pSavedGammaMap) {
15416 HDC hDC = GetDC(NULL); // Get the full screen DC
15417 g_pSetDeviceGammaRamp(hDC,
15418 g_pSavedGammaMap); // Restore the saved ramp table
15419 ReleaseDC(NULL, hDC); // Release the DC
15420 }
15421
15422 if (brightness < 100) {
15423 if (NULL == g_pcurtain) InitScreenBrightness();
15424
15425 if (g_pcurtain) {
15426 int sbrite = wxMax(1, brightness);
15427 sbrite = wxMin(100, sbrite);
15428
15429 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15430 }
15431 } else {
15432 if (g_pcurtain) {
15433 g_pcurtain->Close();
15434 g_pcurtain->Destroy();
15435 g_pcurtain = NULL;
15436 }
15437 }
15438
15439 return 1;
15440 }
15441
15442#endif
15443
15444#ifdef BRIGHT_XCALIB
15445
15446 if (!g_brightness_init) {
15447 last_brightness = 100;
15448 g_brightness_init = true;
15449 temp_file_name = wxFileName::CreateTempFileName("");
15450 InitScreenBrightness();
15451 }
15452
15453#ifdef __OPCPN_USEICC__
15454 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15455 // desired, and then activate this temporary profile using xcalib <filename>
15456 if (!CreateSimpleICCProfileFile(
15457 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15458 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15459 wxString cmd("xcalib ");
15460 cmd += temp_file_name;
15461
15462 wxExecute(cmd, wxEXEC_ASYNC);
15463 }
15464
15465#else
15466 // Or, use "xcalib -co" to set overall contrast value
15467 // This is not as nice, since the -co parameter wants to be a fraction of
15468 // the current contrast, and values greater than 100 are not allowed. As a
15469 // result, increases of contrast must do a "-clear" step first, which
15470 // produces objectionable flashing.
15471 if (brightness > last_brightness) {
15472 wxString cmd;
15473 cmd = "xcalib -clear";
15474 wxExecute(cmd, wxEXEC_ASYNC);
15475
15476 ::wxMilliSleep(10);
15477
15478 int brite_adj = wxMax(1, brightness);
15479 cmd.Printf("xcalib -co %2d -a", brite_adj);
15480 wxExecute(cmd, wxEXEC_ASYNC);
15481 } else {
15482 int brite_adj = wxMax(1, brightness);
15483 int factor = (brite_adj * 100) / last_brightness;
15484 factor = wxMax(1, factor);
15485 wxString cmd;
15486 cmd.Printf("xcalib -co %2d -a", factor);
15487 wxExecute(cmd, wxEXEC_ASYNC);
15488 }
15489
15490#endif
15491
15492 last_brightness = brightness;
15493
15494#endif
15495
15496 return 0;
15497}
15498
15499#ifdef __OPCPN_USEICC__
15500
15501#define MLUT_TAG 0x6d4c5554L
15502#define VCGT_TAG 0x76636774L
15503
15504int GetIntEndian(unsigned char *s) {
15505 int ret;
15506 unsigned char *p;
15507 int i;
15508
15509 p = (unsigned char *)&ret;
15510
15511 if (1)
15512 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15513 else
15514 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15515
15516 return ret;
15517}
15518
15519unsigned short GetShortEndian(unsigned char *s) {
15520 unsigned short ret;
15521 unsigned char *p;
15522 int i;
15523
15524 p = (unsigned char *)&ret;
15525
15526 if (1)
15527 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15528 else
15529 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15530
15531 return ret;
15532}
15533
15534// Create a very simple Gamma correction file readable by xcalib
15535int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15536 double co_green, double co_blue) {
15537 FILE *fp;
15538
15539 if (file_name) {
15540 fp = fopen(file_name, "wb");
15541 if (!fp) return -1; /* file can not be created */
15542 } else
15543 return -1; /* filename char pointer not valid */
15544
15545 // Write header
15546 char header[128];
15547 for (int i = 0; i < 128; i++) header[i] = 0;
15548
15549 fwrite(header, 128, 1, fp);
15550
15551 // Num tags
15552 int numTags0 = 1;
15553 int numTags = GetIntEndian((unsigned char *)&numTags0);
15554 fwrite(&numTags, 1, 4, fp);
15555
15556 int tagName0 = VCGT_TAG;
15557 int tagName = GetIntEndian((unsigned char *)&tagName0);
15558 fwrite(&tagName, 1, 4, fp);
15559
15560 int tagOffset0 = 128 + 4 * sizeof(int);
15561 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15562 fwrite(&tagOffset, 1, 4, fp);
15563
15564 int tagSize0 = 1;
15565 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15566 fwrite(&tagSize, 1, 4, fp);
15567
15568 fwrite(&tagName, 1, 4, fp); // another copy of tag
15569
15570 fwrite(&tagName, 1, 4, fp); // dummy
15571
15572 // Table type
15573
15574 /* VideoCardGammaTable (The simplest type) */
15575 int gammatype0 = 0;
15576 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15577 fwrite(&gammatype, 1, 4, fp);
15578
15579 int numChannels0 = 3;
15580 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15581 fwrite(&numChannels, 1, 2, fp);
15582
15583 int numEntries0 = 256;
15584 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15585 fwrite(&numEntries, 1, 2, fp);
15586
15587 int entrySize0 = 1;
15588 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15589 fwrite(&entrySize, 1, 2, fp);
15590
15591 unsigned char ramp[256];
15592
15593 // Red ramp
15594 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15595 fwrite(ramp, 256, 1, fp);
15596
15597 // Green ramp
15598 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15599 fwrite(ramp, 256, 1, fp);
15600
15601 // Blue ramp
15602 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15603 fwrite(ramp, 256, 1, fp);
15604
15605 fclose(fp);
15606
15607 return 0;
15608}
15609#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:36
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:61
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1316
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1315
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1316
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1315
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:174
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13972
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:4611
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11993
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4607
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:13931
obs::EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:899
void GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision,...
Definition chcanv.cpp:4557
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:808
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:512
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:543
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2360
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8098
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7904
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5139
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4688
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5417
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:184
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:792
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4632
bool IsTideDialogOpen() const
Definition chcanv.cpp:13970
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:4694
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13927
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4552
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5436
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10404
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:500
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Locked down interface published in 5.14.0.
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:202
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:1792
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:359
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:777
Dialog for displaying query results of S57 objects.
Definition tc_win.h:46
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:77
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:139
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:145
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:36
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
void Notify() override
Notify all listeners, no data supplied.
Definition evtvar.h:83
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
std::vector< ConnectionParams * > & TheConnectionParams()
Return global list of connections.
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:77
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
wxString getUsrHeightUnit(int unit)
Get the abbreviation for the preferred height unit.
double toUsrHeight(double m_height, int unit)
Convert height from meters to preferred height units.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Miscellaneous utilities, many of which string related.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:190
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:100
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:97
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.