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 m_ignore_next_leftup = false;
480
481 m_canvas_scale_factor = 1.;
482
483 m_canvas_width = 1000;
484
485 m_overzoomTextWidth = 0;
486 m_overzoomTextHeight = 0;
487
488 // Create the default world chart
489 pWorldBackgroundChart = new GSHHSChart;
490 gShapeBasemap.Reset();
491
492 // Create the default depth unit emboss maps
493 m_pEM_Feet = NULL;
494 m_pEM_Meters = NULL;
495 m_pEM_Fathoms = NULL;
496
497 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
498
499 m_pEM_OverZoom = NULL;
500 SetOverzoomFont();
501 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
502
503 // Build icons for tide/current points
504 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
505 m_bmTideDay =
506 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
507
508 // Dusk
509 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
510
511 // Night
512 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
513
514 // Build Dusk/Night ownship icons
515 double factor_dusk = 0.5;
516 double factor_night = 0.25;
517
518 // Red
519 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
520
521 int rimg_width = m_os_image_red_day.GetWidth();
522 int rimg_height = m_os_image_red_day.GetHeight();
523
524 m_os_image_red_dusk = m_os_image_red_day.Copy();
525 m_os_image_red_night = m_os_image_red_day.Copy();
526
527 for (int iy = 0; iy < rimg_height; iy++) {
528 for (int ix = 0; ix < rimg_width; ix++) {
529 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
530 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
531 m_os_image_red_day.GetGreen(ix, iy),
532 m_os_image_red_day.GetBlue(ix, iy));
533 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
534 hsv.value = hsv.value * factor_dusk;
535 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
536 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
537
538 hsv = wxImage::RGBtoHSV(rgb);
539 hsv.value = hsv.value * factor_night;
540 nrgb = wxImage::HSVtoRGB(hsv);
541 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
542 }
543 }
544 }
545
546 // Grey
547 m_os_image_grey_day =
548 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
549
550 int gimg_width = m_os_image_grey_day.GetWidth();
551 int gimg_height = m_os_image_grey_day.GetHeight();
552
553 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
554 m_os_image_grey_night = m_os_image_grey_day.Copy();
555
556 for (int iy = 0; iy < gimg_height; iy++) {
557 for (int ix = 0; ix < gimg_width; ix++) {
558 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
559 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
560 m_os_image_grey_day.GetGreen(ix, iy),
561 m_os_image_grey_day.GetBlue(ix, iy));
562 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
563 hsv.value = hsv.value * factor_dusk;
564 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
565 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
566
567 hsv = wxImage::RGBtoHSV(rgb);
568 hsv.value = hsv.value * factor_night;
569 nrgb = wxImage::HSVtoRGB(hsv);
570 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
571 }
572 }
573 }
574
575 // Yellow
576 m_os_image_yellow_day = m_os_image_red_day.Copy();
577
578 gimg_width = m_os_image_yellow_day.GetWidth();
579 gimg_height = m_os_image_yellow_day.GetHeight();
580
581 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
582 m_os_image_yellow_night = m_os_image_red_day.Copy();
583
584 for (int iy = 0; iy < gimg_height; iy++) {
585 for (int ix = 0; ix < gimg_width; ix++) {
586 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
587 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
588 m_os_image_yellow_day.GetGreen(ix, iy),
589 m_os_image_yellow_day.GetBlue(ix, iy));
590 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
591 hsv.hue += 60. / 360.; // shift to yellow
592 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
593 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
594
595 hsv = wxImage::RGBtoHSV(rgb);
596 hsv.value = hsv.value * factor_dusk;
597 hsv.hue += 60. / 360.; // shift to yellow
598 nrgb = wxImage::HSVtoRGB(hsv);
599 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
600
601 hsv = wxImage::RGBtoHSV(rgb);
602 hsv.hue += 60. / 360.; // shift to yellow
603 hsv.value = hsv.value * factor_night;
604 nrgb = wxImage::HSVtoRGB(hsv);
605 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
606 }
607 }
608 }
609
610 // Set initial pointers to ownship images
611 m_pos_image_red = &m_os_image_red_day;
612 m_pos_image_yellow = &m_os_image_yellow_day;
613 m_pos_image_grey = &m_os_image_grey_day;
614
615 SetUserOwnship();
616
617 m_pBrightPopup = NULL;
618
619#ifdef ocpnUSE_GL
620 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
621#endif
622
623 SetupGridFont();
624
625 m_Piano = new Piano(this);
626
627 m_bShowCompassWin = true;
628 m_Compass = new ocpnCompass(this);
629 m_Compass->SetScaleFactor(g_compass_scalefactor);
630 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
631
632 if (IsPrimaryCanvas()) {
633 m_notification_button = new NotificationButton(this);
634 m_notification_button->SetScaleFactor(g_compass_scalefactor);
635 m_notification_button->Show(true);
636 }
637
638 m_pianoFrozen = false;
639
640 SetMinSize(wxSize(200, 200));
641
642 m_displayScale = 1.0;
643#if defined(__WXOSX__) || defined(__WXGTK3__)
644 // Support scaled HDPI displays.
645 m_displayScale = GetContentScaleFactor();
646#endif
647 VPoint.SetPixelScale(m_displayScale);
648
649#ifdef HAVE_WX_GESTURE_EVENTS
650 // if (!m_glcc)
651 {
652 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
653 wxLogError("Failed to enable touch events");
654 }
655
656 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
657
658 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
659 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
660
661 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
662 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
663
664 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
665 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
666
667 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
668 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
669 }
670#endif
671
672 // Listen for notification events
673 auto &noteman = NotificationManager::GetInstance();
674
675 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
676 evt_notificationlist_change_listener.Listen(
677 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
678 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
679 if (m_NotificationsList && m_NotificationsList->IsShown()) {
680 m_NotificationsList->ReloadNotificationList();
681 }
682 Refresh();
683 });
684}
685
686ChartCanvas::~ChartCanvas() {
687 delete pThumbDIBShow;
688
689 // Delete Cursors
690 delete pCursorLeft;
691 delete pCursorRight;
692 delete pCursorUp;
693 delete pCursorDown;
694 delete pCursorArrow;
695 delete pCursorPencil;
696 delete pCursorCross;
697
698 delete pPanTimer;
699 delete pMovementTimer;
700 delete pMovementStopTimer;
701 delete pCurTrackTimer;
702 delete pRotDefTimer;
703 delete m_DoubleClickTimer;
704
705 delete m_pTrackRolloverWin;
706 delete m_pRouteRolloverWin;
707 delete m_pAISRolloverWin;
708 delete m_pBrightPopup;
709
710 delete m_pCIWin;
711
712 delete pscratch_bm;
713
714 m_dc_route.SelectObject(wxNullBitmap);
715 delete proute_bm;
716
717 delete pWorldBackgroundChart;
718 delete pss_overlay_bmp;
719
720 delete m_pEM_Feet;
721 delete m_pEM_Meters;
722 delete m_pEM_Fathoms;
723
724 delete m_pEM_OverZoom;
725 // delete m_pEM_CM93Offset;
726
727 delete m_prot_bm;
728
729 delete m_pos_image_user_day;
730 delete m_pos_image_user_dusk;
731 delete m_pos_image_user_night;
732 delete m_pos_image_user_grey_day;
733 delete m_pos_image_user_grey_dusk;
734 delete m_pos_image_user_grey_night;
735 delete m_pos_image_user_yellow_day;
736 delete m_pos_image_user_yellow_dusk;
737 delete m_pos_image_user_yellow_night;
738
739 delete undo;
740#ifdef ocpnUSE_GL
741 if (!g_bdisable_opengl) {
742 delete m_glcc;
743
744#if wxCHECK_VERSION(2, 9, 0)
745 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
746#endif
747 }
748#endif
749
750 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
751 // wx tries to deliver events to this canvas during destroy.
752 MUIBar *muiBar = m_muiBar;
753 m_muiBar = 0;
754 delete muiBar;
755 delete m_pQuilt;
756 delete m_pCurrentStack;
757 delete m_Compass;
758 delete m_Piano;
759 delete m_notification_button;
760}
761
762void ChartCanvas::SetupGridFont() {
763 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
764 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
765 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
766 m_pgridFont = FontMgr::Get().FindOrCreateFont(
767 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
768 FALSE, wxString("Arial"));
769}
770
771void ChartCanvas::RebuildCursors() {
772 delete pCursorLeft;
773 delete pCursorRight;
774 delete pCursorUp;
775 delete pCursorDown;
776 delete pCursorArrow;
777 delete pCursorPencil;
778 delete pCursorCross;
779
780 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
781 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
782
783 double pencilScale =
784 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
785
786 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
787 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
788 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
789 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
790 wxImage ICursorPencil =
791 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
792 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
793
794#if !defined(__WXMSW__) && !defined(__WXQT__)
795 ICursorLeft.ConvertAlphaToMask(128);
796 ICursorRight.ConvertAlphaToMask(128);
797 ICursorUp.ConvertAlphaToMask(128);
798 ICursorDown.ConvertAlphaToMask(128);
799 ICursorPencil.ConvertAlphaToMask(10);
800 ICursorCross.ConvertAlphaToMask(10);
801#endif
802
803 if (ICursorLeft.Ok()) {
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
805 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
806 pCursorLeft = new wxCursor(ICursorLeft);
807 } else
808 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
809
810 if (ICursorRight.Ok()) {
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
812 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
813 pCursorRight = new wxCursor(ICursorRight);
814 } else
815 pCursorRight = new wxCursor(wxCURSOR_ARROW);
816
817 if (ICursorUp.Ok()) {
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
819 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
820 pCursorUp = new wxCursor(ICursorUp);
821 } else
822 pCursorUp = new wxCursor(wxCURSOR_ARROW);
823
824 if (ICursorDown.Ok()) {
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
826 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
827 pCursorDown = new wxCursor(ICursorDown);
828 } else
829 pCursorDown = new wxCursor(wxCURSOR_ARROW);
830
831 if (ICursorPencil.Ok()) {
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
833 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
834 pCursorPencil = new wxCursor(ICursorPencil);
835 } else
836 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
837
838 if (ICursorCross.Ok()) {
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
840 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
841 pCursorCross = new wxCursor(ICursorCross);
842 } else
843 pCursorCross = new wxCursor(wxCURSOR_ARROW);
844
845 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
846 pPlugIn_Cursor = NULL;
847}
848
849void ChartCanvas::CanvasApplyLocale() {
850 CreateDepthUnitEmbossMaps(m_cs);
851 CreateOZEmbossMapData(m_cs);
852}
853
854void ChartCanvas::SetupGlCanvas() {
855#ifndef __ANDROID__
856#ifdef ocpnUSE_GL
857 if (!g_bdisable_opengl) {
858 if (g_bopengl) {
859 wxLogMessage("Creating glChartCanvas");
860 m_glcc = new glChartCanvas(this);
861
862 // We use one context for all GL windows, so that textures etc will be
863 // automatically shared
864 if (IsPrimaryCanvas()) {
865 // qDebug() << "Creating Primary Context";
866
867 // wxGLContextAttrs ctxAttr;
868 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
869 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
870 // NULL, &ctxAttr);
871 wxGLContext *pctx = new wxGLContext(m_glcc);
872 m_glcc->SetContext(pctx);
873 g_pGLcontext = pctx; // Save a copy of the common context
874 } else {
875#ifdef __WXOSX__
876 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
877#else
878 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
879 // saved common context
880#endif
881 }
882 }
883 }
884#endif
885#endif
886
887#ifdef __ANDROID__ // ocpnUSE_GL
888 if (!g_bdisable_opengl) {
889 if (g_bopengl) {
890 // qDebug() << "SetupGlCanvas";
891 wxLogMessage("Creating glChartCanvas");
892
893 // We use one context for all GL windows, so that textures etc will be
894 // automatically shared
895 if (IsPrimaryCanvas()) {
896 qDebug() << "Creating Primary glChartCanvas";
897
898 // wxGLContextAttrs ctxAttr;
899 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
900 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
901 // NULL, &ctxAttr);
902 m_glcc = new glChartCanvas(this);
903
904 wxGLContext *pctx = new wxGLContext(m_glcc);
905 m_glcc->SetContext(pctx);
906 g_pGLcontext = pctx; // Save a copy of the common context
907 m_glcc->m_pParentCanvas = this;
908 // m_glcc->Reparent(this);
909 } else {
910 qDebug() << "Creating Secondary glChartCanvas";
911 // QGLContext *pctx =
912 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
913 // << "pctx: " << pctx;
914
915 m_glcc =
916 new glChartCanvas(wxTheApp->GetTopWindow(),
917 top_frame::Get()->GetWxGlCanvas()); // Shared
918 // m_glcc = new glChartCanvas(this, pctx); //Shared
919 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
920 wxGLContext *pwxctx = new wxGLContext(m_glcc);
921 m_glcc->SetContext(pwxctx);
922 m_glcc->m_pParentCanvas = this;
923 // m_glcc->Reparent(this);
924 }
925 }
926 }
927#endif
928}
929
930void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
931 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
932
933 // On Android, we get a KillFocus on just about every keystroke.
934 // Why?
935#ifdef __ANDROID__
936 return;
937#endif
938
939 // Special logic:
940 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
941 // canvas focus. Why??? Who knows... So, we provide for this case by
942 // starting a timer if required to actually Finish() a route on a legitimate
943 // focus change, but not if the focus is quickly regained ( <20 msec.) on
944 // this canvas.
945#ifdef __WXOSX__
946 if (m_routeState && m_FinishRouteOnKillFocus)
947 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
948#else
949 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
950#endif
951}
952
953void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
954 m_routeFinishTimer.Stop();
955
956 // Try to keep the global top-line menubar selections up to date with the
957 // current "focus" canvas
958 top_frame::Get()->UpdateGlobalMenuItems(this);
959
960 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
961}
962
963void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
964 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
965}
966
967#ifdef HAVE_WX_GESTURE_EVENTS
968void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
969#ifdef __ANDROID__
970 /* we defer the popup menu call upon the leftup event
971 else the menu disappears immediately,
972 (see
973 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
974 */
975 m_popupWanted = true;
976#else
977 m_inLongPress = !g_bhide_context_menus;
978
979 // Send a synthetic mouse left-up event to sync the mouse pan logic.
980 m_menuPos = event.GetPosition();
981 wxMouseEvent ev(wxEVT_LEFT_UP);
982 ev.m_x = m_menuPos.x;
983 ev.m_y = m_menuPos.y;
984 wxPostEvent(this, ev);
985
986 // In touch mode, send a "RIGHT CLICK" event, for plugins
987 if (g_btouch) {
988 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
989 ev_right_click.m_x = m_menuPos.x;
990 ev_right_click.m_y = m_menuPos.y;
991 MouseEvent(ev_right_click);
992 }
993#endif
994}
995
996void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
997 // not implemented yet
998}
999
1000void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1001
1002void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1003
1004void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1005#ifdef __WXGTK__
1006 long dt = m_sw_left_up.Time() - m_sw_up_time;
1007 m_sw_up_time = m_sw_left_up.Time();
1008
1009 // printf(" dt %ld\n",dt);
1010 if (dt < 5) {
1011 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1012 // ignore it.
1013 return;
1014 }
1015#endif
1016 // printf("Left_UP\n");
1017
1018 wxPoint pos = event.GetPosition();
1019
1020 m_leftdown = false;
1021
1022 if (!m_popupWanted) {
1023 wxMouseEvent ev(wxEVT_LEFT_UP);
1024 ev.m_x = pos.x;
1025 ev.m_y = pos.y;
1026 MouseEvent(ev);
1027 return;
1028 }
1029
1030 m_popupWanted = false;
1031
1032 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1033 ev.m_x = pos.x;
1034 ev.m_y = pos.y;
1035
1036 MouseEvent(ev);
1037}
1038
1039void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1040 m_leftdown = true;
1041
1042 // Detect and manage multiple left-downs coming from GTK mouse emulation
1043#ifdef __WXGTK__
1044 long dt = m_sw_left_down.Time() - m_sw_down_time;
1045 m_sw_down_time = m_sw_left_down.Time();
1046
1047 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1048
1049 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1050 // The timing between the two events is dependent upon the wxWidgets
1051 // message queue status, and the processing time required for intervening
1052 // events.
1053 // We detect and remove the duplicate events by measuring the elapsed time
1054 // between arrival of events.
1055 // Choose a duplicate detection time long enough to catch worst case time lag
1056 // between duplicating events, but considerably shorter than the nominal
1057 // "intentional double-click" time interval defined generally as 350 msec.
1058 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1059 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1060 // ignore it.
1061 return;
1062 }
1063#endif
1064
1065 // printf("Left_DOWN\n");
1066
1067 // detect and manage double-tap
1068#ifdef __WXGTK__
1069 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1070 2; // Use system setting for distance
1071 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1072 m_lastTapPos.y - max_double_click_distance,
1073 max_double_click_distance * 2, max_double_click_distance * 2);
1074
1075 // A new tap has started, check if it's close enough and in time
1076 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1077 // printf(" TapBump 1\n");
1078 m_tap_count += 1;
1079 } else {
1080 // printf(" TapSet 1\n");
1081 m_tap_count = 1;
1082 m_lastTapPos = event.GetPosition();
1083 m_tap_timer.StartOnce(
1084 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1085 }
1086
1087 if (m_tap_count == 2) {
1088 // printf(" Doubletap detected\n");
1089 m_tap_count = 0; // Reset after a double-tap
1090
1091 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1092 ev.m_x = event.m_x;
1093 ev.m_y = event.m_y;
1094 // wxPostEvent(this, ev);
1095 MouseEvent(ev);
1096 return;
1097 }
1098
1099#endif
1100
1101 MouseEvent(event);
1102}
1103
1104void ChartCanvas::OnMotion(wxMouseEvent &event) {
1105 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1106 dragging, upon simple click, and without the OnLeftDown event before Thus,
1107 this consists in skiping it, and setting the leftdown bit according to a
1108 status that we trust */
1109 event.m_leftDown = m_leftdown;
1110 MouseEvent(event);
1111}
1112
1113void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1114 /* there are spurious end zoom events upon right-click */
1115 if (event.IsGestureEnd()) return;
1116
1117 double factor = event.GetZoomFactor();
1118
1119 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1120 m_oldVPSScale = GetVPScale();
1121 }
1122
1123 double current_vps = GetVPScale();
1124 double wanted_factor = m_oldVPSScale / current_vps * factor;
1125
1126 ZoomCanvas(wanted_factor, true, false);
1127
1128 // Allow combined zoom/pan operation
1129 if (event.IsGestureStart()) {
1130 m_zoomStartPoint = event.GetPosition();
1131 } else {
1132 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1133 PanCanvas(-delta.x, -delta.y);
1134 m_zoomStartPoint = event.GetPosition();
1135 }
1136}
1137
1138void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1139
1140void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1141 DoRotateCanvas(0.0);
1142}
1143#endif /* HAVE_WX_GESTURE_EVENTS */
1144
1145void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1146 // printf("tap timer %d\n", m_tap_count);
1147 m_tap_count = 0;
1148}
1149
1150void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1151 m_FinishRouteOnKillFocus = false;
1152 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1153 m_FinishRouteOnKillFocus = true;
1154}
1155
1156void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1157 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1158 m_vLat = pcc->iLat;
1159 m_vLon = pcc->iLon;
1160
1161 m_restore_dbindex = pcc->DBindex;
1162 m_bFollow = pcc->bFollow;
1163 if (pcc->GroupID < 0) pcc->GroupID = 0;
1164
1165 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1166 m_groupIndex = 0;
1167 else
1168 m_groupIndex = pcc->GroupID;
1169
1170 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1171
1172 ShowTides(pcc->bShowTides);
1173 ShowCurrents(pcc->bShowCurrents);
1174
1175 SetShowDepthUnits(pcc->bShowDepthUnits);
1176 SetShowGrid(pcc->bShowGrid);
1177 SetShowOutlines(pcc->bShowOutlines);
1178
1179 SetShowAIS(pcc->bShowAIS);
1180 SetAttenAIS(pcc->bAttenAIS);
1181
1182 // ENC options
1183 SetShowENCText(pcc->bShowENCText);
1184 m_encDisplayCategory = pcc->nENCDisplayCategory;
1185 m_encShowDepth = pcc->bShowENCDepths;
1186 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1187 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1188 m_encShowLights = pcc->bShowENCLights;
1189 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1190 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1191 m_encShowDataQual = pcc->bShowENCDataQuality;
1192
1193 bool courseUp = pcc->bCourseUp;
1194 bool headUp = pcc->bHeadUp;
1195 m_upMode = NORTH_UP_MODE;
1196 if (courseUp)
1197 m_upMode = COURSE_UP_MODE;
1198 else if (headUp)
1199 m_upMode = HEAD_UP_MODE;
1200
1201 m_bLookAhead = pcc->bLookahead;
1202
1203 m_singleChart = NULL;
1204}
1205
1206void ChartCanvas::ApplyGlobalSettings() {
1207 // GPS compas window
1208 if (m_Compass) {
1209 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1210 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1211 }
1212 if (m_notification_button) m_notification_button->UpdateStatus();
1213}
1214
1215void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1216 bool groupOK = CheckGroup(m_groupIndex);
1217
1218 if (!groupOK) {
1219 SetGroupIndex(m_groupIndex, true);
1220 }
1221}
1222
1223void ChartCanvas::SetShowGPS(bool bshow) {
1224 if (m_bShowGPS != bshow) {
1225 delete m_Compass;
1226 m_Compass = new ocpnCompass(this, bshow);
1227 m_Compass->SetScaleFactor(g_compass_scalefactor);
1228 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1229 }
1230 m_bShowGPS = bshow;
1231}
1232
1233void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1234 m_bShowCompassWin = bshow;
1235 if (m_Compass) {
1236 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1237 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1238 }
1239}
1240
1241int ChartCanvas::GetPianoHeight() {
1242 int height = 0;
1243 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1244
1245 return height;
1246}
1247
1248void ChartCanvas::ConfigureChartBar() {
1249 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1250
1251 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1252 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1253
1254 if (GetQuiltMode()) {
1255 m_Piano->SetRoundedRectangles(true);
1256 }
1257 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1258 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1259 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1260}
1261
1262void ChartCanvas::ShowTides(bool bShow) {
1263 top_frame::Get()->LoadHarmonics();
1264
1265 if (ptcmgr->IsReady()) {
1266 SetbShowTide(bShow);
1267
1268 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1269 } else {
1270 wxLogMessage("Chart1::Event...TCMgr Not Available");
1271 SetbShowTide(false);
1272 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1273 }
1274
1275 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1276 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1277
1278 // TODO
1279 // if( GetbShowTide() ) {
1280 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1281 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1282 // update
1283 // } else
1284 // FrameTCTimer.Stop();
1285}
1286
1287void ChartCanvas::ShowCurrents(bool bShow) {
1288 top_frame::Get()->LoadHarmonics();
1289
1290 if (ptcmgr->IsReady()) {
1291 SetbShowCurrent(bShow);
1292 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1293 } else {
1294 wxLogMessage("Chart1::Event...TCMgr Not Available");
1295 SetbShowCurrent(false);
1296 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1297 }
1298
1299 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1300 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1301
1302 // TODO
1303 // if( GetbShowCurrent() ) {
1304 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1305 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1306 // update
1307 // } else
1308 // FrameTCTimer.Stop();
1309}
1310
1311// TODO
1312static ChartDummy *pDummyChart;
1313
1316
1317void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1318
1319void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1320 SetAlertString("");
1321
1322 int new_index = index;
1323 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1324
1325 bool bgroup_override = false;
1326 int old_group_index = new_index;
1327
1328 if (!CheckGroup(new_index)) {
1329 new_index = 0;
1330 bgroup_override = true;
1331 }
1332
1333 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1334 new_index = index;
1335
1336 // Get the currently displayed chart native scale, and the current ViewPort
1337 int current_chart_native_scale = GetCanvasChartNativeScale();
1338 ViewPort vp = GetVP();
1339
1340 m_groupIndex = new_index;
1341
1342 // Are there ENCs in this group
1343 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1344
1345 // Update the MUIBar for ENC availability
1346 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1347
1348 // Allow the chart database to pre-calculate the MBTile inclusion test
1349 // boolean...
1350 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1351
1352 // Invalidate the "sticky" chart on group change, since it might not be in
1353 // the new group
1354 g_sticky_chart = -1;
1355
1356 // We need a chartstack and quilt to figure out which chart to open in the
1357 // new group
1358 UpdateCanvasOnGroupChange();
1359
1360 int dbi_now = -1;
1361 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1362
1363 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1364
1365 // If a new reference chart is indicated, set a good scale for it.
1366 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1367 double best_scale = GetBestStartScale(dbi_hint, vp);
1368 SetVPScale(best_scale);
1369 }
1370
1371 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1372
1373 // Refresh the canvas, selecting the "best" chart,
1374 // applying the prior ViewPort exactly
1375 canvasChartsRefresh(dbi_hint);
1376
1377 UpdateCanvasControlBar();
1378
1379 if (!autoSwitch && bgroup_override) {
1380 // show a short timed message box
1381 wxString msg(_("Group \""));
1382
1383 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1384 msg += pGroup->m_group_name;
1385
1386 msg += _("\" is empty.");
1387
1388 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1389
1390 return;
1391 }
1392
1393 // Message box is deferred so that canvas refresh occurs properly before
1394 // dialog
1395 if (bgroup_override) {
1396 wxString msg(_("Group \""));
1397
1398 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1399 msg += pGroup->m_group_name;
1400
1401 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1402
1403 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1404 }
1405}
1406
1407bool ChartCanvas::CheckGroup(int igroup) {
1408 if (!ChartData) return true; // Not known yet...
1409
1410 if (igroup == 0) return true; // "all charts" is always OK
1411
1412 if (igroup < 0) // negative group is an error
1413 return false;
1414
1415 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1416
1417 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1418 // and auto-shift to group 0
1419 return false;
1420
1421 for (const auto &elem : pGroup->m_element_array) {
1422 for (unsigned int ic = 0;
1423 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1424 auto &cte = ChartData->GetChartTableEntry(ic);
1425 wxString chart_full_path(cte.GetpFullPath(), wxConvUTF8);
1426
1427 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1428 }
1429 }
1430
1431 // If necessary, check for GSHHS
1432 for (const auto &elem : pGroup->m_element_array) {
1433 const wxString &element_root = elem.m_element_name;
1434 wxString test_string = "GSHH";
1435 if (element_root.Upper().Contains(test_string)) return true;
1436 }
1437
1438 return false;
1439}
1440
1441void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1442 if (!ChartData) return;
1443
1444 AbstractPlatform::ShowBusySpinner();
1445
1446 double old_scale = GetVPScale();
1447 InvalidateQuilt();
1448 SetQuiltRefChart(-1);
1449
1450 m_singleChart = NULL;
1451
1452 // delete m_pCurrentStack;
1453 // m_pCurrentStack = NULL;
1454
1455 // Build a new ChartStack
1456 if (!m_pCurrentStack) {
1457 m_pCurrentStack = new ChartStack;
1458 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1459 }
1460
1461 if (-1 != dbi_hint) {
1462 if (GetQuiltMode()) {
1463 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1464 SetQuiltRefChart(dbi_hint);
1465 } else {
1466 // Open the saved chart
1467 ChartBase *pTentative_Chart;
1468 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1469
1470 if (pTentative_Chart) {
1471 /* m_singleChart is always NULL here, (set above) should this go before
1472 * that? */
1473 if (m_singleChart) m_singleChart->Deactivate();
1474
1475 m_singleChart = pTentative_Chart;
1476 m_singleChart->Activate();
1477
1478 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1479 GetpCurrentStack(), m_singleChart->GetFullPath());
1480 }
1481 }
1482
1483 // refresh_Piano();
1484 } else {
1485 // Select reference chart from the stack, as though clicked by user
1486 // Make it the smallest scale chart on the stack
1487 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1488 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1489 SetQuiltRefChart(selected_index);
1490 }
1491
1492 // Validate the correct single chart, or set the quilt mode as appropriate
1493 SetupCanvasQuiltMode();
1494 if (!GetQuiltMode() && m_singleChart == 0) {
1495 // use a dummy like in DoChartUpdate
1496 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1497 m_singleChart = pDummyChart;
1498 SetVPScale(old_scale);
1499 }
1500
1501 ReloadVP();
1502
1503 UpdateCanvasControlBar();
1504 UpdateGPSCompassStatusBox(true);
1505
1506 SetCursor(wxCURSOR_ARROW);
1507
1508 AbstractPlatform::HideBusySpinner();
1509}
1510
1511bool ChartCanvas::DoCanvasUpdate() {
1512 double tLat, tLon; // Chart Stack location
1513 double vpLat, vpLon; // ViewPort location
1514 bool blong_jump = false;
1515 meters_to_shift = 0;
1516 dir_to_shift = 0;
1517
1518 bool bNewChart = false;
1519 bool bNewView = false;
1520 bool bCanvasChartAutoOpen = true; // debugging
1521
1522 bool bNewPiano = false;
1523 bool bOpenSpecified;
1524 ChartStack LastStack;
1525 ChartBase *pLast_Ch;
1526
1527 ChartStack WorkStack;
1528
1529 if (bDBUpdateInProgress) return false;
1530 if (!ChartData) return false;
1531
1532 if (ChartData->IsBusy()) return false;
1533 if (m_chart_drag_inertia_active) return false;
1534
1535 // Startup case:
1536 // Quilting is enabled, but the last chart seen was not quiltable
1537 // In this case, drop to single chart mode, set persistence flag,
1538 // And open the specified chart
1539 // TODO implement this
1540 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1541 // if( GetQuiltMode() ) {
1542 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1543 // gFrame->ToggleQuiltMode();
1544 // m_bpersistent_quilt = true;
1545 // m_singleChart = NULL;
1546 // }
1547 // }
1548 // }
1549
1550 // If in auto-follow mode, use the current glat,glon to build chart
1551 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1552 // other means
1553
1554 if (m_bFollow) {
1555 tLat = gLat;
1556 tLon = gLon;
1557
1558 // Set the ViewPort center based on the OWNSHIP offset
1559 double dx = m_OSoffsetx;
1560 double dy = m_OSoffsety;
1561 double d_east = dx / GetVP().view_scale_ppm;
1562 double d_north = dy / GetVP().view_scale_ppm;
1563
1564 if (GetUpMode() == NORTH_UP_MODE) {
1565 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1566 } else {
1567 double offset_angle = atan2(d_north, d_east);
1568 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1569 double chart_angle = GetVPRotation();
1570 double target_angle = chart_angle + offset_angle;
1571 double d_east_mod = offset_distance * cos(target_angle);
1572 double d_north_mod = offset_distance * sin(target_angle);
1573 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1574 }
1575
1576 // on lookahead mode, adjust the vp center point
1577 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1578 double cog_to_use = gCog;
1579 if (g_btenhertz &&
1580 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1581 cog_to_use = gCog_gt;
1582 blong_jump = true;
1583 }
1584 if (!g_btenhertz) cog_to_use = g_COGAvg;
1585
1586 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1587
1588 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1589 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1590
1591 double pixel_delta_tent =
1592 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1593
1594 double pixel_delta = 0;
1595
1596 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1597 // avoid jumping of the vp center point during slow maneuvering, or at
1598 // anchor....
1599 if (!std::isnan(gSog)) {
1600 if (gSog < 2.0)
1601 pixel_delta = 0.;
1602 else
1603 pixel_delta = pixel_delta_tent;
1604 }
1605
1606 meters_to_shift = 0;
1607 dir_to_shift = 0;
1608 if (!std::isnan(gCog)) {
1609 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1610 dir_to_shift = cog_to_use;
1611 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1612 &vpLon);
1613 } else {
1614 vpLat = gLat;
1615 vpLon = gLon;
1616 }
1617 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1618 m_OSoffsetx = 0; // center ownship on loss of GPS
1619 m_OSoffsety = 0;
1620 vpLat = gLat;
1621 vpLon = gLon;
1622 }
1623
1624 } else {
1625 tLat = m_vLat;
1626 tLon = m_vLon;
1627 vpLat = m_vLat;
1628 vpLon = m_vLon;
1629 }
1630
1631 if (GetQuiltMode()) {
1632 int current_db_index = -1;
1633 if (m_pCurrentStack)
1634 current_db_index =
1635 m_pCurrentStack
1636 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1637 // chart dbIndex
1638 else
1639 m_pCurrentStack = new ChartStack;
1640
1641 // This logic added to enable opening a chart when there is no
1642 // previous chart indication, either from inital startup, or from adding
1643 // new chart directory
1644 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1645 m_pCurrentStack) {
1646 if (m_pCurrentStack->nEntry) {
1647 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1648 1); // smallest scale
1649 SelectQuiltRefdbChart(new_dbIndex, true);
1650 m_bautofind = false;
1651 }
1652 }
1653
1654 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1655 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1656
1657 if (m_bFirstAuto) {
1658 // Allow the chart database to pre-calculate the MBTile inclusion test
1659 // boolean...
1660 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1661
1662 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1663 // physical pixels. On standard DPI displays where logical = physical
1664 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1665 // logical pixels, this ratio would be 0.5.
1666 double proposed_scale_onscreen =
1667 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1668
1669 int initial_db_index = m_restore_dbindex;
1670 if (initial_db_index < 0) {
1671 if (m_pCurrentStack->nEntry) {
1672 initial_db_index =
1673 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1674 } else
1675 m_bautofind = true; // initial_db_index = 0;
1676 }
1677
1678 if (m_pCurrentStack->nEntry) {
1679 int initial_type = ChartData->GetDBChartType(initial_db_index);
1680
1681 // Check to see if the target new chart is quiltable as a reference
1682 // chart
1683
1684 if (!IsChartQuiltableRef(initial_db_index)) {
1685 // If it is not quiltable, then walk the stack up looking for a
1686 // satisfactory chart i.e. one that is quiltable and of the same type
1687 // XXX if there's none?
1688 int stack_index = 0;
1689
1690 if (stack_index >= 0) {
1691 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1692 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1693 if (IsChartQuiltableRef(test_db_index) &&
1694 (initial_type ==
1695 ChartData->GetDBChartType(initial_db_index))) {
1696 initial_db_index = test_db_index;
1697 break;
1698 }
1699 stack_index++;
1700 }
1701 }
1702 }
1703
1704 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1705 if (pc) {
1706 SetQuiltRefChart(initial_db_index);
1707 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1708 }
1709 }
1710 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1711 // just GetVPScale(), so I'm not sure why it's necessary to define the
1712 // proposed_scale_onscreen variable.
1713 bNewView |= SetViewPoint(vpLat, vpLon,
1714 GetCanvasScaleFactor() / proposed_scale_onscreen,
1715 0, GetVPRotation());
1716 }
1717 // Measure rough jump distance if in bfollow mode
1718 // No good reason to do smooth pan for
1719 // jump distance more than one screen width at scale.
1720 bool super_jump = false;
1721 if (m_bFollow) {
1722 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1723 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1724 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1725 }
1726#if 0
1727 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1728 int nstep = 5;
1729 if (blong_jump) nstep = 20;
1730 StartTimedMovementVP(vpLat, vpLon, nstep);
1731 } else
1732#endif
1733 {
1734 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1735 }
1736
1737 goto update_finish;
1738 }
1739
1740 // Single Chart Mode from here....
1741 pLast_Ch = m_singleChart;
1742 ChartTypeEnum new_open_type;
1743 ChartFamilyEnum new_open_family;
1744 if (pLast_Ch) {
1745 new_open_type = pLast_Ch->GetChartType();
1746 new_open_family = pLast_Ch->GetChartFamily();
1747 } else {
1748 new_open_type = CHART_TYPE_KAP;
1749 new_open_family = CHART_FAMILY_RASTER;
1750 }
1751
1752 bOpenSpecified = m_bFirstAuto;
1753
1754 // Make sure the target stack is valid
1755 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1756
1757 // Build a chart stack based on tLat, tLon
1758 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1759 m_groupIndex)) { // Bogus Lat, Lon?
1760 if (NULL == pDummyChart) {
1761 pDummyChart = new ChartDummy;
1762 bNewChart = true;
1763 }
1764
1765 if (m_singleChart)
1766 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1767
1768 m_singleChart = pDummyChart;
1769
1770 // If the current viewpoint is invalid, set the default scale to
1771 // something reasonable.
1772 double set_scale = GetVPScale();
1773 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1774
1775 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1776
1777 // If the chart stack has just changed, there is new status
1778 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1779 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1780 bNewPiano = true;
1781 bNewChart = true;
1782 }
1783 }
1784
1785 // Copy the new (by definition empty) stack into the target stack
1786 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1787
1788 goto update_finish;
1789 }
1790
1791 // Check to see if Chart Stack has changed
1792 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1793 // New chart stack, so...
1794 bNewPiano = true;
1795
1796 // Save a copy of the current stack
1797 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1798
1799 // Copy the new stack into the target stack
1800 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1801
1802 // Is Current Chart in new stack?
1803
1804 int tEntry = -1;
1805 if (NULL != m_singleChart) // this handles startup case
1806 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1807 m_singleChart->GetFullPath());
1808
1809 if (tEntry != -1) { // m_singleChart is in the new stack
1810 m_pCurrentStack->CurrentStackEntry = tEntry;
1811 bNewChart = false;
1812 }
1813
1814 else // m_singleChart is NOT in new stack
1815 { // So, need to open a new chart
1816 // Find the largest scale raster chart that opens OK
1817
1818 ChartBase *pProposed = NULL;
1819
1820 if (bCanvasChartAutoOpen) {
1821 bool search_direction =
1822 false; // default is to search from lowest to highest
1823 int start_index = 0;
1824
1825 // A special case: If panning at high scale, open largest scale
1826 // chart first
1827 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1828 (LastStack.nEntry == 0)) {
1829 search_direction = true;
1830 start_index = m_pCurrentStack->nEntry - 1;
1831 }
1832
1833 // Another special case, open specified index on program start
1834 if (bOpenSpecified) {
1835 search_direction = false;
1836 start_index = 0;
1837 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1838 start_index = 0;
1839
1840 new_open_type = CHART_TYPE_DONTCARE;
1841 }
1842
1843 pProposed = ChartData->OpenStackChartConditional(
1844 m_pCurrentStack, start_index, search_direction, new_open_type,
1845 new_open_family);
1846
1847 // Try to open other types/families of chart in some priority
1848 if (NULL == pProposed)
1849 pProposed = ChartData->OpenStackChartConditional(
1850 m_pCurrentStack, start_index, search_direction,
1851 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1852
1853 if (NULL == pProposed)
1854 pProposed = ChartData->OpenStackChartConditional(
1855 m_pCurrentStack, start_index, search_direction,
1856 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1857
1858 bNewChart = true;
1859
1860 } // bCanvasChartAutoOpen
1861
1862 else
1863 pProposed = NULL;
1864
1865 // If no go, then
1866 // Open a Dummy Chart
1867 if (NULL == pProposed) {
1868 if (NULL == pDummyChart) {
1869 pDummyChart = new ChartDummy;
1870 bNewChart = true;
1871 }
1872
1873 if (pLast_Ch)
1874 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1875
1876 pProposed = pDummyChart;
1877 }
1878
1879 // Arriving here, pProposed points to an opened chart, or NULL.
1880 if (m_singleChart) m_singleChart->Deactivate();
1881 m_singleChart = pProposed;
1882
1883 if (m_singleChart) {
1884 m_singleChart->Activate();
1885 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1886 m_pCurrentStack, m_singleChart->GetFullPath());
1887 }
1888 } // need new chart
1889
1890 // Arriving here, m_singleChart is opened and OK, or NULL
1891 if (NULL != m_singleChart) {
1892 // Setup the view using the current scale
1893 double set_scale = GetVPScale();
1894
1895 // If the current viewpoint is invalid, set the default scale to
1896 // something reasonable.
1897 if (!GetVP().IsValid())
1898 set_scale = 1. / 20000.;
1899 else { // otherwise, match scale if elected.
1900 double proposed_scale_onscreen;
1901
1902 if (m_bFollow) { // autoset the scale only if in autofollow
1903 double new_scale_ppm =
1904 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1905 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1906 } else
1907 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1908
1909 // This logic will bring a new chart onscreen at roughly twice the true
1910 // paper scale equivalent. Note that first chart opened on application
1911 // startup (bOpenSpecified = true) will open at the config saved scale
1912 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1913 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1914 double equivalent_vp_scale =
1915 GetCanvasScaleFactor() / proposed_scale_onscreen;
1916 double new_scale_ppm =
1917 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1918 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1919 }
1920
1921 if (m_bFollow) { // bounds-check the scale only if in autofollow
1922 proposed_scale_onscreen =
1923 wxMin(proposed_scale_onscreen,
1924 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1925 GetCanvasWidth()));
1926 proposed_scale_onscreen =
1927 wxMax(proposed_scale_onscreen,
1928 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1930 }
1931
1932 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1933 }
1934
1935 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1936 m_singleChart->GetChartSkew() * PI / 180.,
1937 GetVPRotation());
1938 }
1939 } // new stack
1940
1941 else // No change in Chart Stack
1942 {
1943 if ((m_bFollow) && m_singleChart)
1944 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1945 m_singleChart->GetChartSkew() * PI / 180.,
1946 GetVPRotation());
1947 }
1948
1949update_finish:
1950
1951 // TODO
1952 // if( bNewPiano ) UpdateControlBar();
1953
1954 m_bFirstAuto = false; // Auto open on program start
1955
1956 // If we need a Refresh(), do it here...
1957 // But don't duplicate a Refresh() done by SetViewPoint()
1958 if (bNewChart && !bNewView) Refresh(false);
1959
1960#ifdef ocpnUSE_GL
1961 // If a new chart, need to invalidate gl viewport for refresh
1962 // so the fbo gets flushed
1963 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1964#endif
1965
1966 return bNewChart | bNewView;
1967}
1968
1969void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1970 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1971
1972 SetQuiltRefChart(db_index);
1973 if (ChartData) {
1974 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1975 if (pc) {
1976 if (b_autoscale) {
1977 double best_scale_ppm = GetBestVPScale(pc);
1978 SetVPScale(best_scale_ppm);
1979 }
1980 } else
1981 SetQuiltRefChart(-1);
1982 } else
1983 SetQuiltRefChart(-1);
1984}
1985
1986void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1987 std::vector<int> piano_chart_index_array =
1988 GetQuiltExtendedStackdbIndexArray();
1989 int current_db_index = piano_chart_index_array[selected_index];
1990
1991 SelectQuiltRefdbChart(current_db_index);
1992}
1993
1994double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1995 if (pchart) {
1996 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1997
1998 if ((g_bPreserveScaleOnX) ||
1999 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2000 double new_scale_ppm = GetVPScale();
2001 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2002 } else {
2003 // This logic will bring the new chart onscreen at roughly twice the true
2004 // paper scale equivalent.
2005 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2006 double equivalent_vp_scale =
2007 GetCanvasScaleFactor() / proposed_scale_onscreen;
2008 double new_scale_ppm =
2009 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2010 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2011 }
2012
2013 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2014 // set. Otherwise, we get severe performance problems on all platforms
2015
2016 double max_underzoom_multiplier = 2.0;
2017 if (GetVP().b_quilt) {
2018 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2019 pchart->GetChartType(),
2020 pchart->GetChartFamily());
2021 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2022 }
2023
2024 proposed_scale_onscreen = wxMin(
2025 proposed_scale_onscreen,
2026 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2027 max_underzoom_multiplier);
2028
2029 // And, do not allow excessive overzoom either
2030 proposed_scale_onscreen =
2031 wxMax(proposed_scale_onscreen,
2032 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2033
2034 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2035 } else
2036 return 1.0;
2037}
2038
2039void ChartCanvas::SetupCanvasQuiltMode() {
2040 if (GetQuiltMode()) // going to quilt mode
2041 {
2042 ChartData->LockCache();
2043
2044 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2045
2046 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2047
2048 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2049 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2050 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2051 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2052
2053 m_Piano->SetRoundedRectangles(true);
2054
2055 // Select the proper Ref chart
2056 int target_new_dbindex = -1;
2057 if (m_pCurrentStack) {
2058 target_new_dbindex =
2059 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2060
2061 if (-1 != target_new_dbindex) {
2062 if (!IsChartQuiltableRef(target_new_dbindex)) {
2063 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2064 int type = ChartData->GetDBChartType(target_new_dbindex);
2065
2066 // walk the stack up looking for a satisfactory chart
2067 int stack_index = m_pCurrentStack->CurrentStackEntry;
2068
2069 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2070 (stack_index >= 0)) {
2071 int proj_tent = ChartData->GetDBChartProj(
2072 m_pCurrentStack->GetDBIndex(stack_index));
2073 int type_tent = ChartData->GetDBChartType(
2074 m_pCurrentStack->GetDBIndex(stack_index));
2075
2076 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2077 if ((proj == proj_tent) && (type_tent == type)) {
2078 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2079 break;
2080 }
2081 }
2082 stack_index++;
2083 }
2084 }
2085 }
2086 }
2087
2088 if (IsChartQuiltableRef(target_new_dbindex))
2089 SelectQuiltRefdbChart(target_new_dbindex,
2090 false); // Try not to allow a scale change
2091 else
2092 SelectQuiltRefdbChart(-1, false);
2093
2094 m_singleChart = NULL; // Bye....
2095
2096 // Re-qualify the quilt reference chart selection
2097 AdjustQuiltRefChart();
2098
2099 // Restore projection type saved on last quilt mode toggle
2100 // TODO
2101 // if(g_sticky_projection != -1)
2102 // GetVP().SetProjectionType(g_sticky_projection);
2103 // else
2104 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2105 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2106
2107 } else // going to SC Mode
2108 {
2109 std::vector<int> empty_array;
2110 m_Piano->SetActiveKeyArray(empty_array);
2111 m_Piano->SetNoshowIndexArray(empty_array);
2112 m_Piano->SetEclipsedIndexArray(empty_array);
2113
2114 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2115 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2116 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2117 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2118 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2119
2120 m_Piano->SetRoundedRectangles(false);
2121 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2122 }
2123
2124 // When shifting from quilt to single chart mode, select the "best" single
2125 // chart to show
2126 if (!GetQuiltMode()) {
2127 if (ChartData && ChartData->IsValid()) {
2128 UnlockQuilt();
2129
2130 double tLat, tLon;
2131 if (m_bFollow == true) {
2132 tLat = gLat;
2133 tLon = gLon;
2134 } else {
2135 tLat = m_vLat;
2136 tLon = m_vLon;
2137 }
2138
2139 if (!m_singleChart) {
2140 // Build a temporary chart stack based on tLat, tLon
2141 ChartStack TempStack;
2142 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2143 m_groupIndex);
2144
2145 // Iterate over the quilt charts actually shown, looking for the
2146 // largest scale chart that will be in the new chartstack.... This
2147 // will (almost?) always be the reference chart....
2148
2149 ChartBase *Candidate_Chart = NULL;
2150 int cur_max_scale = (int)1e8;
2151
2152 ChartBase *pChart = GetFirstQuiltChart();
2153 while (pChart) {
2154 // Is this pChart in new stack?
2155 int tEntry =
2156 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2157 if (tEntry != -1) {
2158 if (pChart->GetNativeScale() < cur_max_scale) {
2159 Candidate_Chart = pChart;
2160 cur_max_scale = pChart->GetNativeScale();
2161 }
2162 }
2163 pChart = GetNextQuiltChart();
2164 }
2165
2166 m_singleChart = Candidate_Chart;
2167
2168 // If the quilt is empty, there is no "best" chart.
2169 // So, open the smallest scale chart in the current stack
2170 if (NULL == m_singleChart) {
2171 m_singleChart = ChartData->OpenStackChartConditional(
2172 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2173 CHART_FAMILY_DONTCARE);
2174 }
2175 }
2176
2177 // Invalidate all the charts in the quilt,
2178 // as any cached data may be region based and not have fullscreen coverage
2179 InvalidateAllQuiltPatchs();
2180
2181 if (m_singleChart) {
2182 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2183 std::vector<int> one_array;
2184 one_array.push_back(dbi);
2185 m_Piano->SetActiveKeyArray(one_array);
2186 }
2187
2188 if (m_singleChart) {
2189 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2190 }
2191 }
2192 // Invalidate the current stack so that it will be rebuilt on next tick
2193 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2194 }
2195}
2196
2197bool ChartCanvas::IsTempMenuBarEnabled() {
2198#ifdef __WXMSW__
2199 int major;
2200 wxGetOsVersion(&major);
2201 return (major >
2202 5); // For Windows, function is only available on Vista and above
2203#else
2204 return true;
2205#endif
2206}
2207
2208double ChartCanvas::GetCanvasRangeMeters() {
2209 int width, height;
2210 GetSize(&width, &height);
2211 int minDimension = wxMin(width, height);
2212
2213 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2214 range *= cos(GetVP().clat * PI / 180.);
2215 return range;
2216}
2217
2218void ChartCanvas::SetCanvasRangeMeters(double range) {
2219 int width, height;
2220 GetSize(&width, &height);
2221 int minDimension = wxMin(width, height);
2222
2223 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2224 SetVPScale(scale_ppm / 2);
2225}
2226
2227bool ChartCanvas::SetUserOwnship() {
2228 // Look for user defined ownship image
2229 // This may be found in the shared data location along with other user
2230 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2231 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2232 double factor_dusk = 0.5;
2233 double factor_night = 0.25;
2234
2235 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2236 m_pos_image_user_day = new wxImage;
2237 *m_pos_image_user_day = pbmp->ConvertToImage();
2238 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2239
2240 int gimg_width = m_pos_image_user_day->GetWidth();
2241 int gimg_height = m_pos_image_user_day->GetHeight();
2242
2243 // Make dusk and night images
2244 m_pos_image_user_dusk = new wxImage;
2245 m_pos_image_user_night = new wxImage;
2246
2247 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2248 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2249
2250 for (int iy = 0; iy < gimg_height; iy++) {
2251 for (int ix = 0; ix < gimg_width; ix++) {
2252 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2253 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2254 m_pos_image_user_day->GetGreen(ix, iy),
2255 m_pos_image_user_day->GetBlue(ix, iy));
2256 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2257 hsv.value = hsv.value * factor_dusk;
2258 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2259 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2260 nrgb.blue);
2261
2262 hsv = wxImage::RGBtoHSV(rgb);
2263 hsv.value = hsv.value * factor_night;
2264 nrgb = wxImage::HSVtoRGB(hsv);
2265 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2266 nrgb.blue);
2267 }
2268 }
2269 }
2270
2271 // Make some alternate greyed out day/dusk/night images
2272 m_pos_image_user_grey_day = new wxImage;
2273 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2274
2275 m_pos_image_user_grey_dusk = new wxImage;
2276 m_pos_image_user_grey_night = new wxImage;
2277
2278 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2279 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2280
2281 for (int iy = 0; iy < gimg_height; iy++) {
2282 for (int ix = 0; ix < gimg_width; ix++) {
2283 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2284 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2285 m_pos_image_user_grey_day->GetGreen(ix, iy),
2286 m_pos_image_user_grey_day->GetBlue(ix, iy));
2287 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2288 hsv.value = hsv.value * factor_dusk;
2289 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2290 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2291 nrgb.blue);
2292
2293 hsv = wxImage::RGBtoHSV(rgb);
2294 hsv.value = hsv.value * factor_night;
2295 nrgb = wxImage::HSVtoRGB(hsv);
2296 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2297 nrgb.blue);
2298 }
2299 }
2300 }
2301
2302 // Make a yellow image for rendering under low accuracy chart conditions
2303 m_pos_image_user_yellow_day = new wxImage;
2304 m_pos_image_user_yellow_dusk = new wxImage;
2305 m_pos_image_user_yellow_night = new wxImage;
2306
2307 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2308 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2309 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2310
2311 for (int iy = 0; iy < gimg_height; iy++) {
2312 for (int ix = 0; ix < gimg_width; ix++) {
2313 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2314 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2315 m_pos_image_user_grey_day->GetGreen(ix, iy),
2316 m_pos_image_user_grey_day->GetBlue(ix, iy));
2317
2318 // Simply remove all "blue" from the greyscaled image...
2319 // so, what is not black becomes yellow.
2320 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2321 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2322 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2323
2324 hsv = wxImage::RGBtoHSV(rgb);
2325 hsv.value = hsv.value * factor_dusk;
2326 nrgb = wxImage::HSVtoRGB(hsv);
2327 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2328
2329 hsv = wxImage::RGBtoHSV(rgb);
2330 hsv.value = hsv.value * factor_night;
2331 nrgb = wxImage::HSVtoRGB(hsv);
2332 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2333 0);
2334 }
2335 }
2336 }
2337
2338 return true;
2339 } else
2340 return false;
2341}
2342
2344 m_display_size_mm = size;
2345
2346 // int sx, sy;
2347 // wxDisplaySize( &sx, &sy );
2348
2349 // Calculate logical pixels per mm for later reference.
2350 wxSize sd = g_Platform->getDisplaySize();
2351 double horizontal = sd.x;
2352 // Set DPI (Win) scale factor
2353 g_scaler = g_Platform->GetDisplayDIPMult(this);
2354
2355 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2356 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2357
2358 if (ps52plib) {
2359 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2360 ps52plib->SetPPMM(m_pix_per_mm);
2361 }
2362
2363 wxString msg;
2364 msg.Printf(
2365 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2366 "%d:%d ",
2367 m_display_size_mm, sd.x, sd.y);
2368 wxLogDebug(msg);
2369
2370 int ssx, ssy;
2371 ssx = g_monitor_info[g_current_monitor].width;
2372 ssy = g_monitor_info[g_current_monitor].height;
2373 msg.Printf("monitor size: %d %d", ssx, ssy);
2374 wxLogDebug(msg);
2375
2376 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2377}
2378#if 0
2379void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2380{
2381 wxString msg(event.m_string.c_str(), wxConvUTF8);
2382 // if cpus are removed between runs
2383 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2384 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2385 }
2386
2387 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2388 {
2389 compress_msg_array.RemoveAt(event.thread);
2390 compress_msg_array.Insert( msg, event.thread);
2391 }
2392 else
2393 compress_msg_array.Add(msg);
2394
2395
2396 wxString combined_msg;
2397 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2398 combined_msg += compress_msg_array[i];
2399 combined_msg += "\n";
2400 }
2401
2402 bool skip = false;
2403 pprog->Update(pprog_count, combined_msg, &skip );
2404 pprog->SetSize(pprog_size);
2405 if(skip)
2406 b_skipout = skip;
2407}
2408#endif
2409void ChartCanvas::InvalidateGL() {
2410 if (!m_glcc) return;
2411#ifdef ocpnUSE_GL
2412 if (g_bopengl) m_glcc->Invalidate();
2413#endif
2414 if (m_Compass) m_Compass->UpdateStatus(true);
2415}
2416
2417int ChartCanvas::GetCanvasChartNativeScale() {
2418 int ret = 1;
2419 if (!VPoint.b_quilt) {
2420 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2421 } else
2422 ret = (int)m_pQuilt->GetRefNativeScale();
2423
2424 return ret;
2425}
2426
2427ChartBase *ChartCanvas::GetChartAtCursor() {
2428 ChartBase *target_chart;
2429 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2430 target_chart = m_singleChart;
2431 else if (VPoint.b_quilt)
2432 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2433 else
2434 target_chart = NULL;
2435 return target_chart;
2436}
2437
2438ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2439 ChartBase *target_chart;
2440 if (VPoint.b_quilt)
2441 target_chart =
2442 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2443 else
2444 target_chart = NULL;
2445 return target_chart;
2446}
2447
2448int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2449 int new_dbIndex = -1;
2450 if (!VPoint.b_quilt) {
2451 if (m_pCurrentStack) {
2452 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2453 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2454 if (sc >= scale) {
2455 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2456 break;
2457 }
2458 }
2459 }
2460 } else {
2461 // Using the current quilt, select a useable reference chart
2462 // Said chart will be in the extended (possibly full-screen) stack,
2463 // And will have a scale equal to or just greater than the stipulated
2464 // value
2465 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2466 if (im > 0) {
2467 for (unsigned int is = 0; is < im; is++) {
2468 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2469 m_pQuilt->GetExtendedStackIndexArray()[is]);
2470 if ((m.Scale_ge(
2471 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2472 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2473 break;
2474 }
2475 }
2476 }
2477 }
2478
2479 return new_dbIndex;
2480}
2481
2482void ChartCanvas::EnablePaint(bool b_enable) {
2483 m_b_paint_enable = b_enable;
2484#ifdef ocpnUSE_GL
2485 if (m_glcc) m_glcc->EnablePaint(b_enable);
2486#endif
2487}
2488
2489bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2490
2491void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2492
2493std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2494 return m_pQuilt->GetQuiltIndexArray();
2495 ;
2496}
2497
2498void ChartCanvas::SetQuiltMode(bool b_quilt) {
2499 VPoint.b_quilt = b_quilt;
2500 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2501}
2502
2503bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2504
2505int ChartCanvas::GetQuiltReferenceChartIndex() {
2506 return m_pQuilt->GetRefChartdbIndex();
2507}
2508
2509void ChartCanvas::InvalidateAllQuiltPatchs() {
2510 m_pQuilt->InvalidateAllQuiltPatchs();
2511}
2512
2513ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2514 return m_pQuilt->GetLargestScaleChart();
2515}
2516
2517ChartBase *ChartCanvas::GetFirstQuiltChart() {
2518 return m_pQuilt->GetFirstChart();
2519}
2520
2521ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2522
2523int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2524
2525void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2526 m_pQuilt->SetHiliteIndex(dbIndex);
2527}
2528
2529void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2530 m_pQuilt->SetHiliteIndexArray(hilite_array);
2531}
2532
2533void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2534 m_pQuilt->ClearHiliteIndexArray();
2535}
2536
2537std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2538 bool flag2) {
2539 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2540}
2541
2542int ChartCanvas::GetQuiltRefChartdbIndex() {
2543 return m_pQuilt->GetRefChartdbIndex();
2544}
2545
2546std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2547 return m_pQuilt->GetExtendedStackIndexArray();
2548}
2549
2550std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2551 return m_pQuilt->GetFullscreenIndexArray();
2552}
2553
2554std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2555 return m_pQuilt->GetEclipsedStackIndexArray();
2556}
2557
2558void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2559
2560double ChartCanvas::GetQuiltMaxErrorFactor() {
2561 return m_pQuilt->GetMaxErrorFactor();
2562}
2563
2564bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2565 return m_pQuilt->IsChartQuiltableRef(db_index);
2566}
2567
2568bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2569 double chartMaxScale =
2570 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2571 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2572}
2573
2574void ChartCanvas::StartMeasureRoute() {
2575 if (!m_routeState) { // no measure tool if currently creating route
2576 if (m_bMeasure_Active) {
2577 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2578 m_pMeasureRoute = NULL;
2579 }
2580
2581 m_bMeasure_Active = true;
2582 m_nMeasureState = 1;
2583 m_bDrawingRoute = false;
2584
2585 SetCursor(*pCursorPencil);
2586 Refresh();
2587 }
2588}
2589
2590void ChartCanvas::CancelMeasureRoute() {
2591 m_bMeasure_Active = false;
2592 m_nMeasureState = 0;
2593 m_bDrawingRoute = false;
2594
2595 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2596 m_pMeasureRoute = NULL;
2597
2598 SetCursor(*pCursorArrow);
2599}
2600
2601ViewPort &ChartCanvas::GetVP() { return VPoint; }
2602
2603void ChartCanvas::SetVP(ViewPort &vp) {
2604 VPoint = vp;
2605 VPoint.SetPixelScale(m_displayScale);
2606}
2607
2608// void ChartCanvas::SetFocus()
2609// {
2610// printf("set %d\n", m_canvasIndex);
2611// //wxWindow:SetFocus();
2612// }
2613
2614void ChartCanvas::TriggerDeferredFocus() {
2615 // #if defined(__WXGTK__) || defined(__WXOSX__)
2616
2617 m_deferredFocusTimer.Start(20, true);
2618
2619#if defined(__WXGTK__) || defined(__WXOSX__)
2620 top_frame::Get()->Raise();
2621#endif
2622
2623 // top_frame::Get()->Raise();
2624 // #else
2625 // SetFocus();
2626 // Refresh(true);
2627 // #endif
2628}
2629
2630void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2631 SetFocus();
2632 Refresh(true);
2633}
2634
2635void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2636 if (SendKeyEventToPlugins(event))
2637 return; // PlugIn did something, and does not want the canvas to do
2638 // anything else
2639
2640 int key_char = event.GetKeyCode();
2641 switch (key_char) {
2642 case '?':
2643 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2644 break;
2645 case '+':
2646 ZoomCanvas(g_plus_minus_zoom_factor, false);
2647 break;
2648 case '-':
2649 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2650 break;
2651 default:
2652 break;
2653 }
2654 if (g_benable_rotate) {
2655 switch (key_char) {
2656 case ']':
2657 RotateCanvas(1);
2658 Refresh();
2659 break;
2660
2661 case '[':
2662 RotateCanvas(-1);
2663 Refresh();
2664 break;
2665
2666 case '\\':
2667 DoRotateCanvas(0);
2668 break;
2669 }
2670 }
2671
2672 event.Skip();
2673}
2674
2675void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2676 if (SendKeyEventToPlugins(event))
2677 return; // PlugIn did something, and does not want the canvas to do
2678 // anything else
2679
2680 bool b_handled = false;
2681
2682 m_modkeys = event.GetModifiers();
2683
2684 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2685
2686#ifdef OCPN_ALT_MENUBAR
2687#ifndef __WXOSX__
2688 // If the permanent menubar is disabled, we show it temporarily when Alt is
2689 // pressed or when Alt + a letter is presssed (for the top-menu-level
2690 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2691 // some special cases.
2692 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2693 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2694 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2695 if (!g_bTempShowMenuBar) {
2696 g_bTempShowMenuBar = true;
2697 top_frame::Get()->ApplyGlobalSettings(false);
2698 }
2699 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2700 event.Skip();
2701 return;
2702 }
2703 // If another key is pressed while Alt is down, do NOT toggle the menus when
2704 // Alt is released
2705 if (event.GetKeyCode() != WXK_ALT) {
2706 m_bMayToggleMenuBar = false;
2707 }
2708 }
2709#endif
2710#endif
2711
2712 // HOTKEYS
2713 switch (event.GetKeyCode()) {
2714 case WXK_TAB:
2715 // parent_frame->SwitchKBFocus( this );
2716 break;
2717
2718 case WXK_MENU:
2719 int x, y;
2720 event.GetPosition(&x, &y);
2721 m_FinishRouteOnKillFocus = false;
2722 CallPopupMenu(x, y);
2723 m_FinishRouteOnKillFocus = true;
2724 break;
2725
2726 case WXK_ALT:
2727 m_modkeys |= wxMOD_ALT;
2728 break;
2729
2730 case WXK_CONTROL:
2731 m_modkeys |= wxMOD_CONTROL;
2732 break;
2733
2734#ifdef __WXOSX__
2735 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2736 case WXK_RAW_CONTROL:
2737 m_modkeys |= wxMOD_RAW_CONTROL;
2738 break;
2739#endif
2740
2741 case WXK_LEFT:
2742 if (m_modkeys == wxMOD_CONTROL)
2743 top_frame::Get()->DoStackDown(this);
2744 else if (g_bsmoothpanzoom) {
2745 StartTimedMovement();
2746 m_panx = -1;
2747 } else {
2748 PanCanvas(-panspeed, 0);
2749 }
2750 b_handled = true;
2751 break;
2752
2753 case WXK_UP:
2754 if (g_bsmoothpanzoom) {
2755 StartTimedMovement();
2756 m_pany = -1;
2757 } else
2758 PanCanvas(0, -panspeed);
2759 b_handled = true;
2760 break;
2761
2762 case WXK_RIGHT:
2763 if (m_modkeys == wxMOD_CONTROL)
2764 top_frame::Get()->DoStackUp(this);
2765 else if (g_bsmoothpanzoom) {
2766 StartTimedMovement();
2767 m_panx = 1;
2768 } else
2769 PanCanvas(panspeed, 0);
2770 b_handled = true;
2771
2772 break;
2773
2774 case WXK_DOWN:
2775 if (g_bsmoothpanzoom) {
2776 StartTimedMovement();
2777 m_pany = 1;
2778 } else
2779 PanCanvas(0, panspeed);
2780 b_handled = true;
2781 break;
2782
2783 case WXK_F2: {
2784 if (event.ShiftDown()) {
2785 double scale = GetVP().view_scale_ppm;
2786 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2787
2788 std::shared_ptr<HostApi> host_api;
2789 host_api = GetHostApi();
2790 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2791
2792 if (api_121)
2793 api_121->SelectChartFamily(m_canvasIndex,
2794 (ChartFamilyEnumPI)target_family);
2795 } else
2796 TogglebFollow();
2797 break;
2798 }
2799 case WXK_F3: {
2800 if (event.ShiftDown()) {
2801 double scale = GetVP().view_scale_ppm;
2802 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2803
2804 std::shared_ptr<HostApi> host_api;
2805 host_api = GetHostApi();
2806 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2807
2808 if (api_121)
2809 api_121->SelectChartFamily(m_canvasIndex,
2810 (ChartFamilyEnumPI)target_family);
2811 } else {
2812 SetShowENCText(!GetShowENCText());
2813 Refresh(true);
2814 InvalidateGL();
2815 }
2816 break;
2817 }
2818 case WXK_F4:
2819 if (!m_bMeasure_Active) {
2820 if (event.ShiftDown())
2821 m_bMeasure_DistCircle = true;
2822 else
2823 m_bMeasure_DistCircle = false;
2824
2825 StartMeasureRoute();
2826 } else {
2827 CancelMeasureRoute();
2828
2829 SetCursor(*pCursorArrow);
2830
2831 // SurfaceToolbar();
2832 InvalidateGL();
2833 Refresh(false);
2834 }
2835
2836 break;
2837
2838 case WXK_F5:
2839 top_frame::Get()->ToggleColorScheme();
2840 top_frame::Get()->Raise();
2841 TriggerDeferredFocus();
2842 break;
2843
2844 case WXK_F6: {
2845 int mod = m_modkeys & wxMOD_SHIFT;
2846 if (mod != m_brightmod) {
2847 m_brightmod = mod;
2848 m_bbrightdir = !m_bbrightdir;
2849 }
2850
2851 if (!m_bbrightdir) {
2852 g_nbrightness -= 10;
2853 if (g_nbrightness <= MIN_BRIGHT) {
2854 g_nbrightness = MIN_BRIGHT;
2855 m_bbrightdir = true;
2856 }
2857 } else {
2858 g_nbrightness += 10;
2859 if (g_nbrightness >= MAX_BRIGHT) {
2860 g_nbrightness = MAX_BRIGHT;
2861 m_bbrightdir = false;
2862 }
2863 }
2864
2865 SetScreenBrightness(g_nbrightness);
2866 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2867
2868 SetFocus(); // just in case the external program steals it....
2869 top_frame::Get()->Raise(); // And reactivate the application main
2870
2871 break;
2872 }
2873
2874 case WXK_F7:
2875 top_frame::Get()->DoStackDown(this);
2876 break;
2877
2878 case WXK_F8:
2879 top_frame::Get()->DoStackUp(this);
2880 break;
2881
2882#ifndef __WXOSX__
2883 case WXK_F9: {
2884 ToggleCanvasQuiltMode();
2885 break;
2886 }
2887#endif
2888
2889 case WXK_F11:
2890 top_frame::Get()->ToggleFullScreen();
2891 b_handled = true;
2892 break;
2893
2894 case WXK_F12: {
2895 if (m_modkeys == wxMOD_ALT) {
2896 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2897 } else {
2898 ToggleChartOutlines();
2899 }
2900 break;
2901 }
2902
2903 case WXK_PAUSE: // Drop MOB
2904 top_frame::Get()->ActivateMOB();
2905 break;
2906
2907 // NUMERIC PAD
2908 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2909 case WXK_PAGEUP: {
2910 ZoomCanvas(g_plus_minus_zoom_factor, false);
2911 break;
2912 }
2913 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2914 case WXK_PAGEDOWN: {
2915 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2916 break;
2917 }
2918 case WXK_DELETE:
2919 case WXK_BACK:
2920 if (m_bMeasure_Active) {
2921 if (m_nMeasureState > 2) {
2922 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2923 m_pMeasureRoute->m_lastMousePointIndex =
2924 m_pMeasureRoute->GetnPoints();
2925 m_nMeasureState--;
2926 top_frame::Get()->RefreshAllCanvas();
2927 } else {
2928 CancelMeasureRoute();
2929 StartMeasureRoute();
2930 }
2931 }
2932 break;
2933 default:
2934 break;
2935 }
2936
2937 if (event.GetKeyCode() < 128) // ascii
2938 {
2939 int key_char = event.GetKeyCode();
2940
2941 // Handle both QWERTY and AZERTY keyboard separately for a few control
2942 // codes
2943 if (!g_b_assume_azerty) {
2944#ifdef __WXMAC__
2945 if (g_benable_rotate) {
2946 switch (key_char) {
2947 // On other platforms these are handled in OnKeyChar, which
2948 // (apparently) works better in some locales. On OS X it is better
2949 // to handle them here, since pressing Alt (which should change the
2950 // rotation speed) changes the key char and so prevents the keys
2951 // from working.
2952 case ']':
2953 RotateCanvas(1);
2954 b_handled = true;
2955 break;
2956
2957 case '[':
2958 RotateCanvas(-1);
2959 b_handled = true;
2960 break;
2961
2962 case '\\':
2963 DoRotateCanvas(0);
2964 b_handled = true;
2965 break;
2966 }
2967 }
2968#endif
2969 } else { // AZERTY
2970 switch (key_char) {
2971 case 43:
2972 ZoomCanvas(g_plus_minus_zoom_factor, false);
2973 break;
2974
2975 case 54: // '-' alpha/num pad
2976 // case 56: // '_' alpha/num pad
2977 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2978 break;
2979 }
2980 }
2981
2982#ifdef __WXOSX__
2983 // Ctrl+Cmd+F toggles fullscreen on macOS
2984 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2985 m_modkeys & wxMOD_RAW_CONTROL) {
2986 top_frame::Get()->ToggleFullScreen();
2987 return;
2988 }
2989#endif
2990
2991 if (event.ControlDown()) key_char -= 64;
2992
2993 if (key_char >= '0' && key_char <= '9')
2994 SetGroupIndex(key_char - '0');
2995 else
2996
2997 switch (key_char) {
2998 case 'A':
2999 SetShowENCAnchor(!GetShowENCAnchor());
3000 ReloadVP();
3001
3002 break;
3003
3004 case 'C':
3005 top_frame::Get()->ToggleColorScheme();
3006 break;
3007
3008 case 'D': {
3009 int x, y;
3010 event.GetPosition(&x, &y);
3011 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3012 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3013 // First find out what kind of chart is being used
3014 if (!pPopupDetailSlider) {
3015 if (VPoint.b_quilt) {
3016 if (m_pQuilt) {
3017 if (m_pQuilt->GetChartAtPix(
3018 VPoint,
3019 wxPoint(
3020 x, y))) // = null if no chart loaded for this point
3021 {
3022 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3023 ->GetChartType();
3024 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3025 ->GetChartFamily();
3026 }
3027 }
3028 } else {
3029 if (m_singleChart) {
3030 ChartType = m_singleChart->GetChartType();
3031 ChartFam = m_singleChart->GetChartFamily();
3032 }
3033 }
3034 // If a charttype is found show the popupslider
3035 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3036 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3038 this, -1, ChartType, ChartFam,
3039 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3040 wxDefaultSize, wxSIMPLE_BORDER, "");
3042 }
3043 } else //( !pPopupDetailSlider ) close popupslider
3044 {
3046 pPopupDetailSlider = NULL;
3047 }
3048 break;
3049 }
3050
3051 case 'E':
3052 m_nmea_log->Show();
3053 m_nmea_log->Raise();
3054 break;
3055
3056 case 'L':
3057 SetShowENCLights(!GetShowENCLights());
3058 ReloadVP();
3059
3060 break;
3061
3062 case 'M':
3063 if (event.ShiftDown())
3064 m_bMeasure_DistCircle = true;
3065 else
3066 m_bMeasure_DistCircle = false;
3067
3068 StartMeasureRoute();
3069 break;
3070
3071 case 'N':
3072 if (g_bInlandEcdis && ps52plib) {
3073 SetENCDisplayCategory((_DisCat)STANDARD);
3074 }
3075 break;
3076
3077 case 'O':
3078 ToggleChartOutlines();
3079 break;
3080
3081 case 'Q':
3082 ToggleCanvasQuiltMode();
3083 break;
3084
3085 case 'P':
3086 top_frame::Get()->ToggleTestPause();
3087 break;
3088 case 'R':
3089 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3090 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3091 g_iNavAidRadarRingsNumberVisible = 1;
3092 else if (!g_bNavAidRadarRingsShown &&
3093 g_iNavAidRadarRingsNumberVisible == 1)
3094 g_iNavAidRadarRingsNumberVisible = 0;
3095 break;
3096 case 'S':
3097 SetShowENCDepth(!m_encShowDepth);
3098 ReloadVP();
3099 break;
3100
3101 case 'T':
3102 SetShowENCText(!GetShowENCText());
3103 ReloadVP();
3104 break;
3105
3106 case 'U':
3107 SetShowENCDataQual(!GetShowENCDataQual());
3108 ReloadVP();
3109 break;
3110
3111 case 'V':
3112 m_bShowNavobjects = !m_bShowNavobjects;
3113 Refresh(true);
3114 break;
3115
3116 case 'W': // W Toggle CPA alarm
3117 ToggleCPAWarn();
3118
3119 break;
3120
3121 case 1: // Ctrl A
3122 TogglebFollow();
3123
3124 break;
3125
3126 case 2: // Ctrl B
3127 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3128 break;
3129
3130 case 13: // Ctrl M // Drop Marker at cursor
3131 {
3132 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3133 break;
3134 }
3135
3136 case 14: // Ctrl N - Activate next waypoint in a route
3137 {
3138 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3139 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3140 if ((indexActive + 1) <= r->GetnPoints()) {
3142 InvalidateGL();
3143 Refresh(false);
3144 }
3145 }
3146 break;
3147 }
3148
3149 case 15: // Ctrl O - Drop Marker at boat's position
3150 {
3151 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3152 break;
3153 }
3154
3155 case 32: // Special needs use space bar
3156 {
3157 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3158 break;
3159 }
3160
3161 case -32: // Ctrl Space // Drop MOB
3162 {
3163 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3164
3165 break;
3166 }
3167
3168 case -20: // Ctrl ,
3169 {
3170 top_frame::Get()->DoSettings();
3171 break;
3172 }
3173 case 17: // Ctrl Q
3174 parent_frame->Close();
3175 return;
3176
3177 case 18: // Ctrl R
3178 StartRoute();
3179 return;
3180
3181 case 20: // Ctrl T
3182 if (NULL == pGoToPositionDialog) // There is one global instance of
3183 // the Go To Position Dialog
3185 pGoToPositionDialog->SetCanvas(this);
3186 pGoToPositionDialog->Show();
3187 break;
3188
3189 case 25: // Ctrl Y
3190 if (undo->AnythingToRedo()) {
3191 undo->RedoNextAction();
3192 InvalidateGL();
3193 Refresh(false);
3194 }
3195 break;
3196
3197 case 26:
3198 if (event.ShiftDown()) { // Shift-Ctrl-Z
3199 if (undo->AnythingToRedo()) {
3200 undo->RedoNextAction();
3201 InvalidateGL();
3202 Refresh(false);
3203 }
3204 } else { // Ctrl Z
3205 if (undo->AnythingToUndo()) {
3206 undo->UndoLastAction();
3207 InvalidateGL();
3208 Refresh(false);
3209 }
3210 }
3211 break;
3212
3213 case 27:
3214 // Generic break
3215 if (m_bMeasure_Active) {
3216 CancelMeasureRoute();
3217
3218 SetCursor(*pCursorArrow);
3219
3220 // SurfaceToolbar();
3221 top_frame::Get()->RefreshAllCanvas();
3222 }
3223
3224 if (m_routeState) // creating route?
3225 {
3226 FinishRoute();
3227 // SurfaceToolbar();
3228 InvalidateGL();
3229 Refresh(false);
3230 }
3231
3232 break;
3233
3234 case 7: // Ctrl G
3235 switch (gamma_state) {
3236 case (0):
3237 r_gamma_mult = 0;
3238 g_gamma_mult = 1;
3239 b_gamma_mult = 0;
3240 gamma_state = 1;
3241 break;
3242 case (1):
3243 r_gamma_mult = 1;
3244 g_gamma_mult = 0;
3245 b_gamma_mult = 0;
3246 gamma_state = 2;
3247 break;
3248 case (2):
3249 r_gamma_mult = 1;
3250 g_gamma_mult = 1;
3251 b_gamma_mult = 1;
3252 gamma_state = 0;
3253 break;
3254 }
3255 SetScreenBrightness(g_nbrightness);
3256
3257 break;
3258
3259 case 9: // Ctrl I
3260 if (event.ControlDown()) {
3261 m_bShowCompassWin = !m_bShowCompassWin;
3262 SetShowGPSCompassWindow(m_bShowCompassWin);
3263 Refresh(false);
3264 }
3265 break;
3266
3267 default:
3268 break;
3269
3270 } // switch
3271 }
3272
3273 // Allow OnKeyChar to catch the key events too.
3274 if (!b_handled) {
3275 event.Skip();
3276 }
3277}
3278
3279void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3280 if (SendKeyEventToPlugins(event))
3281 return; // PlugIn did something, and does not want the canvas to do
3282 // anything else
3283
3284 switch (event.GetKeyCode()) {
3285 case WXK_TAB:
3286 top_frame::Get()->SwitchKBFocus(this);
3287 break;
3288
3289 case WXK_LEFT:
3290 case WXK_RIGHT:
3291 m_panx = 0;
3292 if (!m_pany) m_panspeed = 0;
3293 break;
3294
3295 case WXK_UP:
3296 case WXK_DOWN:
3297 m_pany = 0;
3298 if (!m_panx) m_panspeed = 0;
3299 break;
3300
3301 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3302 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3303 case WXK_PAGEUP:
3304 case WXK_PAGEDOWN:
3305 if (m_mustmove) DoMovement(m_mustmove);
3306
3307 m_zoom_factor = 1;
3308 break;
3309
3310 case WXK_ALT:
3311 m_modkeys &= ~wxMOD_ALT;
3312#ifdef OCPN_ALT_MENUBAR
3313#ifndef __WXOSX__
3314 // If the permanent menu bar is disabled, and we are not in the middle of
3315 // another key combo, then show the menu bar temporarily when Alt is
3316 // released (or hide it if already visible).
3317 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3318 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3319 top_frame::Get()->ApplyGlobalSettings(false);
3320 }
3321 m_bMayToggleMenuBar = true;
3322#endif
3323#endif
3324 break;
3325
3326 case WXK_CONTROL:
3327 m_modkeys &= ~wxMOD_CONTROL;
3328 break;
3329 }
3330
3331 if (event.GetKeyCode() < 128) // ascii
3332 {
3333 int key_char = event.GetKeyCode();
3334
3335 // Handle both QWERTY and AZERTY keyboard separately for a few control
3336 // codes
3337 if (!g_b_assume_azerty) {
3338 switch (key_char) {
3339 case '+':
3340 case '=':
3341 case '-':
3342 case '_':
3343 case 54:
3344 case 56: // '_' alpha/num pad
3345 DoMovement(m_mustmove);
3346
3347 // m_zoom_factor = 1;
3348 break;
3349 case '[':
3350 case ']':
3351 DoMovement(m_mustmove);
3352 m_rotation_speed = 0;
3353 break;
3354 }
3355 } else {
3356 switch (key_char) {
3357 case 43:
3358 case 54: // '-' alpha/num pad
3359 case 56: // '_' alpha/num pad
3360 DoMovement(m_mustmove);
3361
3362 m_zoom_factor = 1;
3363 break;
3364 }
3365 }
3366 }
3367 event.Skip();
3368}
3369
3370void ChartCanvas::ToggleChartOutlines() {
3371 m_bShowOutlines = !m_bShowOutlines;
3372
3373 Refresh(false);
3374
3375#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3376 // needs a full refresh
3377 if (g_bopengl) InvalidateGL();
3378#endif
3379}
3380
3381void ChartCanvas::ToggleLookahead() {
3382 m_bLookAhead = !m_bLookAhead;
3383 m_OSoffsetx = 0; // center ownship
3384 m_OSoffsety = 0;
3385}
3386
3387void ChartCanvas::SetUpMode(int mode) {
3388 m_upMode = mode;
3389
3390 if (mode != NORTH_UP_MODE) {
3391 // Stuff the COGAvg table in case COGUp is selected
3392 double stuff = 0;
3393 if (!std::isnan(gCog)) stuff = gCog;
3394
3395 if (g_COGAvgSec > 0) {
3396 auto cog_table = top_frame::Get()->GetCOGTable();
3397 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3398 }
3399 g_COGAvg = stuff;
3400 top_frame::Get()->StartCogTimer();
3401 } else {
3402 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3403 SetVPRotation(GetVPSkew());
3404 else
3405 SetVPRotation(0); /* reset to north up */
3406 }
3407
3408 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3409 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3410
3411 UpdateGPSCompassStatusBox(true);
3412 top_frame::Get()->DoChartUpdate();
3413}
3414
3415bool ChartCanvas::DoCanvasCOGSet() {
3416 if (GetUpMode() == NORTH_UP_MODE) return false;
3417 double cog_use = g_COGAvg;
3418 if (g_btenhertz) cog_use = gCog;
3419
3420 double rotation = 0;
3421 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3422 rotation = -gHdt * PI / 180.;
3423 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3424 rotation = -cog_use * PI / 180.;
3425
3426 SetVPRotation(rotation);
3427 return true;
3428}
3429
3430double easeOutCubic(double t) {
3431 // Starts quickly and slows down toward the end
3432 return 1.0 - pow(1.0 - t, 3.0);
3433}
3434
3435void ChartCanvas::StartChartDragInertia() {
3436 m_bChartDragging = false;
3437
3438 // Set some parameters
3439 m_chart_drag_inertia_time = 750; // msec
3440 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3441 m_last_elapsed = 0;
3442
3443 // Calculate ending drag velocity
3444 size_t n_vel = 10;
3445 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3446 int xacc = 0;
3447 int yacc = 0;
3448 double tacc = 0;
3449 size_t length = m_drag_vec_t.size();
3450 for (size_t i = 0; i < n_vel; i++) {
3451 xacc += m_drag_vec_x.at(length - 1 - i);
3452 yacc += m_drag_vec_y.at(length - 1 - i);
3453 tacc += m_drag_vec_t.at(length - 1 - i);
3454 }
3455
3456 if (tacc == 0) return;
3457
3458 double drag_velocity_x = xacc / tacc;
3459 double drag_velocity_y = yacc / tacc;
3460 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3461 // drag_velocity_y);
3462
3463 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3464 // touch tap.
3465 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3466
3467 m_chart_drag_velocity_x = drag_velocity_x;
3468 m_chart_drag_velocity_y = drag_velocity_y;
3469
3470 m_chart_drag_inertia_active = true;
3471 // First callback as fast as possible.
3472 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3473}
3474
3475void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3476 if (!m_chart_drag_inertia_active) return;
3477 // Calculate time fraction from 0..1
3478 wxLongLong now = wxGetLocalTimeMillis();
3479 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3480 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3481 if (t > 1.0) t = 1.0;
3482 double e = 1.0 - easeOutCubic(t); // 0..1
3483
3484 double dx =
3485 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3486 double dy =
3487 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3488
3489 m_last_elapsed = elapsed;
3490
3491 // Ensure that target destination lies on whole-pixel boundary
3492 // This allows the render engine to use a faster FBO copy method for drawing
3493 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3494 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3495 double inertia_lat, inertia_lon;
3496 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3497 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3498 // Check if ownship has moved off-screen
3499 if (!IsOwnshipOnScreen()) {
3500 m_bFollow = false; // update the follow flag
3501 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3502 UpdateFollowButtonState();
3503 m_OSoffsetx = 0;
3504 m_OSoffsety = 0;
3505 } else {
3506 m_OSoffsetx += dx;
3507 m_OSoffsety -= dy;
3508 }
3509
3510 Refresh(false);
3511
3512 // Stop condition
3513 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3514 m_chart_drag_inertia_timer.Stop();
3515
3516 // Disable chart pan movement logic
3517 m_target_lat = GetVP().clat;
3518 m_target_lon = GetVP().clon;
3519 m_pan_drag.x = m_pan_drag.y = 0;
3520 m_panx = m_pany = 0;
3521 m_chart_drag_inertia_active = false;
3522 DoCanvasUpdate();
3523
3524 } else {
3525 int target_redraw_interval = 40; // msec
3526 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3527 }
3528}
3529
3530void ChartCanvas::StopMovement() {
3531 m_panx = m_pany = 0;
3532 m_panspeed = 0;
3533 m_zoom_factor = 1;
3534 m_rotation_speed = 0;
3535 m_mustmove = 0;
3536#if 0
3537#if !defined(__WXGTK__) && !defined(__WXQT__)
3538 SetFocus();
3539 top_frame::Get()->Raise();
3540#endif
3541#endif
3542}
3543
3544/* instead of integrating in timer callbacks
3545 (which do not always get called fast enough)
3546 we can perform the integration of movement
3547 at each render frame based on the time change */
3548bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3549 // Start/restart the stop movement timer
3550 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3551
3552 if (!pMovementTimer->IsRunning()) {
3553 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3554 }
3555
3556 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3557 // already moving, gets called again because of key-repeat event
3558 return false;
3559 }
3560
3561 m_last_movement_time = wxDateTime::UNow();
3562
3563 return true;
3564}
3565void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3566 int nstep) {
3567 // Save the target
3568 m_target_lat = target_lat;
3569 m_target_lon = target_lon;
3570
3571 // Save the start point
3572 m_start_lat = GetVP().clat;
3573 m_start_lon = GetVP().clon;
3574
3575 m_VPMovementTimer.Start(1, true); // oneshot
3576 m_timed_move_vp_active = true;
3577 m_stvpc = 0;
3578 m_timedVP_step = nstep;
3579}
3580
3581void ChartCanvas::DoTimedMovementVP() {
3582 if (!m_timed_move_vp_active) return; // not active
3583 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3584 StopMovement();
3585 return;
3586 }
3587 // Stop condition
3588 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3589 double d2 =
3590 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3591 d2 = pow(d2, 0.5);
3592
3593 if (d2 < one_pix) {
3594 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3595 StopMovementVP();
3596 return;
3597 }
3598
3599 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3600 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3601 // StopMovementVP();
3602 // return;
3603 // }
3604
3605 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3606 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3607
3608 m_run_lat = new_lat;
3609 m_run_lon = new_lon;
3610
3611 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3612}
3613
3614void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3615
3616void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3617
3618void ChartCanvas::StartTimedMovementTarget() {}
3619
3620void ChartCanvas::DoTimedMovementTarget() {}
3621
3622void ChartCanvas::StopMovementTarget() {}
3623int ntm;
3624
3625void ChartCanvas::DoTimedMovement() {
3626 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3627 !m_rotation_speed)
3628 return; /* not moving */
3629
3630 wxDateTime now = wxDateTime::UNow();
3631 long dt = 0;
3632 if (m_last_movement_time.IsValid())
3633 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3634
3635 m_last_movement_time = now;
3636
3637 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3638 dt = 500;
3639
3640 DoMovement(dt);
3641}
3642
3644 /* if we get here quickly assume 1ms so that some movement occurs */
3645 if (dt == 0) dt = 1;
3646
3647 m_mustmove -= dt;
3648 if (m_mustmove < 0) m_mustmove = 0;
3649
3650 if (!m_inPinch) { // this stops compound zoom/pan
3651 if (m_pan_drag.x || m_pan_drag.y) {
3652 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3653 m_pan_drag.x = m_pan_drag.y = 0;
3654 }
3655
3656 if (m_panx || m_pany) {
3657 const double slowpan = .1, maxpan = 2;
3658 if (m_modkeys == wxMOD_ALT)
3659 m_panspeed = slowpan;
3660 else {
3661 m_panspeed += (double)dt / 500; /* apply acceleration */
3662 m_panspeed = wxMin(maxpan, m_panspeed);
3663 }
3664 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3665 }
3666 }
3667 if (m_zoom_factor != 1) {
3668 double alpha = 400, beta = 1.5;
3669 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3670
3671 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3672
3673 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3674
3675 // Try to hit the zoom target exactly.
3676 // if(m_wheelzoom_stop_oneshot > 0)
3677 {
3678 if (zoom_factor > 1) {
3679 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3680 zoom_factor = VPoint.chart_scale / m_zoom_target;
3681 }
3682
3683 else if (zoom_factor < 1) {
3684 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3685 zoom_factor = VPoint.chart_scale / m_zoom_target;
3686 }
3687 }
3688
3689 if (fabs(zoom_factor - 1) > 1e-4) {
3690 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3691 } else {
3692 StopMovement();
3693 }
3694
3695 if (m_wheelzoom_stop_oneshot > 0) {
3696 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3697 m_wheelzoom_stop_oneshot = 0;
3698 StopMovement();
3699 }
3700
3701 // Don't overshoot the zoom target.
3702 if (zoom_factor > 1) {
3703 if (VPoint.chart_scale <= m_zoom_target) {
3704 m_wheelzoom_stop_oneshot = 0;
3705 StopMovement();
3706 }
3707 } else if (zoom_factor < 1) {
3708 if (VPoint.chart_scale >= m_zoom_target) {
3709 m_wheelzoom_stop_oneshot = 0;
3710 StopMovement();
3711 }
3712 }
3713 }
3714 }
3715
3716 if (m_rotation_speed) { /* in degrees per second */
3717 double speed = m_rotation_speed;
3718 if (m_modkeys == wxMOD_ALT) speed /= 10;
3719 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3720 }
3721}
3722
3723void ChartCanvas::SetColorScheme(ColorScheme cs) {
3724 SetAlertString("");
3725
3726 // Setup ownship image pointers
3727 switch (cs) {
3728 case GLOBAL_COLOR_SCHEME_DAY:
3729 m_pos_image_red = &m_os_image_red_day;
3730 m_pos_image_grey = &m_os_image_grey_day;
3731 m_pos_image_yellow = &m_os_image_yellow_day;
3732 m_pos_image_user = m_pos_image_user_day;
3733 m_pos_image_user_grey = m_pos_image_user_grey_day;
3734 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3735 m_cTideBitmap = m_bmTideDay;
3736 m_cCurrentBitmap = m_bmCurrentDay;
3737
3738 break;
3739 case GLOBAL_COLOR_SCHEME_DUSK:
3740 m_pos_image_red = &m_os_image_red_dusk;
3741 m_pos_image_grey = &m_os_image_grey_dusk;
3742 m_pos_image_yellow = &m_os_image_yellow_dusk;
3743 m_pos_image_user = m_pos_image_user_dusk;
3744 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3745 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3746 m_cTideBitmap = m_bmTideDusk;
3747 m_cCurrentBitmap = m_bmCurrentDusk;
3748 break;
3749 case GLOBAL_COLOR_SCHEME_NIGHT:
3750 m_pos_image_red = &m_os_image_red_night;
3751 m_pos_image_grey = &m_os_image_grey_night;
3752 m_pos_image_yellow = &m_os_image_yellow_night;
3753 m_pos_image_user = m_pos_image_user_night;
3754 m_pos_image_user_grey = m_pos_image_user_grey_night;
3755 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3756 m_cTideBitmap = m_bmTideNight;
3757 m_cCurrentBitmap = m_bmCurrentNight;
3758 break;
3759 default:
3760 m_pos_image_red = &m_os_image_red_day;
3761 m_pos_image_grey = &m_os_image_grey_day;
3762 m_pos_image_yellow = &m_os_image_yellow_day;
3763 m_pos_image_user = m_pos_image_user_day;
3764 m_pos_image_user_grey = m_pos_image_user_grey_day;
3765 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3766 m_cTideBitmap = m_bmTideDay;
3767 m_cCurrentBitmap = m_bmCurrentDay;
3768 break;
3769 }
3770
3771 CreateDepthUnitEmbossMaps(cs);
3772 CreateOZEmbossMapData(cs);
3773
3774 // Set up fog effect base color
3775 m_fog_color = wxColor(
3776 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3777 float dim = 1.0;
3778 switch (cs) {
3779 case GLOBAL_COLOR_SCHEME_DUSK:
3780 dim = 0.5;
3781 break;
3782 case GLOBAL_COLOR_SCHEME_NIGHT:
3783 dim = 0.25;
3784 break;
3785 default:
3786 break;
3787 }
3788 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3789 m_fog_color.Blue() * dim);
3790
3791 // Really dark
3792#if 0
3793 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3794 SetBackgroundColour( wxColour(0,0,0) );
3795
3796 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3797 }
3798 else{
3799 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3800#ifndef __WXMAC__
3801 SetBackgroundColour( wxNullColour );
3802#endif
3803 }
3804#endif
3805
3806 // UpdateToolbarColorScheme(cs);
3807
3808 m_Piano->SetColorScheme(cs);
3809
3810 m_Compass->SetColorScheme(cs);
3811
3812 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3813
3814 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3815
3816 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3817 if (m_notification_button) {
3818 m_notification_button->SetColorScheme(cs);
3819 }
3820
3821#ifdef ocpnUSE_GL
3822 if (g_bopengl && m_glcc) {
3823 m_glcc->SetColorScheme(cs);
3824 g_glTextureManager->ClearAllRasterTextures();
3825 // m_glcc->FlushFBO();
3826 }
3827#endif
3828 SetbTCUpdate(true); // force re-render of tide/current locators
3829 m_brepaint_piano = true;
3830
3831 ReloadVP();
3832
3833 m_cs = cs;
3834}
3835
3836wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3837 wxImage img = Bitmap.ConvertToImage();
3838 int sx = img.GetWidth();
3839 int sy = img.GetHeight();
3840
3841 wxImage new_img(img);
3842
3843 for (int i = 0; i < sx; i++) {
3844 for (int j = 0; j < sy; j++) {
3845 if (!img.IsTransparent(i, j)) {
3846 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3847 (unsigned char)(img.GetGreen(i, j) * factor),
3848 (unsigned char)(img.GetBlue(i, j) * factor));
3849 }
3850 }
3851 }
3852
3853 wxBitmap ret = wxBitmap(new_img);
3854
3855 return ret;
3856}
3857
3858void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3859 int max) {
3860 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3861 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3862
3863 if (!m_pBrightPopup) {
3864 // Calculate size
3865 int x, y;
3866 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3867
3868 m_pBrightPopup = new TimedPopupWin(this, 3);
3869
3870 m_pBrightPopup->SetSize(x, y);
3871 m_pBrightPopup->Move(120, 120);
3872 }
3873
3874 int bmpsx = m_pBrightPopup->GetSize().x;
3875 int bmpsy = m_pBrightPopup->GetSize().y;
3876
3877 wxBitmap bmp(bmpsx, bmpsx);
3878 wxMemoryDC mdc(bmp);
3879
3880 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3881 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3882 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3883 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3884 mdc.Clear();
3885
3886 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3887
3888 mdc.SetFont(*pfont);
3889 wxString val;
3890
3891 if (brightness == max)
3892 val = "MAX";
3893 else if (brightness == min)
3894 val = "MIN";
3895 else
3896 val.Printf("%3d", brightness);
3897
3898 mdc.DrawText(val, 0, 0);
3899
3900 mdc.SelectObject(wxNullBitmap);
3901
3902 m_pBrightPopup->SetBitmap(bmp);
3903 m_pBrightPopup->Show();
3904 m_pBrightPopup->Refresh();
3905}
3906
3907void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3908 m_b_rot_hidef = true;
3909 ReloadVP();
3910}
3911
3912void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3913 if (!g_bRollover) return;
3914
3915 bool b_need_refresh = false;
3916
3917 wxSize win_size = GetSize() * m_displayScale;
3918 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3919
3920 // Handle the AIS Rollover Window first
3921 bool showAISRollover = false;
3922 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3923 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3924 SelectItem *pFind = pSelectAIS->FindSelection(
3925 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3926 if (pFind) {
3927 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3928 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3929
3930 if (ptarget) {
3931 showAISRollover = true;
3932
3933 if (NULL == m_pAISRolloverWin) {
3934 m_pAISRolloverWin = new RolloverWin(this);
3935 m_pAISRolloverWin->IsActive(false);
3936 b_need_refresh = true;
3937 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3938 m_AISRollover_MMSI != FoundAIS_MMSI) {
3939 // Sometimes the mouse moves fast enough to get over a new AIS
3940 // target before the one-shot has fired to remove the old target.
3941 // Result: wrong target data is shown.
3942 // Detect this case,close the existing rollover ASAP, and restart
3943 // the timer.
3944 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3945 m_pAISRolloverWin->IsActive(false);
3946 m_AISRollover_MMSI = 0;
3947 Refresh();
3948 return;
3949 }
3950
3951 m_AISRollover_MMSI = FoundAIS_MMSI;
3952
3953 if (!m_pAISRolloverWin->IsActive()) {
3954 wxString s = ptarget->GetRolloverString();
3955 m_pAISRolloverWin->SetString(s);
3956
3957 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3958 AIS_ROLLOVER, win_size);
3959 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3960 m_pAISRolloverWin->IsActive(true);
3961 b_need_refresh = true;
3962 }
3963 }
3964 } else {
3965 m_AISRollover_MMSI = 0;
3966 showAISRollover = false;
3967 }
3968 }
3969
3970 // Maybe turn the rollover off
3971 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3972 m_pAISRolloverWin->IsActive(false);
3973 m_AISRollover_MMSI = 0;
3974 b_need_refresh = true;
3975 }
3976
3977 // Now the Route info rollover
3978 // Show the route segment info
3979 bool showRouteRollover = false;
3980
3981 if (NULL == m_pRolloverRouteSeg) {
3982 // Get a list of all selectable sgements, and search for the first
3983 // visible segment as the rollover target.
3984
3985 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3986 SelectableItemList SelList = pSelect->FindSelectionList(
3987 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3988 auto node = SelList.begin();
3989 while (node != SelList.end()) {
3990 SelectItem *pFindSel = *node;
3991
3992 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3993
3994 if (pr && pr->IsVisible()) {
3995 m_pRolloverRouteSeg = pFindSel;
3996 showRouteRollover = true;
3997
3998 if (NULL == m_pRouteRolloverWin) {
3999 m_pRouteRolloverWin = new RolloverWin(this, 10);
4000 m_pRouteRolloverWin->IsActive(false);
4001 }
4002
4003 if (!m_pRouteRolloverWin->IsActive()) {
4004 wxString s;
4005 RoutePoint *segShow_point_a =
4006 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4007 RoutePoint *segShow_point_b =
4008 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4009
4010 double brg, dist;
4011 DistanceBearingMercator(
4012 segShow_point_b->m_lat, segShow_point_b->m_lon,
4013 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4014
4015 if (!pr->m_bIsInLayer)
4016 s.Append(_("Route") + ": ");
4017 else
4018 s.Append(_("Layer Route: "));
4019
4020 if (pr->m_RouteNameString.IsEmpty())
4021 s.Append(_("(unnamed)"));
4022 else
4023 s.Append(pr->m_RouteNameString);
4024
4025 s << "\n"
4026 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4027 << "\n"
4028 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4029 << segShow_point_b->GetName() << "\n";
4030
4031 if (g_bShowTrue)
4032 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4033 (int)floor(brg + 0.5), 0x00B0);
4034 if (g_bShowMag) {
4035 double latAverage =
4036 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4037 double lonAverage =
4038 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4039 double varBrg =
4040 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4041
4042 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4043 (int)floor(varBrg + 0.5), 0x00B0);
4044 }
4045
4046 s << FormatDistanceAdaptive(dist);
4047
4048 // Compute and display cumulative distance from route start point to
4049 // current leg end point and RNG,TTG,ETA from ship to current leg end
4050 // point for active route
4051 double shiptoEndLeg = 0.;
4052 bool validActive = false;
4053 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4054 validActive = true;
4055
4056 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4057 auto node = pr->pRoutePointList->begin();
4058 RoutePoint *prp;
4059 float dist_to_endleg = 0;
4060 wxString t;
4061
4062 for (++node; node != pr->pRoutePointList->end(); ++node) {
4063 prp = *node;
4064 if (validActive)
4065 shiptoEndLeg += prp->m_seg_len;
4066 else if (prp->m_bIsActive)
4067 validActive = true;
4068 dist_to_endleg += prp->m_seg_len;
4069 if (prp->IsSame(segShow_point_a)) break;
4070 }
4071 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4072 }
4073 // write from ship to end selected leg point data if the route is
4074 // active
4075 if (validActive) {
4076 s << "\n"
4077 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4078 shiptoEndLeg +=
4080 ->GetCurrentRngToActivePoint(); // add distance from ship
4081 // to active point
4082 shiptoEndLeg +=
4083 segShow_point_b
4084 ->m_seg_len; // add the lenght of the selected leg
4085 s << FormatDistanceAdaptive(shiptoEndLeg);
4086 // ensure sog/cog are valid and vmg is positive to keep data
4087 // coherent
4088 double vmg = 0.;
4089 if (!std::isnan(gCog) && !std::isnan(gSog))
4090 vmg = gSog *
4091 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4092 PI / 180.);
4093 if (vmg > 0.) {
4094 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4095 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4096 s << " - "
4097 << wxString(ttg_sec > SECONDS_PER_DAY
4098 ? ttg_span.Format(_("%Dd %H:%M"))
4099 : ttg_span.Format(_("%H:%M")));
4100 wxDateTime dtnow, eta;
4101 eta = dtnow.SetToCurrent().Add(ttg_span);
4102 s << " - " << eta.Format("%b").Mid(0, 4)
4103 << eta.Format(" %d %H:%M");
4104 } else
4105 s << " ---- ----";
4106 }
4107 m_pRouteRolloverWin->SetString(s);
4108
4109 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4110 LEG_ROLLOVER, win_size);
4111 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4112 m_pRouteRolloverWin->IsActive(true);
4113 b_need_refresh = true;
4114 showRouteRollover = true;
4115 break;
4116 }
4117 } else {
4118 ++node;
4119 }
4120 }
4121 } else {
4122 // Is the cursor still in select radius, and not timed out?
4123 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4124 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4125 m_pRolloverRouteSeg))
4126 showRouteRollover = false;
4127 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4128 showRouteRollover = false;
4129 else
4130 showRouteRollover = true;
4131 }
4132
4133 // If currently creating a route, do not show this rollover window
4134 if (m_routeState) showRouteRollover = false;
4135
4136 // Similar for AIS target rollover window
4137 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4138 showRouteRollover = false;
4139
4140 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4141 !showRouteRollover) {
4142 m_pRouteRolloverWin->IsActive(false);
4143 m_pRolloverRouteSeg = NULL;
4144 m_pRouteRolloverWin->Destroy();
4145 m_pRouteRolloverWin = NULL;
4146 b_need_refresh = true;
4147 } else if (m_pRouteRolloverWin && showRouteRollover) {
4148 m_pRouteRolloverWin->IsActive(true);
4149 b_need_refresh = true;
4150 }
4151
4152 // Now the Track info rollover
4153 // Show the track segment info
4154 bool showTrackRollover = false;
4155
4156 if (NULL == m_pRolloverTrackSeg) {
4157 // Get a list of all selectable sgements, and search for the first
4158 // visible segment as the rollover target.
4159
4160 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4161 SelectableItemList SelList = pSelect->FindSelectionList(
4162 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4163
4164 auto node = SelList.begin();
4165 while (node != SelList.end()) {
4166 SelectItem *pFindSel = *node;
4167
4168 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4169
4170 if (pt && pt->IsVisible()) {
4171 m_pRolloverTrackSeg = pFindSel;
4172 showTrackRollover = true;
4173
4174 if (NULL == m_pTrackRolloverWin) {
4175 m_pTrackRolloverWin = new RolloverWin(this, 10);
4176 m_pTrackRolloverWin->IsActive(false);
4177 }
4178
4179 if (!m_pTrackRolloverWin->IsActive()) {
4180 wxString s;
4181 TrackPoint *segShow_point_a =
4182 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4183 TrackPoint *segShow_point_b =
4184 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4185
4186 double brg, dist;
4187 DistanceBearingMercator(
4188 segShow_point_b->m_lat, segShow_point_b->m_lon,
4189 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4190
4191 if (!pt->m_bIsInLayer)
4192 s.Append(_("Track") + ": ");
4193 else
4194 s.Append(_("Layer Track: "));
4195
4196 if (pt->GetName().IsEmpty())
4197 s.Append(_("(unnamed)"));
4198 else
4199 s.Append(pt->GetName());
4200 double tlenght = pt->Length();
4201 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4202 if (pt->GetLastPoint()->GetTimeString() &&
4203 pt->GetPoint(0)->GetTimeString()) {
4204 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4205 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4206 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4207 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4208 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4209 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4210 << getUsrSpeedUnit();
4211 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4212 : ttime.Format(" %H:%M"));
4213 }
4214 }
4215
4216 if (g_bShowTrackPointTime &&
4217 strlen(segShow_point_b->GetTimeString())) {
4218 wxString stamp = segShow_point_b->GetTimeString();
4219 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4220 if (timestamp.IsValid()) {
4221 // Format track rollover timestamp to OCPN global TZ setting
4224 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4225 }
4226 s << "\n" << _("Segment Created: ") << stamp;
4227 }
4228
4229 s << "\n";
4230 if (g_bShowTrue)
4231 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4232 0x00B0);
4233
4234 if (g_bShowMag) {
4235 double latAverage =
4236 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4237 double lonAverage =
4238 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4239 double varBrg =
4240 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4241
4242 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4243 0x00B0);
4244 }
4245
4246 s << FormatDistanceAdaptive(dist);
4247
4248 if (segShow_point_a->GetTimeString() &&
4249 segShow_point_b->GetTimeString()) {
4250 wxDateTime apoint = segShow_point_a->GetCreateTime();
4251 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4252 if (apoint.IsValid() && bpoint.IsValid()) {
4253 double segmentSpeed = toUsrSpeed(
4254 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4255 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4256 << getUsrSpeedUnit();
4257 }
4258 }
4259
4260 m_pTrackRolloverWin->SetString(s);
4261
4262 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4263 LEG_ROLLOVER, win_size);
4264 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4265 m_pTrackRolloverWin->IsActive(true);
4266 b_need_refresh = true;
4267 showTrackRollover = true;
4268 break;
4269 }
4270 } else {
4271 ++node;
4272 }
4273 }
4274 } else {
4275 // Is the cursor still in select radius, and not timed out?
4276 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4277 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4278 m_pRolloverTrackSeg))
4279 showTrackRollover = false;
4280 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4281 showTrackRollover = false;
4282 else
4283 showTrackRollover = true;
4284 }
4285
4286 // Similar for AIS target rollover window
4287 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4288 showTrackRollover = false;
4289
4290 // Similar for route rollover window
4291 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4292 showTrackRollover = false;
4293
4294 // TODO We onlt show tracks on primary canvas....
4295 // if(!IsPrimaryCanvas())
4296 // showTrackRollover = false;
4297
4298 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4299 !showTrackRollover) {
4300 m_pTrackRolloverWin->IsActive(false);
4301 m_pRolloverTrackSeg = NULL;
4302 m_pTrackRolloverWin->Destroy();
4303 m_pTrackRolloverWin = NULL;
4304 b_need_refresh = true;
4305 } else if (m_pTrackRolloverWin && showTrackRollover) {
4306 m_pTrackRolloverWin->IsActive(true);
4307 b_need_refresh = true;
4308 }
4309
4310 if (b_need_refresh) Refresh();
4311}
4312
4313void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4314 if ((GetShowENCLights() || m_bsectors_shown) &&
4315 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4316 extendedSectorLegs)) {
4317 if (!m_bsectors_shown) {
4318 ReloadVP(false);
4319 m_bsectors_shown = true;
4320 }
4321 } else {
4322 if (m_bsectors_shown) {
4323 ReloadVP(false);
4324 m_bsectors_shown = false;
4325 }
4326 }
4327
4328// This is here because GTK status window update is expensive..
4329// cairo using pango rebuilds the font every time so is very
4330// inefficient
4331// Anyway, only update the status bar when this timer expires
4332#if defined(__WXGTK__) || defined(__WXQT__)
4333 {
4334 // Check the absolute range of the cursor position
4335 // There could be a window wherein the chart geoereferencing is not
4336 // valid....
4337 double cursor_lat, cursor_lon;
4338 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4339
4340 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4341 while (cursor_lon < -180.) cursor_lon += 360.;
4342
4343 while (cursor_lon > 180.) cursor_lon -= 360.;
4344
4345 SetCursorStatus(cursor_lat, cursor_lon);
4346 }
4347 }
4348#endif
4349}
4350
4351void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4352 if (!top_frame::Get()->GetFrameStatusBar()) return;
4353
4354 wxString s1;
4355 s1 += " ";
4356 s1 += toSDMM(1, cursor_lat);
4357 s1 += " ";
4358 s1 += toSDMM(2, cursor_lon);
4359
4360 if (STAT_FIELD_CURSOR_LL >= 0)
4361 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4362
4363 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4364
4365 double brg, dist;
4366 wxString sm;
4367 wxString st;
4368 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4369 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4370 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4371
4372 wxString s = st + sm;
4373 s << FormatDistanceAdaptive(dist);
4374
4375 // CUSTOMIZATION - LIVE ETA OPTION
4376 // -------------------------------------------------------
4377 // Calculate an "live" ETA based on route starting from the current
4378 // position of the boat and goes to the cursor of the mouse.
4379 // In any case, an standard ETA will be calculated with a default speed
4380 // of the boat to give an estimation of the route (in particular if GPS
4381 // is off).
4382
4383 // Display only if option "live ETA" is selected in Settings > Display >
4384 // General.
4385 if (g_bShowLiveETA) {
4386 float realTimeETA;
4387 float boatSpeed;
4388 float boatSpeedDefault = g_defaultBoatSpeed;
4389
4390 // Calculate Estimate Time to Arrival (ETA) in minutes
4391 // Check before is value not closed to zero (it will make an very big
4392 // number...)
4393 if (!std::isnan(gSog)) {
4394 boatSpeed = gSog;
4395 if (boatSpeed < 0.5) {
4396 realTimeETA = 0;
4397 } else {
4398 realTimeETA = dist / boatSpeed * 60;
4399 }
4400 } else {
4401 realTimeETA = 0;
4402 }
4403
4404 // Add space after distance display
4405 s << " ";
4406 // Display ETA
4407 s << minutesToHoursDays(realTimeETA);
4408
4409 // In any case, display also an ETA with default speed at 6knts
4410
4411 s << " [@";
4412 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4413 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4414 s << " ";
4415 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4416 s << "]";
4417 }
4418 // END OF - LIVE ETA OPTION
4419
4420 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4421}
4422
4423// CUSTOMIZATION - FORMAT MINUTES
4424// -------------------------------------------------------
4425// New function to format minutes into a more readable format:
4426// * Hours + minutes, or
4427// * Days + hours.
4428wxString minutesToHoursDays(float timeInMinutes) {
4429 wxString s;
4430
4431 if (timeInMinutes == 0) {
4432 s << "--min";
4433 }
4434
4435 // Less than 60min, keep time in minutes
4436 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4437 s << wxString::Format("%d", (int)timeInMinutes);
4438 s << "min";
4439 }
4440
4441 // Between 1h and less than 24h, display time in hours, minutes
4442 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4443 int hours;
4444 int min;
4445 hours = (int)timeInMinutes / 60;
4446 min = (int)timeInMinutes % 60;
4447
4448 if (min == 0) {
4449 s << wxString::Format("%d", hours);
4450 s << "h";
4451 } else {
4452 s << wxString::Format("%d", hours);
4453 s << "h";
4454 s << wxString::Format("%d", min);
4455 s << "min";
4456 }
4457
4458 }
4459
4460 // More than 24h, display time in days, hours
4461 else if (timeInMinutes > 24 * 60) {
4462 int days;
4463 int hours;
4464 days = (int)(timeInMinutes / 60) / 24;
4465 hours = (int)(timeInMinutes / 60) % 24;
4466
4467 if (hours == 0) {
4468 s << wxString::Format("%d", days);
4469 s << "d";
4470 } else {
4471 s << wxString::Format("%d", days);
4472 s << "d";
4473 s << wxString::Format("%d", hours);
4474 s << "h";
4475 }
4476 }
4477
4478 return s;
4479}
4480
4481// END OF CUSTOMIZATION - FORMAT MINUTES
4482// Thanks open source code ;-)
4483// -------------------------------------------------------
4484
4485void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4486 double clat, clon;
4487 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4488 *lat = clat;
4489 *lon = clon;
4490}
4491
4492void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4493 wxPoint2DDouble *r) {
4494 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4495}
4496
4498 double rlon, wxPoint2DDouble *r) {
4499 // If the Current Chart is a raster chart, and the
4500 // requested lat/long is within the boundaries of the chart,
4501 // and the VP is not rotated,
4502 // then use the embedded BSB chart georeferencing algorithm
4503 // for greater accuracy
4504 // Additionally, use chart embedded georef if the projection is TMERC
4505 // i.e. NOT MERCATOR and NOT POLYCONIC
4506
4507 // If for some reason the chart rejects the request by returning an error,
4508 // then fall back to Viewport Projection estimate from canvas parameters
4509 if (!g_bopengl && m_singleChart &&
4510 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4511 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4512 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4513 (m_singleChart->GetChartProjectionType() !=
4514 PROJECTION_TRANSVERSE_MERCATOR) &&
4515 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4516 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4517 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4518 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4519 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4520 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4521 // Cur_BSB_Ch->GetCOVRTablenPoints
4522 // ( 0 ), rlon,
4523 // rlat );
4524 // bInside = true;
4525 // if ( bInside )
4526 if (Cur_BSB_Ch) {
4527 // This is a Raster chart....
4528 // If the VP is changing, the raster chart parameters may not yet be
4529 // setup So do that before accessing the chart's embedded
4530 // georeferencing
4531 Cur_BSB_Ch->SetVPRasterParms(vp);
4532 double rpixxd, rpixyd;
4533 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4534 r->m_x = rpixxd;
4535 r->m_y = rpixyd;
4536 return;
4537 }
4538 }
4539 }
4540
4541 // if needed, use the VPoint scaling estimator,
4542 *r = vp.GetDoublePixFromLL(rlat, rlon);
4543}
4544
4545// This routine might be deleted and all of the rendering improved
4546// to have floating point accuracy
4547bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4548 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4549}
4550
4551bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4552 wxPoint *r) {
4553 wxPoint2DDouble p;
4554 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4555
4556 // some projections give nan values when invisible values (other side of
4557 // world) are requested we should stop using integer coordinates or return
4558 // false here (and test it everywhere)
4559 if (std::isnan(p.m_x)) {
4560 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4561 return false;
4562 }
4563
4564 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4565 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4566 else
4567 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4568
4569 return true;
4570}
4571
4572void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4573 double &lon) {
4574 // If the Current Chart is a raster chart, and the
4575 // requested x,y is within the boundaries of the chart,
4576 // and the VP is not rotated,
4577 // then use the embedded BSB chart georeferencing algorithm
4578 // for greater accuracy
4579 // Additionally, use chart embedded georef if the projection is TMERC
4580 // i.e. NOT MERCATOR and NOT POLYCONIC
4581
4582 // If for some reason the chart rejects the request by returning an error,
4583 // then fall back to Viewport Projection estimate from canvas parameters
4584 bool bUseVP = true;
4585
4586 if (!g_bopengl && m_singleChart &&
4587 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4588 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4589 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4590 (m_singleChart->GetChartProjectionType() !=
4591 PROJECTION_TRANSVERSE_MERCATOR) &&
4592 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4593 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4594 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4595 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4596
4597 // TODO maybe need iterative process to validate bInside
4598 // first pass is mercator, then check chart boundaries
4599
4600 if (Cur_BSB_Ch) {
4601 // This is a Raster chart....
4602 // If the VP is changing, the raster chart parameters may not yet be
4603 // setup So do that before accessing the chart's embedded
4604 // georeferencing
4605 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4606
4607 double slat, slon;
4608 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4609 lat = slat;
4610
4611 if (slon < -180.)
4612 slon += 360.;
4613 else if (slon > 180.)
4614 slon -= 360.;
4615
4616 lon = slon;
4617 bUseVP = false;
4618 }
4619 }
4620 }
4621
4622 // if needed, use the VPoint scaling estimator
4623 if (bUseVP) {
4624 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4625 }
4626}
4627
4629 StopMovement();
4630 DoZoomCanvas(factor, false);
4631 extendedSectorLegs.clear();
4632}
4633
4634void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4635 bool stoptimer) {
4636 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4637
4638 if (g_bsmoothpanzoom) {
4639 if (StartTimedMovement(stoptimer)) {
4640 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4641 m_zoom_factor = factor;
4642 }
4643
4644 m_zoom_target = VPoint.chart_scale / factor;
4645 } else {
4646 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4647
4648 DoZoomCanvas(factor, can_zoom_to_cursor);
4649 }
4650
4651 extendedSectorLegs.clear();
4652}
4653
4654void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4655 // possible on startup
4656 if (!ChartData) return;
4657 if (!m_pCurrentStack) return;
4658
4659 /* TODO: queue the quilted loading code to a background thread
4660 so yield is never called from here, and also rendering is not delayed */
4661
4662 // Cannot allow Yield() re-entrancy here
4663 if (m_bzooming) return;
4664 m_bzooming = true;
4665
4666 double old_ppm = GetVP().view_scale_ppm;
4667
4668 // Capture current cursor position for zoom to cursor
4669 double zlat = m_cursor_lat;
4670 double zlon = m_cursor_lon;
4671
4672 double proposed_scale_onscreen =
4673 GetVP().chart_scale /
4674 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4675 bool b_do_zoom = false;
4676
4677 if (factor > 1) {
4678 b_do_zoom = true;
4679
4680 // double zoom_factor = factor;
4681
4682 ChartBase *pc = NULL;
4683
4684 if (!VPoint.b_quilt) {
4685 pc = m_singleChart;
4686 } else {
4687 if (!m_disable_adjust_on_zoom) {
4688 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4689 if (new_db_index >= 0)
4690 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4691 else { // for whatever reason, no reference chart is known
4692 // Choose the smallest scale chart on the current stack
4693 // and then adjust for scale range
4694 int current_ref_stack_index = -1;
4695 if (m_pCurrentStack->nEntry) {
4696 int trial_index =
4697 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4698 m_pQuilt->SetReferenceChart(trial_index);
4699 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4700 if (new_db_index >= 0)
4701 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4702 }
4703 }
4704
4705 if (m_pCurrentStack)
4706 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4707 new_db_index); // highlite the correct bar entry
4708 }
4709 }
4710
4711 if (pc) {
4712 // double target_scale_ppm = GetVPScale() * zoom_factor;
4713 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4714 // target_scale_ppm;
4715
4716 // Query the chart to determine the appropriate zoom range
4717 double min_allowed_scale =
4718 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4719
4720 if (proposed_scale_onscreen < min_allowed_scale) {
4721 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4722 m_zoom_factor = 1; /* stop zooming */
4723 b_do_zoom = false;
4724 } else
4725 proposed_scale_onscreen = min_allowed_scale;
4726 }
4727
4728 } else {
4729 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4730 }
4731
4732 } else if (factor < 1) {
4733 b_do_zoom = true;
4734
4735 ChartBase *pc = NULL;
4736
4737 bool b_smallest = false;
4738
4739 if (!VPoint.b_quilt) { // not quilted
4740 pc = m_singleChart;
4741
4742 if (pc) {
4743 // If m_singleChart is not on the screen, unbound the zoomout
4744 LLBBox viewbox = VPoint.GetBBox();
4745 // BoundingBox chart_box;
4746 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4747 double max_allowed_scale;
4748
4749 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4750
4751 // We can allow essentially unbounded zoomout in single chart mode
4752 // if( ChartData->GetDBBoundingBox( current_index,
4753 // &chart_box ) &&
4754 // !viewbox.IntersectOut( chart_box ) )
4755 // // Clamp the minimum scale zoom-out to the value
4756 // specified by the chart max_allowed_scale =
4757 // wxMin(max_allowed_scale, 4.0 *
4758 // pc->GetNormalScaleMax(
4759 // GetCanvasScaleFactor(),
4760 // GetCanvasWidth() ) );
4761 if (proposed_scale_onscreen > max_allowed_scale) {
4762 m_zoom_factor = 1; /* stop zooming */
4763 proposed_scale_onscreen = max_allowed_scale;
4764 }
4765 }
4766
4767 } else {
4768 if (!m_disable_adjust_on_zoom) {
4769 int new_db_index =
4770 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4771 if (new_db_index >= 0)
4772 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4773
4774 if (m_pCurrentStack)
4775 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4776 new_db_index); // highlite the correct bar entry
4777
4778 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4779
4780 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4781 proposed_scale_onscreen =
4782 wxMin(proposed_scale_onscreen,
4783 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4784 }
4785
4786 // set a minimum scale
4787 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4788 m_absolute_min_scale_ppm)
4789 proposed_scale_onscreen =
4790 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4791 }
4792 }
4793 double new_scale =
4794 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4795
4796 if (b_do_zoom) {
4797 // Disable ZTC if lookahead is ON, and currently b_follow is active
4798 bool b_allow_ztc = true;
4799 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4800 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4801 if (m_bLookAhead) {
4802 double brg, distance;
4803 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4804 &distance);
4805 dir_to_shift = brg;
4806 meters_to_shift = distance * 1852;
4807 }
4808 // Arrange to combine the zoom and pan into one operation for smoother
4809 // appearance
4810 SetVPScale(new_scale, false); // adjust, but deferred refresh
4811 wxPoint r;
4812 GetCanvasPointPix(zlat, zlon, &r);
4813 // this will emit the Refresh()
4814 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4815 } else {
4816 SetVPScale(new_scale);
4817 if (m_bFollow) DoCanvasUpdate();
4818 }
4819 }
4820
4821 m_bzooming = false;
4822}
4823
4824void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4825 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4826 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4827}
4828
4829int rot;
4830void ChartCanvas::RotateCanvas(double dir) {
4831 // SetUpMode(NORTH_UP_MODE);
4832
4833 if (g_bsmoothpanzoom) {
4834 if (StartTimedMovement()) {
4835 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4836 m_rotation_speed = dir * 60;
4837 }
4838 } else {
4839 double speed = dir * 10;
4840 if (m_modkeys == wxMOD_ALT) speed /= 20;
4841 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4842 }
4843}
4844
4845void ChartCanvas::DoRotateCanvas(double rotation) {
4846 while (rotation < 0) rotation += 2 * PI;
4847 while (rotation > 2 * PI) rotation -= 2 * PI;
4848
4849 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4850
4851 SetVPRotation(rotation);
4852 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4853}
4854
4855void ChartCanvas::DoTiltCanvas(double tilt) {
4856 while (tilt < 0) tilt = 0;
4857 while (tilt > .95) tilt = .95;
4858
4859 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4860
4861 VPoint.tilt = tilt;
4862 Refresh(false);
4863}
4864
4865void ChartCanvas::TogglebFollow() {
4866 if (!m_bFollow)
4867 SetbFollow();
4868 else
4869 ClearbFollow();
4870}
4871
4872void ChartCanvas::ClearbFollow() {
4873 m_bFollow = false; // update the follow flag
4874
4875 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4876
4877 UpdateFollowButtonState();
4878
4879 DoCanvasUpdate();
4880 ReloadVP();
4881 top_frame::Get()->SetChartUpdatePeriod();
4882}
4883
4884void ChartCanvas::SetbFollow() {
4885 // Is the OWNSHIP on-screen?
4886 // If not, then reset the OWNSHIP offset to 0 (center screen)
4887 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4888 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4889 m_OSoffsetx = 0;
4890 m_OSoffsety = 0;
4891 }
4892
4893 // Apply the present b_follow offset values to ship position
4894 wxPoint2DDouble p;
4896 p.m_x += m_OSoffsetx;
4897 p.m_y -= m_OSoffsety;
4898
4899 // compute the target center screen lat/lon
4900 double dlat, dlon;
4901 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4902
4903 JumpToPosition(dlat, dlon, GetVPScale());
4904 m_bFollow = true;
4905
4906 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4907 UpdateFollowButtonState();
4908
4909 if (!g_bSmoothRecenter) {
4910 DoCanvasUpdate();
4911 ReloadVP();
4912 }
4913 top_frame::Get()->SetChartUpdatePeriod();
4914}
4915
4916void ChartCanvas::UpdateFollowButtonState() {
4917 if (m_muiBar) {
4918 if (!m_bFollow)
4919 m_muiBar->SetFollowButtonState(0);
4920 else {
4921 if (m_bLookAhead)
4922 m_muiBar->SetFollowButtonState(2);
4923 else
4924 m_muiBar->SetFollowButtonState(1);
4925 }
4926 }
4927
4928#ifdef __ANDROID__
4929 if (!m_bFollow)
4930 androidSetFollowTool(0);
4931 else {
4932 if (m_bLookAhead)
4933 androidSetFollowTool(2);
4934 else
4935 androidSetFollowTool(1);
4936 }
4937#endif
4938
4939 // Look for plugin using API-121 or later
4940 // If found, make the follow state callback.
4941 if (g_pi_manager) {
4942 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4943 if (pic->m_enabled && pic->m_init_state) {
4944 switch (pic->m_api_version) {
4945 case 121: {
4946 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4947 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4948 break;
4949 }
4950 default:
4951 break;
4952 }
4953 }
4954 }
4955 }
4956}
4957
4958void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4959 if (g_bSmoothRecenter && !m_routeState) {
4960 if (StartSmoothJump(lat, lon, scale_ppm))
4961 return;
4962 else {
4963 // move closer to the target destination, and try again
4964 double gcDist, gcBearingEnd;
4965 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4966 &gcBearingEnd);
4967 gcBearingEnd += 180;
4968 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4969 GetCanvasWidth() / GetVPScale(); // meters
4970 double lon_offset =
4971 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4972 double new_lat = lat + (lat_offset / (1852 * 60));
4973 double new_lon = lon + (lon_offset / (1852 * 60));
4974 SetViewPoint(new_lat, new_lon);
4975 ReloadVP();
4976 StartSmoothJump(lat, lon, scale_ppm);
4977 return;
4978 }
4979 }
4980
4981 if (lon > 180.0) lon -= 360.0;
4982 m_vLat = lat;
4983 m_vLon = lon;
4984 StopMovement();
4985 m_bFollow = false;
4986
4987 if (!GetQuiltMode()) {
4988 double skew = 0;
4989 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4990 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4991 } else {
4992 if (scale_ppm != GetVPScale()) {
4993 // XXX should be done in SetViewPoint
4994 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4995 AdjustQuiltRefChart();
4996 }
4997 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4998 }
4999
5000 ReloadVP();
5001
5002 UpdateFollowButtonState();
5003
5004 // TODO
5005 // if( g_pi_manager ) {
5006 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5007 // }
5008}
5009
5010bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5011 // Check distance to jump, in pixels at current chart scale
5012 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5013 // width.
5014 double gcDist;
5015 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5016 double distance_pixels = gcDist * GetVPScale();
5017 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5018 // Jump is too far, try again
5019 return false;
5020 }
5021
5022 // Save where we're coming from
5023 m_startLat = m_vLat;
5024 m_startLon = m_vLon;
5025 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5026
5027 // Save where we want to end up
5028 m_endLat = lat;
5029 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5030 m_endScale = scale_ppm;
5031
5032 // Setup timing
5033 m_animationDuration = 600; // ms
5034 m_animationStart = wxGetLocalTimeMillis();
5035
5036 // Stop any previous movement, ensure no conflicts
5037 StopMovement();
5038 m_bFollow = false;
5039
5040 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5041 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5042 m_animationActive = true;
5043
5044 return true;
5045}
5046
5047void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5048 // Calculate time fraction from 0..1
5049 wxLongLong now = wxGetLocalTimeMillis();
5050 double elapsed = (now - m_animationStart).ToDouble();
5051 double t = elapsed / m_animationDuration.ToDouble();
5052 if (t > 1.0) t = 1.0;
5053
5054 // Ease function for smoother movement
5055 double e = easeOutCubic(t);
5056
5057 // Interpolate lat/lon/scale
5058 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5059 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5060 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5061
5062 // Update viewpoint
5063 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5064 // portion)
5065 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5066 ReloadVP();
5067
5068 // If we reached the end, stop the timer and finalize
5069 if (t >= 1.0) {
5070 m_easeTimer.Stop();
5071 m_animationActive = false;
5072 UpdateFollowButtonState();
5073 ZoomCanvasSimple(1.0001);
5074 DoCanvasUpdate();
5075 ReloadVP();
5076 }
5077}
5078
5079bool ChartCanvas::PanCanvas(double dx, double dy) {
5080 if (!ChartData) return false;
5081 extendedSectorLegs.clear();
5082
5083 double dlat, dlon;
5084 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5085
5086 int iters = 0;
5087 for (;;) {
5088 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5089
5090 if (iters++ > 5) return false;
5091 if (!std::isnan(dlat)) break;
5092
5093 dx *= .5, dy *= .5;
5094 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5095 }
5096
5097 // avoid overshooting the poles
5098 if (dlat > 90)
5099 dlat = 90;
5100 else if (dlat < -90)
5101 dlat = -90;
5102
5103 if (dlon > 360.) dlon -= 360.;
5104 if (dlon < -360.) dlon += 360.;
5105
5106 // This should not really be necessary, but round-trip georef on some
5107 // charts is not perfect, So we can get creep on repeated unidimensional
5108 // pans, and corrupt chart cacheing.......
5109
5110 // But this only works on north-up projections
5111 // TODO: can we remove this now?
5112 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5113 // .001 ) ) {
5114 //
5115 // if( dx == 0 ) dlon = clon;
5116 // if( dy == 0 ) dlat = clat;
5117 // }
5118
5119 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5120
5121 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5122
5123 if (VPoint.b_quilt) {
5124 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5125 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5126 // Tweak the scale slightly for a new ref chart
5127 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5128 if (pc) {
5129 double tweak_scale_ppm =
5130 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5131 SetVPScale(tweak_scale_ppm);
5132 }
5133 }
5134
5135 if (new_ref_dbIndex == -1) {
5136#pragma GCC diagnostic push
5137#pragma GCC diagnostic ignored "-Warray-bounds"
5138 // The compiler sees a -1 index being used. Does not happen, though.
5139
5140 // for whatever reason, no reference chart is known
5141 // Probably panned out of the coverage region
5142 // If any charts are anywhere on-screen, choose the smallest
5143 // scale chart on the screen to be a new reference chart.
5144 int trial_index = -1;
5145 if (m_pCurrentStack->nEntry) {
5146 int trial_index =
5147 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5148 }
5149
5150 if (trial_index < 0) {
5151 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5152 if (full_screen_array.size())
5153 trial_index = full_screen_array[full_screen_array.size() - 1];
5154 }
5155
5156 if (trial_index >= 0) {
5157 m_pQuilt->SetReferenceChart(trial_index);
5158 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5159 VPoint.rotation);
5160 ReloadVP();
5161 }
5162#pragma GCC diagnostic pop
5163 }
5164 }
5165
5166 // Turn off bFollow only if the ownship has left the screen
5167 if (m_bFollow) {
5168 double offx, offy;
5169 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5170
5171 double offset_angle = atan2(offy, offx);
5172 double offset_distance = sqrt((offy * offy) + (offx * offx));
5173 double chart_angle = GetVPRotation();
5174 double target_angle = chart_angle - offset_angle;
5175 double d_east_mod = offset_distance * cos(target_angle);
5176 double d_north_mod = offset_distance * sin(target_angle);
5177
5178 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5179 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5180
5181 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5182 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5183 m_bFollow = false; // update the follow flag
5184 UpdateFollowButtonState();
5185 }
5186 }
5187
5188 Refresh(false);
5189
5190 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5191
5192 return true;
5193}
5194
5195bool ChartCanvas::IsOwnshipOnScreen() {
5196 wxPoint r;
5198 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5199 ((r.y > 0) && r.y < GetCanvasHeight()))
5200 return true;
5201 else
5202 return false;
5203}
5204
5205void ChartCanvas::ReloadVP(bool b_adjust) {
5206 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5207
5208 LoadVP(VPoint, b_adjust);
5209}
5210
5211void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5212#ifdef ocpnUSE_GL
5213 if (g_bopengl && m_glcc) {
5214 m_glcc->Invalidate();
5215 if (m_glcc->GetSize() != GetSize()) {
5216 m_glcc->SetSize(GetSize());
5217 }
5218 } else
5219#endif
5220 {
5221 m_cache_vp.Invalidate();
5222 m_bm_cache_vp.Invalidate();
5223 }
5224
5225 VPoint.Invalidate();
5226
5227 if (m_pQuilt) m_pQuilt->Invalidate();
5228
5229 // Make sure that the Selected Group is sensible...
5230 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5231 // m_groupIndex = 0;
5232 // if( !CheckGroup( m_groupIndex ) )
5233 // m_groupIndex = 0;
5234
5235 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5236 vp.m_projection_type, b_adjust);
5237}
5238
5239void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5240 m_pQuilt->SetReferenceChart(dbIndex);
5241 VPoint.Invalidate();
5242 m_pQuilt->Invalidate();
5243}
5244
5245double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5246 if (m_pQuilt)
5247 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5248 else
5249 return vp.view_scale_ppm;
5250}
5251
5252// Verify and adjust the current reference chart,
5253// so that it will not lead to excessive overzoom or underzoom onscreen
5254int ChartCanvas::AdjustQuiltRefChart() {
5255 int ret = -1;
5256 if (m_pQuilt) {
5257 wxASSERT(ChartData);
5258 ChartBase *pc =
5259 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5260 if (pc) {
5261 double min_ref_scale =
5262 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5263 double max_ref_scale =
5264 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5265
5266 if (VPoint.chart_scale < min_ref_scale) {
5267 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5268 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5269 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5270 } else {
5271 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5272
5273 if (!brender_ok) {
5274 int target_stack_index = wxNOT_FOUND;
5275 int il = 0;
5276 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5277 if (index == m_pQuilt->GetRefChartdbIndex()) {
5278 target_stack_index = il;
5279 break;
5280 }
5281 il++;
5282 }
5283 if (wxNOT_FOUND == target_stack_index) // should never happen...
5284 target_stack_index = 0;
5285
5286 int ref_family = pc->GetChartFamily();
5287 int extended_array_count =
5288 m_pQuilt->GetExtendedStackIndexArray().size();
5289 while ((!brender_ok) &&
5290 ((int)target_stack_index < (extended_array_count - 1))) {
5291 target_stack_index++;
5292 int test_db_index =
5293 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5294
5295 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5296 IsChartQuiltableRef(test_db_index)) {
5297 // open the target, and check the min_scale
5298 ChartBase *ptest_chart =
5299 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5300 if (ptest_chart) {
5301 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5302 }
5303 }
5304 }
5305
5306 if (brender_ok) { // found a better reference chart
5307 int new_db_index =
5308 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5309 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5310 IsChartQuiltableRef(new_db_index)) {
5311 m_pQuilt->SetReferenceChart(new_db_index);
5312 ret = new_db_index;
5313 } else
5314 ret = m_pQuilt->GetRefChartdbIndex();
5315 } else
5316 ret = m_pQuilt->GetRefChartdbIndex();
5317
5318 } else
5319 ret = m_pQuilt->GetRefChartdbIndex();
5320 }
5321 } else
5322 ret = -1;
5323 }
5324
5325 return ret;
5326}
5327
5328void ChartCanvas::UpdateCanvasOnGroupChange() {
5329 delete m_pCurrentStack;
5330 m_pCurrentStack = new ChartStack;
5331 wxASSERT(ChartData);
5332 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5333 m_groupIndex);
5334
5335 if (m_pQuilt) {
5336 m_pQuilt->Compose(VPoint);
5337 SetFocus();
5338 }
5339}
5340
5341bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5342 double latNE, double lonNE) {
5343 // Center Point
5344 double latc = (latSW + latNE) / 2.0;
5345 double lonc = (lonSW + lonNE) / 2.0;
5346
5347 // Get scale in ppm (latitude)
5348 double ne_easting, ne_northing;
5349 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5350
5351 double sw_easting, sw_northing;
5352 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5353
5354 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5355
5356 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5357}
5358
5359bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5360 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5361 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5362}
5363
5364bool ChartCanvas::SetVPProjection(int projection) {
5365 if (!g_bopengl) // alternative projections require opengl
5366 return false;
5367
5368 // the view scale varies depending on geographic location and projection
5369 // rescale to keep the relative scale on the screen the same
5370 double prev_true_scale_ppm = m_true_scale_ppm;
5371 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5372 VPoint.skew, VPoint.rotation, projection) &&
5373 SetVPScale(wxMax(
5374 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5375 m_absolute_min_scale_ppm));
5376}
5377
5378bool ChartCanvas::SetViewPoint(double lat, double lon) {
5379 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5380 VPoint.rotation);
5381}
5382
5383bool ChartCanvas::SetVPRotation(double angle) {
5384 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5385 VPoint.skew, angle);
5386}
5387bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5388 double skew, double rotation, int projection,
5389 bool b_adjust, bool b_refresh) {
5390 if (ChartData->IsBusy()) return false;
5391 bool b_ret = false;
5392 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5393 skew -= 2 * PI;
5394 // Any sensible change?
5395 if (VPoint.IsValid()) {
5396 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5397 (fabs(VPoint.skew - skew) < 1e-9) &&
5398 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5399 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5400 (VPoint.m_projection_type == projection ||
5401 projection == PROJECTION_UNKNOWN))
5402 return false;
5403 }
5404 if (VPoint.m_projection_type != projection)
5405 VPoint.InvalidateTransformCache(); // invalidate
5406
5407 // Take a local copy of the last viewport
5408 ViewPort last_vp = VPoint;
5409
5410 VPoint.skew = skew;
5411 VPoint.clat = lat;
5412 VPoint.clon = lon;
5413 VPoint.rotation = rotation;
5414 VPoint.view_scale_ppm = scale_ppm;
5415 if (projection != PROJECTION_UNKNOWN)
5416 VPoint.SetProjectionType(projection);
5417 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5418 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5419
5420 // don't allow latitude above 88 for mercator (90 is infinity)
5421 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5422 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5423 if (VPoint.clat > 89.5)
5424 VPoint.clat = 89.5;
5425 else if (VPoint.clat < -89.5)
5426 VPoint.clat = -89.5;
5427 }
5428
5429 // don't zoom out too far for transverse mercator polyconic until we resolve
5430 // issues
5431 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5432 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5433 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5434
5435 // SetVPRotation(rotation);
5436
5437 if (!g_bopengl) // tilt is not possible without opengl
5438 VPoint.tilt = 0;
5439
5440 if ((VPoint.pix_width <= 0) ||
5441 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5442 return false;
5443
5444 bool bwasValid = VPoint.IsValid();
5445 VPoint.Validate(); // Mark this ViewPoint as OK
5446
5447 // Has the Viewport scale changed? If so, invalidate the vp
5448 if (last_vp.view_scale_ppm != scale_ppm) {
5449 m_cache_vp.Invalidate();
5450 InvalidateGL();
5451 }
5452
5453 // A preliminary value, may be tweaked below
5454 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5455
5456 // recompute cursor position
5457 // and send to interested plugins if the mouse is actually in this window
5458 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5459 int mouseX = mouse_x;
5460 int mouseY = mouse_y;
5461 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5462 (mouseY < VPoint.pix_height)) {
5463 double lat_mouse, lon_mouse;
5464 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5465 m_cursor_lat = lat_mouse;
5466 m_cursor_lon = lon_mouse;
5467 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5468 }
5469 }
5470
5471 if (!VPoint.b_quilt && m_singleChart) {
5472 VPoint.SetBoxes();
5473
5474 // Allow the chart to adjust the new ViewPort for performance optimization
5475 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5476 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5477
5478 // If there is a sensible change in the chart render, refresh the whole
5479 // screen
5480 if ((!m_cache_vp.IsValid()) ||
5481 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5482 Refresh(false);
5483 b_ret = true;
5484 } else {
5485 wxPoint cp_last, cp_this;
5486 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5487 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5488
5489 if (cp_last != cp_this) {
5490 Refresh(false);
5491 b_ret = true;
5492 }
5493 }
5494 // Create the stack
5495 if (m_pCurrentStack) {
5496 assert(ChartData != 0);
5497 int current_db_index;
5498 current_db_index =
5499 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5500
5501 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5502 m_groupIndex);
5503 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5504 }
5505
5506 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5507 }
5508
5509 // Handle the quilted case
5510 if (VPoint.b_quilt) {
5511 VPoint.SetBoxes();
5512
5513 if (last_vp.view_scale_ppm != scale_ppm)
5514 m_pQuilt->InvalidateAllQuiltPatchs();
5515
5516 // Create the quilt
5517 if (ChartData /*&& ChartData->IsValid()*/) {
5518 if (!m_pCurrentStack) return false;
5519
5520 int current_db_index;
5521 current_db_index =
5522 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5523
5524 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5525 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5526
5527 // Check to see if the current quilt reference chart is in the new stack
5528 int current_ref_stack_index = -1;
5529 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5530 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5531 current_ref_stack_index = i;
5532 }
5533
5534 if (g_bFullScreenQuilt) {
5535 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5536 }
5537
5538 // We might need a new Reference Chart
5539 bool b_needNewRef = false;
5540
5541 // If the new stack does not contain the current ref chart....
5542 if ((-1 == current_ref_stack_index) &&
5543 (m_pQuilt->GetRefChartdbIndex() >= 0))
5544 b_needNewRef = true;
5545
5546 // Would the current Ref Chart be excessively underzoomed?
5547 // We need to check this here to be sure, since we cannot know where the
5548 // reference chart was assigned. For instance, the reference chart may
5549 // have been selected from the config file, or from a long jump with a
5550 // chart family switch implicit. Anyway, we check to be sure....
5551 bool renderable = true;
5552 ChartBase *referenceChart =
5553 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5554 if (referenceChart) {
5555 double chartMaxScale = referenceChart->GetNormalScaleMax(
5556 GetCanvasScaleFactor(), GetCanvasWidth());
5557 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5558 }
5559 if (!renderable) b_needNewRef = true;
5560
5561 // Need new refchart?
5562 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5563 const ChartTableEntry &cte_ref =
5564 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5565 int target_scale = cte_ref.GetScale();
5566 int target_type = cte_ref.GetChartType();
5567 int candidate_stack_index;
5568
5569 // reset the ref chart in a way that does not lead to excessive
5570 // underzoom, for performance reasons Try to find a chart that is the
5571 // same type, and has a scale of just smaller than the current ref
5572 // chart
5573
5574 candidate_stack_index = 0;
5575 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5576 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5577 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5578 int candidate_scale = cte_candidate.GetScale();
5579 int candidate_type = cte_candidate.GetChartType();
5580
5581 if ((candidate_scale >= target_scale) &&
5582 (candidate_type == target_type)) {
5583 bool renderable = true;
5584 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5585 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5586 if (tentative_referenceChart) {
5587 double chartMaxScale =
5588 tentative_referenceChart->GetNormalScaleMax(
5589 GetCanvasScaleFactor(), GetCanvasWidth());
5590 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5591 }
5592
5593 if (renderable) break;
5594 }
5595
5596 candidate_stack_index++;
5597 }
5598
5599 // If that did not work, look for a chart of just larger scale and
5600 // same type
5601 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5602 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5603 while (candidate_stack_index >= 0) {
5604 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5605 if (idx >= 0) {
5606 const ChartTableEntry &cte_candidate =
5607 ChartData->GetChartTableEntry(idx);
5608 int candidate_scale = cte_candidate.GetScale();
5609 int candidate_type = cte_candidate.GetChartType();
5610
5611 if ((candidate_scale <= target_scale) &&
5612 (candidate_type == target_type))
5613 break;
5614 }
5615 candidate_stack_index--;
5616 }
5617 }
5618
5619 // and if that did not work, chose stack entry 0
5620 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5621 (candidate_stack_index < 0))
5622 candidate_stack_index = 0;
5623
5624 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5625
5626 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5627 }
5628
5629 if (!g_bopengl) {
5630 // Preset the VPoint projection type to match what the quilt projection
5631 // type will be
5632 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5633
5634 // Always keep the default Mercator projection if the reference chart is
5635 // not in the PatchList or the scale is too small for it to render.
5636
5637 bool renderable = true;
5638 ChartBase *referenceChart =
5639 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5640 if (referenceChart) {
5641 double chartMaxScale = referenceChart->GetNormalScaleMax(
5642 GetCanvasScaleFactor(), GetCanvasWidth());
5643 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5644 proj = ChartData->GetDBChartProj(ref_db_index);
5645 } else
5646 proj = PROJECTION_MERCATOR;
5647
5648 VPoint.b_MercatorProjectionOverride =
5649 (m_pQuilt->GetnCharts() == 0 || !renderable);
5650
5651 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5652
5653 VPoint.SetProjectionType(proj);
5654 }
5655
5656 // If this quilt will be a perceptible delta from the existing quilt,
5657 // then refresh the entire screen
5658 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5659 // Allow the quilt to adjust the new ViewPort for performance
5660 // optimization This will normally be only a fractional (i.e.
5661 // sub-pixel) adjustment...
5662 if (b_adjust) {
5663 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5664 }
5665
5666 // ChartData->ClearCacheInUseFlags();
5667 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5668
5669 // wxStopWatch sw;
5670
5671#ifdef __ANDROID__
5672 // This is an optimization for panning on touch screen systems.
5673 // The quilt composition is deferred until the OnPaint() message gets
5674 // finally removed and processed from the message queue.
5675 // Takes advantage of the fact that touch-screen pan gestures are
5676 // usually short in distance,
5677 // so not requiring a full quilt rebuild until the pan gesture is
5678 // complete.
5679 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5680 // qDebug() << "Force compose";
5681 m_pQuilt->Compose(VPoint);
5682 } else {
5683 m_pQuilt->Invalidate();
5684 }
5685#else
5686 m_pQuilt->Compose(VPoint);
5687#endif
5688
5689 // printf("comp time %ld\n", sw.Time());
5690
5691 // If the extended chart stack has changed, invalidate any cached
5692 // render bitmap
5693 // if(m_pQuilt->GetXStackHash() != hash1) {
5694 // m_bm_cache_vp.Invalidate();
5695 // InvalidateGL();
5696 // }
5697
5698 ChartData->PurgeCacheUnusedCharts(0.7);
5699
5700 if (b_refresh) Refresh(false);
5701
5702 b_ret = true;
5703 }
5704 }
5705
5706 VPoint.skew = 0.; // Quilting supports 0 Skew
5707 } else if (!g_bopengl) {
5708 OcpnProjType projection = PROJECTION_UNKNOWN;
5709 if (m_singleChart) // viewport projection must match chart projection
5710 // without opengl
5711 projection = m_singleChart->GetChartProjectionType();
5712 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5713 VPoint.SetProjectionType(projection);
5714 }
5715
5716 // Has the Viewport projection changed? If so, invalidate the vp
5717 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5718 m_cache_vp.Invalidate();
5719 InvalidateGL();
5720 }
5721
5722 UpdateCanvasControlBar(); // Refresh the Piano
5723
5724 VPoint.chart_scale = 1.0; // fallback default value
5725
5726 if (VPoint.GetBBox().GetValid()) {
5727 // Update the viewpoint reference scale
5728 if (m_singleChart)
5729 VPoint.ref_scale = m_singleChart->GetNativeScale();
5730 else {
5731#ifdef __ANDROID__
5732 // This is an optimization for panning on touch screen systems.
5733 // See above.
5734 // Quilt might not be fully composed at this point, so for cm93
5735 // the reference scale may not be known.
5736 // In this case, do not update the VP ref_scale.
5737 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5738 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5739 }
5740#else
5741 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5742#endif
5743 }
5744
5745 // Calculate the on-screen displayed actual scale
5746 // by a simple traverse northward from the center point
5747 // of roughly one eighth of the canvas height
5748 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5749
5750 double delta_check =
5751 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5752 delta_check /= 8.;
5753
5754 double check_point = wxMin(89., VPoint.clat);
5755
5756 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5757
5758 double rhumbDist;
5759 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5760 VPoint.clon, 0, &rhumbDist);
5761
5762 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5763 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5764 // Calculate the distance between r1 and r in physical pixels.
5765 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5766 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5767
5768 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5769
5770 // A fall back in case of very high zoom-out, giving delta_y == 0
5771 // which can probably only happen with vector charts
5772 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5773
5774 // Another fallback, for highly zoomed out charts
5775 // This adjustment makes the displayed TrueScale correspond to the
5776 // same algorithm used to calculate the chart zoom-out limit for
5777 // ChartDummy.
5778 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5779
5780 if (m_true_scale_ppm)
5781 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5782 else
5783 VPoint.chart_scale = 1.0;
5784
5785 // Create a nice renderable string
5786 double round_factor = 1000.;
5787 if (VPoint.chart_scale <= 1000.)
5788 round_factor = 10.;
5789 else if (VPoint.chart_scale <= 10000.)
5790 round_factor = 100.;
5791 else if (VPoint.chart_scale <= 100000.)
5792 round_factor = 1000.;
5793
5794 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5795 double retina_coef = 1;
5796#ifdef ocpnUSE_GL
5797#ifdef __WXOSX__
5798 if (g_bopengl) {
5799 retina_coef = GetContentScaleFactor();
5800 }
5801#endif
5802#endif
5803
5804 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5805 // rounded to the nearest 10, 100 or 1000.
5806 //
5807 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5808 // true_scale_display. That does not make sense. The chart scale should be
5809 // the same as the true scale within the limits of the rounding factor.
5810 double true_scale_display =
5811 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5812 wxString text;
5813
5814 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5815
5816 if (m_displayed_scale_factor > 10.0)
5817 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5818 m_displayed_scale_factor);
5819 else if (m_displayed_scale_factor > 1.0)
5820 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5821 m_displayed_scale_factor);
5822 else if (m_displayed_scale_factor > 0.1) {
5823 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5824 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5825 } else if (m_displayed_scale_factor > 0.01) {
5826 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5827 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5828 } else {
5829 text.Printf(
5830 "%s %4.0f (---)", _("Scale"),
5831 true_scale_display); // Generally, no chart, so no chart scale factor
5832 }
5833
5834 m_scaleValue = true_scale_display;
5835 m_scaleText = text;
5836 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5837
5838 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5839 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5840 STAT_FIELD_SCALE)) {
5841 // Check to see if the text will fit in the StatusBar field...
5842 bool b_noshow = false;
5843 {
5844 int w = 0;
5845 int h;
5846 wxClientDC dc(top_frame::Get()->GetStatusBar());
5847 if (dc.IsOk()) {
5848 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5849 dc.SetFont(*templateFont);
5850 dc.GetTextExtent(text, &w, &h);
5851
5852 // If text is too long for the allocated field, try to reduce the text
5853 // string a bit.
5854 wxRect rect;
5855 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5856 rect);
5857 if (w && w > rect.width) {
5858 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5859 }
5860
5861 // Test again...if too big still, then give it up.
5862 dc.GetTextExtent(text, &w, &h);
5863
5864 if (w && w > rect.width) {
5865 b_noshow = true;
5866 }
5867 }
5868 }
5869
5870 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5871 }
5872 }
5873
5874 // Maintain member vLat/vLon
5875 m_vLat = VPoint.clat;
5876 m_vLon = VPoint.clon;
5877
5878 return b_ret;
5879}
5880
5881// Static Icon definitions for some symbols requiring
5882// scaling/rotation/translation Very specific wxDC draw commands are
5883// necessary to properly render these icons...See the code in
5884// ShipDraw()
5885
5886// This icon was adapted and scaled from the S52 Presentation Library
5887// version 3_03.
5888// Symbol VECGND02
5889
5890static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5891
5892// This ownship icon was adapted and scaled from the S52 Presentation
5893// Library version 3_03 Symbol OWNSHP05
5894static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5895 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5896
5897wxColour ChartCanvas::PredColor() {
5898 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5899 // visibility.
5900 if (SHIP_NORMAL == m_ownship_state)
5901 return GetGlobalColor("URED");
5902
5903 else if (SHIP_LOWACCURACY == m_ownship_state)
5904 return GetGlobalColor("YELO1");
5905
5906 return GetGlobalColor("NODTA");
5907}
5908
5909wxColour ChartCanvas::ShipColor() {
5910 // Establish ship color
5911 // It changes color based on GPS and Chart accuracy/availability
5912
5913 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5914
5915 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5916
5917 return GetGlobalColor("URED"); // default is OK
5918}
5919
5920void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5921 wxPoint2DDouble lShipMidPoint) {
5922 dc.SetPen(wxPen(PredColor(), 2));
5923
5924 if (SHIP_NORMAL == m_ownship_state)
5925 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5926 else
5927 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5928
5929 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5930 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5931
5932 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5933 lShipMidPoint.m_y);
5934 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5935 lShipMidPoint.m_y + 12);
5936}
5937
5938void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5939 wxPoint GPSOffsetPixels,
5940 wxPoint2DDouble lGPSPoint) {
5941 // if (m_animationActive) return;
5942 // Develop a uniform length for course predictor line dash length, based on
5943 // physical display size Use this reference length to size all other graphics
5944 // elements
5945 float ref_dim = m_display_size_mm / 24;
5946 ref_dim = wxMin(ref_dim, 12);
5947 ref_dim = wxMax(ref_dim, 6);
5948
5949 wxColour cPred;
5950 cPred.Set(g_cog_predictor_color);
5951 if (cPred == wxNullColour) cPred = PredColor();
5952
5953 // Establish some graphic element line widths dependent on the platform
5954 // display resolution
5955 // double nominal_line_width_pix = wxMax(1.0,
5956 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5957 // not less than 1 pixel
5958 double nominal_line_width_pix = wxMax(
5959 1.0,
5960 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5961
5962 // If the calculated value is greater than the config file spec value, then
5963 // use it.
5964 if (nominal_line_width_pix > g_cog_predictor_width)
5965 g_cog_predictor_width = nominal_line_width_pix;
5966
5967 // Calculate ownship Position Predictor
5968 wxPoint lPredPoint, lHeadPoint;
5969
5970 float pCog = std::isnan(gCog) ? 0 : gCog;
5971 float pSog = std::isnan(gSog) ? 0 : gSog;
5972
5973 double pred_lat, pred_lon;
5974 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5975 &pred_lat, &pred_lon);
5976 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5977
5978 // test to catch the case where COG/HDG line crosses the screen
5979 LLBBox box;
5980
5981 // Should we draw the Head vector?
5982 // Compare the points lHeadPoint and lPredPoint
5983 // If they differ by more than n pixels, and the head vector is valid, then
5984 // render the head vector
5985
5986 float ndelta_pix = 10.;
5987 double hdg_pred_lat, hdg_pred_lon;
5988 bool b_render_hdt = false;
5989 if (!std::isnan(gHdt)) {
5990 // Calculate ownship Heading pointer as a predictor
5991 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5992 &hdg_pred_lon);
5993 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5994 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5995 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5996 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5997 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5998 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5999 }
6000 }
6001
6002 // draw course over ground if they are longer than the ship
6003 wxPoint lShipMidPoint;
6004 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6005 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6006 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6007 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6008
6009 if (lpp >= img_height / 2) {
6010 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6011 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6012 !std::isnan(gSog)) {
6013 // COG Predictor
6014 float dash_length = ref_dim;
6015 wxDash dash_long[2];
6016 dash_long[0] =
6017 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6018 g_cog_predictor_width); // Long dash , in mm <---------+
6019 dash_long[1] = dash_long[0] / 2.0; // Short gap
6020
6021 // On ultra-hi-res displays, do not allow the dashes to be greater than
6022 // 250, since it is defined as (char)
6023 if (dash_length > 250.) {
6024 dash_long[0] = 250. / g_cog_predictor_width;
6025 dash_long[1] = dash_long[0] / 2;
6026 }
6027
6028 wxPen ppPen2(cPred, g_cog_predictor_width,
6029 (wxPenStyle)g_cog_predictor_style);
6030 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6031 ppPen2.SetDashes(2, dash_long);
6032 dc.SetPen(ppPen2);
6033 dc.StrokeLine(
6034 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6035 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6036
6037 if (g_cog_predictor_width > 1) {
6038 float line_width = g_cog_predictor_width / 3.;
6039
6040 wxDash dash_long3[2];
6041 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6042 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6043
6044 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6045 (wxPenStyle)g_cog_predictor_style);
6046 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6047 ppPen3.SetDashes(2, dash_long3);
6048 dc.SetPen(ppPen3);
6049 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6050 lGPSPoint.m_y + GPSOffsetPixels.y,
6051 lPredPoint.x + GPSOffsetPixels.x,
6052 lPredPoint.y + GPSOffsetPixels.y);
6053 }
6054
6055 if (g_cog_predictor_endmarker) {
6056 // Prepare COG predictor endpoint icon
6057 double png_pred_icon_scale_factor = .4;
6058 if (g_ShipScaleFactorExp > 1.0)
6059 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6060 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6061
6062 wxPoint icon[4];
6063
6064 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6065 (float)(lPredPoint.x - lShipMidPoint.x));
6066 cog_rad += (float)PI;
6067
6068 for (int i = 0; i < 4; i++) {
6069 int j = i * 2;
6070 double pxa = (double)(s_png_pred_icon[j]);
6071 double pya = (double)(s_png_pred_icon[j + 1]);
6072
6073 pya *= png_pred_icon_scale_factor;
6074 pxa *= png_pred_icon_scale_factor;
6075
6076 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6077 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6078
6079 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6080 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6081 }
6082
6083 // Render COG endpoint icon
6084 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6085 wxPENSTYLE_SOLID);
6086 dc.SetPen(ppPen1);
6087 dc.SetBrush(wxBrush(cPred));
6088
6089 dc.StrokePolygon(4, icon);
6090 }
6091 }
6092 }
6093
6094 // HDT Predictor
6095 if (b_render_hdt) {
6096 float hdt_dash_length = ref_dim * 0.4;
6097
6098 cPred.Set(g_ownship_HDTpredictor_color);
6099 if (cPred == wxNullColour) cPred = PredColor();
6100 float hdt_width =
6101 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6102 : g_cog_predictor_width * 0.8);
6103 wxDash dash_short[2];
6104 dash_short[0] =
6105 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6106 hdt_width); // Short dash , in mm <---------+
6107 dash_short[1] =
6108 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6109 hdt_width); // Short gap |
6110
6111 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6112 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6113 ppPen2.SetDashes(2, dash_short);
6114
6115 dc.SetPen(ppPen2);
6116 dc.StrokeLine(
6117 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6118 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6119
6120 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6121 dc.SetPen(ppPen1);
6122 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6123
6124 if (g_ownship_HDTpredictor_endmarker) {
6125 double nominal_circle_size_pixels = wxMax(
6126 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6127
6128 // Scale the circle to ChartScaleFactor, slightly softened....
6129 if (g_ShipScaleFactorExp > 1.0)
6130 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6131
6132 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6133 lHeadPoint.y + GPSOffsetPixels.y,
6134 nominal_circle_size_pixels / 2);
6135 }
6136 }
6137
6138 // Draw radar rings if activated
6139 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6140 double factor = 1.00;
6141 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6142 factor = 1 / 1.852;
6143 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6144 if (std::isnan(gSog))
6145 factor = 0.0;
6146 else
6147 factor = gSog / 60;
6148 }
6149 factor *= g_fNavAidRadarRingsStep;
6150
6151 double tlat, tlon;
6152 wxPoint r;
6153 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6154 GetCanvasPointPix(tlat, tlon, &r);
6155
6156 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6157 pow((double)(lGPSPoint.m_y - r.y), 2));
6158 int pix_radius = (int)lpp;
6159
6160 wxColor rangeringcolour =
6161 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6162
6163 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6164
6165 dc.SetPen(ppPen1);
6166 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6167
6168 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6169 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6170 }
6171}
6172
6173#if ocpnUSE_GL
6174void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6175bool ChartCanvas::CanAccelerateGlPanning() {
6176 return GetglCanvas()->CanAcceleratePanning();
6177}
6178void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6179
6180#else
6181void ChartCanvas::ResetGlGridFont() {}
6182bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6183void ChartCanvas::SetupGlCompression() {}
6184#endif
6185
6186void ChartCanvas::ComputeShipScaleFactor(
6187 float icon_hdt, int ownShipWidth, int ownShipLength,
6188 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6189 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6190 float screenResolution = m_pix_per_mm;
6191
6192 // Calculate the true ship length in exact pixels
6193 double ship_bow_lat, ship_bow_lon;
6194 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6195 &ship_bow_lat, &ship_bow_lon);
6196 wxPoint lShipBowPoint;
6197 wxPoint2DDouble b_point =
6198 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6199 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6200
6201 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6202 powf((float)(b_point.m_y - a_point.m_y), 2));
6203
6204 // And in mm
6205 float shipLength_mm = shipLength_px / screenResolution;
6206
6207 // Set minimum ownship drawing size
6208 float ownship_min_mm = g_n_ownship_min_mm;
6209 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6210
6211 // Calculate Nautical Miles distance from midships to gps antenna
6212 float hdt_ant = icon_hdt + 180.;
6213 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6214 float dx = g_n_gps_antenna_offset_x / 1852.;
6215 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6216 {
6217 hdt_ant = icon_hdt;
6218 dy = -dy;
6219 }
6220
6221 // If the drawn ship size is going to be clamped, adjust the gps antenna
6222 // offsets
6223 if (shipLength_mm < ownship_min_mm) {
6224 dy /= shipLength_mm / ownship_min_mm;
6225 dx /= shipLength_mm / ownship_min_mm;
6226 }
6227
6228 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6229
6230 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6231 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6232 &ship_mid_lon1);
6233
6234 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6235 &lShipMidPoint);
6236
6237 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6238 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6239
6240 float scale_factor = shipLength_px / ownShipLength;
6241
6242 // Calculate a scale factor that would produce a reasonably sized icon
6243 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6244
6245 // And choose the correct one
6246 scale_factor = wxMax(scale_factor, scale_factor_min);
6247
6248 scale_factor_y = scale_factor;
6249 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6250 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6251}
6252
6253void ChartCanvas::ShipDraw(ocpnDC &dc) {
6254 if (!GetVP().IsValid()) return;
6255
6256 wxPoint GPSOffsetPixels(0, 0);
6257 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6258
6259 // COG/SOG may be undefined in NMEA data stream
6260 float pCog = std::isnan(gCog) ? 0 : gCog;
6261 float pSog = std::isnan(gSog) ? 0 : gSog;
6262
6263 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6264
6265 lShipMidPoint = lGPSPoint;
6266
6267 // Draw the icon rotated to the COG
6268 // or to the Hdt if available
6269 float icon_hdt = pCog;
6270 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6271
6272 // COG may be undefined in NMEA data stream
6273 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6274
6275 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6276 // predictor
6277 double osd_head_lat, osd_head_lon;
6278 wxPoint osd_head_point;
6279
6280 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6281 &osd_head_lon);
6282
6283 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6284
6285 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6286 (float)(osd_head_point.x - lShipMidPoint.m_x));
6287 icon_rad += (float)PI;
6288
6289 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6290
6291 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6292 // nominal size and is just barely outside the viewport ....
6293 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6294
6295 // TODO: fix to include actual size of boat that will be rendered
6296 int img_height = 0;
6297 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6298 if (GetVP().chart_scale >
6299 300000) // According to S52, this should be 50,000
6300 {
6301 ShipDrawLargeScale(dc, lShipMidPoint);
6302 img_height = 20;
6303 } else {
6304 wxImage pos_image;
6305
6306 // Substitute user ownship image if found
6307 if (m_pos_image_user)
6308 pos_image = m_pos_image_user->Copy();
6309 else if (SHIP_NORMAL == m_ownship_state)
6310 pos_image = m_pos_image_red->Copy();
6311 if (SHIP_LOWACCURACY == m_ownship_state)
6312 pos_image = m_pos_image_yellow->Copy();
6313 else if (SHIP_NORMAL != m_ownship_state)
6314 pos_image = m_pos_image_grey->Copy();
6315
6316 // Substitute user ownship image if found
6317 if (m_pos_image_user) {
6318 pos_image = m_pos_image_user->Copy();
6319
6320 if (SHIP_LOWACCURACY == m_ownship_state)
6321 pos_image = m_pos_image_user_yellow->Copy();
6322 else if (SHIP_NORMAL != m_ownship_state)
6323 pos_image = m_pos_image_user_grey->Copy();
6324 }
6325
6326 img_height = pos_image.GetHeight();
6327
6328 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6329 g_OwnShipIconType > 0) // use large ship
6330 {
6331 int ownShipWidth = 22; // Default values from s_ownship_icon
6332 int ownShipLength = 84;
6333 if (g_OwnShipIconType == 1) {
6334 ownShipWidth = pos_image.GetWidth();
6335 ownShipLength = pos_image.GetHeight();
6336 }
6337
6338 float scale_factor_x, scale_factor_y;
6339 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6340 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6341 scale_factor_x, scale_factor_y);
6342
6343 if (g_OwnShipIconType == 1) { // Scaled bitmap
6344 pos_image.Rescale(ownShipWidth * scale_factor_x,
6345 ownShipLength * scale_factor_y,
6346 wxIMAGE_QUALITY_HIGH);
6347 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6348 wxImage rot_image =
6349 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6350
6351 // Simple sharpening algorithm.....
6352 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6353 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6354 if (rot_image.GetAlpha(ip, jp) > 64)
6355 rot_image.SetAlpha(ip, jp, 255);
6356
6357 wxBitmap os_bm(rot_image);
6358
6359 int w = os_bm.GetWidth();
6360 int h = os_bm.GetHeight();
6361 img_height = h;
6362
6363 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6364 lShipMidPoint.m_y - h / 2, true);
6365
6366 // Maintain dirty box,, missing in __WXMSW__ library
6367 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6368 lShipMidPoint.m_y - h / 2);
6369 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6370 lShipMidPoint.m_y - h / 2 + h);
6371 }
6372
6373 else if (g_OwnShipIconType == 2) { // Scaled Vector
6374 wxPoint ownship_icon[10];
6375
6376 for (int i = 0; i < 10; i++) {
6377 int j = i * 2;
6378 float pxa = (float)(s_ownship_icon[j]);
6379 float pya = (float)(s_ownship_icon[j + 1]);
6380 pya *= scale_factor_y;
6381 pxa *= scale_factor_x;
6382
6383 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6384 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6385
6386 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6387 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6388 }
6389
6390 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6391 dc.SetPen(ppPen1);
6392 dc.SetBrush(wxBrush(ShipColor()));
6393
6394 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6395
6396 // draw reference point (midships) cross
6397 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6398 ownship_icon[7].y);
6399 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6400 ownship_icon[9].y);
6401 }
6402
6403 img_height = ownShipLength * scale_factor_y;
6404
6405 // Reference point, where the GPS antenna is
6406 int circle_rad = 3;
6407 if (m_pos_image_user) circle_rad = 1;
6408
6409 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6410 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6411 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6412 } else { // Fixed bitmap icon.
6413 /* non opengl, or suboptimal opengl via ocpndc: */
6414 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6415 wxImage rot_image =
6416 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6417
6418 // Simple sharpening algorithm.....
6419 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6420 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6421 if (rot_image.GetAlpha(ip, jp) > 64)
6422 rot_image.SetAlpha(ip, jp, 255);
6423
6424 wxBitmap os_bm(rot_image);
6425
6426 if (g_ShipScaleFactorExp > 1) {
6427 wxImage scaled_image = os_bm.ConvertToImage();
6428 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6429 1.0; // soften the scale factor a bit
6430 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6431 scaled_image.GetHeight() * factor,
6432 wxIMAGE_QUALITY_HIGH));
6433 }
6434 int w = os_bm.GetWidth();
6435 int h = os_bm.GetHeight();
6436 img_height = h;
6437
6438 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6439 lShipMidPoint.m_y - h / 2, true);
6440
6441 // Reference point, where the GPS antenna is
6442 int circle_rad = 3;
6443 if (m_pos_image_user) circle_rad = 1;
6444
6445 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6446 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6447 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6448
6449 // Maintain dirty box,, missing in __WXMSW__ library
6450 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6451 lShipMidPoint.m_y - h / 2);
6452 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6453 lShipMidPoint.m_y - h / 2 + h);
6454 }
6455 } // ownship draw
6456 }
6457
6458 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6459}
6460
6461/* @ChartCanvas::CalcGridSpacing
6462 **
6463 ** Calculate the major and minor spacing between the lat/lon grid
6464 **
6465 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6466 *window
6467 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6468 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6469 ** @return [void]
6470 */
6471void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6472 float &MinorSpacing) {
6473 // table for calculating the distance between the grids
6474 // [0] view_scale ppm
6475 // [1] spacing between major grid lines in degrees
6476 // [2] spacing between minor grid lines in degrees
6477 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6478 {.000001f, 45.0f, 15.0f},
6479 {.0002f, 30.0f, 10.0f},
6480 {.0003f, 10.0f, 2.0f},
6481 {.0008f, 5.0f, 1.0f},
6482 {.001f, 2.0f, 30.0f / 60.0f},
6483 {.003f, 1.0f, 20.0f / 60.0f},
6484 {.006f, 0.5f, 10.0f / 60.0f},
6485 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6486 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6487 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6488 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6489 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6490 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6491 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6492 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6493
6494 unsigned int tabi;
6495 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6496 if (view_scale_ppm < lltab[tabi][0]) break;
6497 MajorSpacing = lltab[tabi][1]; // major latitude distance
6498 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6499 return;
6500}
6501/* @ChartCanvas::CalcGridText *************************************
6502 **
6503 ** Calculates text to display at the major grid lines
6504 **
6505 ** @param [r] latlon [float] latitude or longitude of grid line
6506 ** @param [r] spacing [float] distance between two major grid lines
6507 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6508 **
6509 ** @return
6510 */
6511
6512wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6513 int deg = (int)fabs(latlon); // degrees
6514 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6515 char postfix;
6516
6517 // calculate postfix letter (NSEW)
6518 if (latlon > 0.0) {
6519 if (bPostfix) {
6520 postfix = 'N';
6521 } else {
6522 postfix = 'E';
6523 }
6524 } else if (latlon < 0.0) {
6525 if (bPostfix) {
6526 postfix = 'S';
6527 } else {
6528 postfix = 'W';
6529 }
6530 } else {
6531 postfix = ' '; // no postfix for equator and greenwich
6532 }
6533 // calculate text, display minutes only if spacing is smaller than one degree
6534
6535 wxString ret;
6536 if (spacing >= 1.0) {
6537 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6538 } else if (spacing >= (1.0 / 60.0)) {
6539 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6540 } else {
6541 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6542 }
6543
6544 return ret;
6545}
6546
6547/* @ChartCanvas::GridDraw *****************************************
6548 **
6549 ** Draws major and minor Lat/Lon Grid on the chart
6550 ** - distance between Grid-lm ines are calculated automatic
6551 ** - major grid lines will be across the whole chart window
6552 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6553 **
6554 ** @param [w] dc [wxDC&] the wx drawing context
6555 **
6556 ** @return [void]
6557 ************************************************************************/
6558void ChartCanvas::GridDraw(ocpnDC &dc) {
6559 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6560
6561 double nlat, elon, slat, wlon;
6562 float lat, lon;
6563 float dlon;
6564 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6565 wxCoord w, h;
6566 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6567 dc.SetPen(GridPen);
6568 if (!m_pgridFont) SetupGridFont();
6569 dc.SetFont(*m_pgridFont);
6570 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6571
6572 w = m_canvas_width;
6573 h = m_canvas_height;
6574
6575 GetCanvasPixPoint(0, 0, nlat,
6576 wlon); // get lat/lon of upper left point of the window
6577 GetCanvasPixPoint(w, h, slat,
6578 elon); // get lat/lon of lower right point of the window
6579 dlon =
6580 elon -
6581 wlon; // calculate how many degrees of longitude are shown in the window
6582 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6583 {
6584 dlon = dlon + 360.0;
6585 }
6586 // calculate distance between latitude grid lines
6587 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6588
6589 // calculate position of first major latitude grid line
6590 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6591
6592 // Draw Major latitude grid lines and text
6593 while (lat < nlat) {
6594 wxPoint r;
6595 wxString st =
6596 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6597 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6598 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6599 dc.DrawText(st, 0, r.y); // draw text
6600 lat = lat + gridlatMajor;
6601
6602 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6603 }
6604
6605 // calculate position of first minor latitude grid line
6606 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6607
6608 // Draw minor latitude grid lines
6609 while (lat < nlat) {
6610 wxPoint r;
6611 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6612 dc.DrawLine(0, r.y, 10, r.y, false);
6613 dc.DrawLine(w - 10, r.y, w, r.y, false);
6614 lat = lat + gridlatMinor;
6615 }
6616
6617 // calculate distance between grid lines
6618 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6619
6620 // calculate position of first major latitude grid line
6621 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6622
6623 // draw major longitude grid lines
6624 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6625 wxPoint r;
6626 wxString st = CalcGridText(lon, gridlonMajor, false);
6627 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6628 dc.DrawLine(r.x, 0, r.x, h, false);
6629 dc.DrawText(st, r.x, 0);
6630 lon = lon + gridlonMajor;
6631 if (lon > 180.0) {
6632 lon = lon - 360.0;
6633 }
6634
6635 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6636 }
6637
6638 // calculate position of first minor longitude grid line
6639 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6640 // draw minor longitude grid lines
6641 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6642 wxPoint r;
6643 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6644 dc.DrawLine(r.x, 0, r.x, 10, false);
6645 dc.DrawLine(r.x, h - 10, r.x, h, false);
6646 lon = lon + gridlonMinor;
6647 if (lon > 180.0) {
6648 lon = lon - 360.0;
6649 }
6650 }
6651}
6652
6653void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6654 if (0 ) {
6655 double blat, blon, tlat, tlon;
6656 wxPoint r;
6657
6658 int x_origin = m_bDisplayGrid ? 60 : 20;
6659 int y_origin = m_canvas_height - 50;
6660
6661 float dist;
6662 int count;
6663 wxPen pen1, pen2;
6664
6665 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6666 {
6667 dist = 10.0;
6668 count = 5;
6669 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6670 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6671 } else // Draw 1 mile scale as SCALEB10
6672 {
6673 dist = 1.0;
6674 count = 10;
6675 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6676 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6677 }
6678
6679 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6680 double rotation = -VPoint.rotation;
6681
6682 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6683 GetCanvasPointPix(tlat, tlon, &r);
6684 int l1 = (y_origin - r.y) / count;
6685
6686 for (int i = 0; i < count; i++) {
6687 int y = l1 * i;
6688 if (i & 1)
6689 dc.SetPen(pen1);
6690 else
6691 dc.SetPen(pen2);
6692
6693 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6694 }
6695 } else {
6696 double blat, blon, tlat, tlon;
6697
6698 int x_origin = 5.0 * GetPixPerMM();
6699 int chartbar_height = GetChartbarHeight();
6700 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6701 // if (style->chartStatusWindowTransparent)
6702 // chartbar_height = 0;
6703 int y_origin = m_canvas_height - chartbar_height - 5;
6704#ifdef __WXOSX__
6705 if (!g_bopengl)
6706 y_origin =
6707 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6708#endif
6709
6710 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6711 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6712
6713 double d;
6714 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6715 d /= 2;
6716
6717 int unit = g_iDistanceFormat;
6718 if (d < .5 &&
6719 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6720 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6721
6722 // nice number
6723 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6724 float places = floor(logdist), rem = logdist - places;
6725 dist = pow(10, places);
6726
6727 if (rem < .2)
6728 dist /= 5;
6729 else if (rem < .5)
6730 dist /= 2;
6731
6732 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6733 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6734 double rotation = -VPoint.rotation;
6735
6736 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6737 &tlat, &tlon);
6738 wxPoint r;
6739 GetCanvasPointPix(tlat, tlon, &r);
6740 int l1 = r.x - x_origin;
6741
6742 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6743 12); // Store this for later reference
6744
6745 dc.SetPen(pen1);
6746
6747 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6748 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6749 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6750
6751 if (!m_pgridFont) SetupGridFont();
6752 dc.SetFont(*m_pgridFont);
6753 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6754 int w, h;
6755 dc.GetTextExtent(s, &w, &h);
6756 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6757 if (g_bopengl) {
6758 w /= dpi_factor;
6759 h /= dpi_factor;
6760 }
6761 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6762 }
6763}
6764
6765void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6766 // Constants?
6767 double da_min = 2.;
6768 double da_max = 6.;
6769 double ra_min = 0.;
6770 double ra_max = 40.;
6771
6772 wxPen pen_save = dc.GetPen();
6773
6774 wxDateTime now = wxDateTime::Now();
6775
6776 dc.SetPen(pen);
6777
6778 int x0, y0, x1, y1;
6779
6780 x0 = x1 = x + radius; // Start point
6781 y0 = y1 = y;
6782 double angle = 0.;
6783 int i = 0;
6784
6785 while (angle < 360.) {
6786 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6787 angle += da;
6788
6789 if (angle > 360.) angle = 360.;
6790
6791 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6792
6793 double r;
6794 if (i & 1)
6795 r = radius + ra;
6796 else
6797 r = radius - ra;
6798
6799 x1 = (int)(x + cos(angle * PI / 180.) * r);
6800 y1 = (int)(y + sin(angle * PI / 180.) * r);
6801
6802 dc.DrawLine(x0, y0, x1, y1);
6803
6804 x0 = x1;
6805 y0 = y1;
6806
6807 i++;
6808 }
6809
6810 dc.DrawLine(x + radius, y, x1, y1); // closure
6811
6812 dc.SetPen(pen_save);
6813}
6814
6815static bool bAnchorSoundPlaying = false;
6816
6817static void onAnchorSoundFinished(void *ptr) {
6818 o_sound::g_anchorwatch_sound->UnLoad();
6819 bAnchorSoundPlaying = false;
6820}
6821
6822void ChartCanvas::AlertDraw(ocpnDC &dc) {
6823 using namespace o_sound;
6824 // Visual and audio alert for anchorwatch goes here
6825 bool play_sound = false;
6826 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6827 if (AnchorAlertOn1) {
6828 wxPoint TargetPoint;
6830 &TargetPoint);
6831 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6832 TargetPoint.y, 100);
6833 play_sound = true;
6834 }
6835 } else
6836 AnchorAlertOn1 = false;
6837
6838 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6839 if (AnchorAlertOn2) {
6840 wxPoint TargetPoint;
6842 &TargetPoint);
6843 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6844 TargetPoint.y, 100);
6845 play_sound = true;
6846 }
6847 } else
6848 AnchorAlertOn2 = false;
6849
6850 if (play_sound) {
6851 if (!bAnchorSoundPlaying) {
6852 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6853 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6854 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6855 if (g_anchorwatch_sound->IsOk()) {
6856 bAnchorSoundPlaying = true;
6857 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6858 g_anchorwatch_sound->Play();
6859 }
6860 }
6861 }
6862}
6863
6864void ChartCanvas::UpdateShips() {
6865 // Get the rectangle in the current dc which bounds the "ownship" symbol
6866
6867 wxClientDC dc(this);
6868 if (!dc.IsOk()) return;
6869
6870 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6871 if (!test_bitmap.IsOk()) return;
6872
6873 wxMemoryDC temp_dc(test_bitmap);
6874
6875 temp_dc.ResetBoundingBox();
6876 temp_dc.DestroyClippingRegion();
6877 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6878
6879 // Draw the ownship on the temp_dc
6880 ocpnDC ocpndc = ocpnDC(temp_dc);
6881 ShipDraw(ocpndc);
6882
6883 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6884 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6885 if (p) {
6886 wxPoint px;
6887 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6888 ocpndc.CalcBoundingBox(px.x, px.y);
6889 }
6890 }
6891
6892 ship_draw_rect =
6893 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6894 temp_dc.MaxY() - temp_dc.MinY());
6895
6896 wxRect own_ship_update_rect = ship_draw_rect;
6897
6898 if (!own_ship_update_rect.IsEmpty()) {
6899 // The required invalidate rectangle is the union of the last drawn
6900 // rectangle and this drawn rectangle
6901 own_ship_update_rect.Union(ship_draw_last_rect);
6902 own_ship_update_rect.Inflate(2);
6903 }
6904
6905 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6906
6907 ship_draw_last_rect = ship_draw_rect;
6908
6909 temp_dc.SelectObject(wxNullBitmap);
6910}
6911
6912void ChartCanvas::UpdateAlerts() {
6913 // Get the rectangle in the current dc which bounds the detected Alert
6914 // targets
6915
6916 // Use this dc
6917 wxClientDC dc(this);
6918
6919 // Get dc boundary
6920 int sx, sy;
6921 dc.GetSize(&sx, &sy);
6922
6923 // Need a bitmap
6924 wxBitmap test_bitmap(sx, sy, -1);
6925
6926 // Create a memory DC
6927 wxMemoryDC temp_dc;
6928 temp_dc.SelectObject(test_bitmap);
6929
6930 temp_dc.ResetBoundingBox();
6931 temp_dc.DestroyClippingRegion();
6932 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6933
6934 // Draw the Alert Targets on the temp_dc
6935 ocpnDC ocpndc = ocpnDC(temp_dc);
6936 AlertDraw(ocpndc);
6937
6938 // Retrieve the drawing extents
6939 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6940 temp_dc.MaxX() - temp_dc.MinX(),
6941 temp_dc.MaxY() - temp_dc.MinY());
6942
6943 if (!alert_rect.IsEmpty())
6944 alert_rect.Inflate(2); // clear all drawing artifacts
6945
6946 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6947 // The required invalidate rectangle is the union of the last drawn
6948 // rectangle and this drawn rectangle
6949 wxRect alert_update_rect = alert_draw_rect;
6950 alert_update_rect.Union(alert_rect);
6951
6952 // Invalidate the rectangular region
6953 RefreshRect(alert_update_rect, false);
6954 }
6955
6956 // Save this rectangle for next time
6957 alert_draw_rect = alert_rect;
6958
6959 temp_dc.SelectObject(wxNullBitmap); // clean up
6960}
6961
6962void ChartCanvas::UpdateAIS() {
6963 if (!g_pAIS) return;
6964
6965 // Get the rectangle in the current dc which bounds the detected AIS targets
6966
6967 // Use this dc
6968 wxClientDC dc(this);
6969
6970 // Get dc boundary
6971 int sx, sy;
6972 dc.GetSize(&sx, &sy);
6973
6974 wxRect ais_rect;
6975
6976 // How many targets are there?
6977
6978 // If more than "some number", it will be cheaper to refresh the entire
6979 // screen than to build update rectangles for each target.
6980 if (g_pAIS->GetTargetList().size() > 10) {
6981 ais_rect = wxRect(0, 0, sx, sy); // full screen
6982 } else {
6983 // Need a bitmap
6984 wxBitmap test_bitmap(sx, sy, -1);
6985
6986 // Create a memory DC
6987 wxMemoryDC temp_dc;
6988 temp_dc.SelectObject(test_bitmap);
6989
6990 temp_dc.ResetBoundingBox();
6991 temp_dc.DestroyClippingRegion();
6992 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6993
6994 // Draw the AIS Targets on the temp_dc
6995 ocpnDC ocpndc = ocpnDC(temp_dc);
6996 AISDraw(ocpndc, GetVP(), this);
6997 AISDrawAreaNotices(ocpndc, GetVP(), this);
6998
6999 // Retrieve the drawing extents
7000 ais_rect =
7001 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7002 temp_dc.MaxY() - temp_dc.MinY());
7003
7004 if (!ais_rect.IsEmpty())
7005 ais_rect.Inflate(2); // clear all drawing artifacts
7006
7007 temp_dc.SelectObject(wxNullBitmap); // clean up
7008 }
7009
7010 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7011 // The required invalidate rectangle is the union of the last drawn
7012 // rectangle and this drawn rectangle
7013 wxRect ais_update_rect = ais_draw_rect;
7014 ais_update_rect.Union(ais_rect);
7015
7016 // Invalidate the rectangular region
7017 RefreshRect(ais_update_rect, false);
7018 }
7019
7020 // Save this rectangle for next time
7021 ais_draw_rect = ais_rect;
7022}
7023
7024void ChartCanvas::ToggleCPAWarn() {
7025 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7026 wxString mess;
7027 if (g_bCPAWarn) {
7028 g_bTCPA_Max = true;
7029 mess = _("ON");
7030 } else {
7031 g_bTCPA_Max = false;
7032 mess = _("OFF");
7033 }
7034 // Print to status bar if available.
7035 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7036 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7037 } else {
7038 if (!g_AisFirstTimeUse) {
7039 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7040 _("CPA") + " " + mess, 4, 4);
7041 }
7042 }
7043}
7044
7045void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7046
7047void ChartCanvas::OnSize(wxSizeEvent &event) {
7048 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7049 // GetClientSize returns the size of the canvas area in logical pixels.
7050 GetClientSize(&m_canvas_width, &m_canvas_height);
7051
7052#ifdef __WXOSX__
7053 // Support scaled HDPI displays.
7054 m_displayScale = GetContentScaleFactor();
7055#endif
7056
7057 // Convert to physical pixels.
7058 m_canvas_width *= m_displayScale;
7059 m_canvas_height *= m_displayScale;
7060
7061 // Resize the current viewport
7062 VPoint.pix_width = m_canvas_width;
7063 VPoint.pix_height = m_canvas_height;
7064 VPoint.SetPixelScale(m_displayScale);
7065
7066 // Get some canvas metrics
7067
7068 // Rescale to current value, in order to rebuild VPoint data
7069 // structures for new canvas size
7071
7072 m_absolute_min_scale_ppm =
7073 m_canvas_width /
7074 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7075
7076 // Inform the parent Frame that I am being resized...
7077 top_frame::Get()->ProcessCanvasResize();
7078
7079 // if MUIBar is active, size the bar
7080 // if(g_useMUI && !m_muiBar){ // rebuild if
7081 // necessary
7082 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7083 // m_muiBarHOSize = m_muiBar->GetSize();
7084 // }
7085
7086 if (m_muiBar) {
7087 SetMUIBarPosition();
7088 UpdateFollowButtonState();
7089 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7090 }
7091
7092 // Set up the scroll margins
7093 xr_margin = m_canvas_width * 95 / 100;
7094 xl_margin = m_canvas_width * 5 / 100;
7095 yt_margin = m_canvas_height * 5 / 100;
7096 yb_margin = m_canvas_height * 95 / 100;
7097
7098 if (m_pQuilt)
7099 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7100
7101 // Resize the scratch BM
7102 delete pscratch_bm;
7103 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7104 m_brepaint_piano = true;
7105
7106 // Resize the Route Calculation BM
7107 m_dc_route.SelectObject(wxNullBitmap);
7108 delete proute_bm;
7109 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7110 m_dc_route.SelectObject(*proute_bm);
7111
7112 // Resize the saved Bitmap
7113 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7114
7115 // Resize the working Bitmap
7116 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7117
7118 // Rescale again, to capture all the changes for new canvas size
7120
7121#ifdef ocpnUSE_GL
7122 if (/*g_bopengl &&*/ m_glcc) {
7123 // FIXME (dave) This can go away?
7124 m_glcc->OnSize(event);
7125 }
7126#endif
7127
7128 FormatPianoKeys();
7129 // Invalidate the whole window
7130 ReloadVP();
7131}
7132
7133void ChartCanvas::ProcessNewGUIScale() {
7134 // m_muiBar->Hide();
7135 delete m_muiBar;
7136 m_muiBar = 0;
7137
7138 CreateMUIBar();
7139}
7140
7141void ChartCanvas::CreateMUIBar() {
7142 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7143 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7144 m_muiBar->SetColorScheme(m_cs);
7145 m_muiBarHOSize = m_muiBar->m_size;
7146 }
7147
7148 if (m_muiBar) {
7149 // We need to update the m_bENCGroup flag, not least for the initial
7150 // creation of a MUIBar
7151 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7152
7153 SetMUIBarPosition();
7154 UpdateFollowButtonState();
7155 m_muiBar->UpdateDynamicValues();
7156 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7157 }
7158}
7159
7160void ChartCanvas::SetMUIBarPosition() {
7161 // if MUIBar is active, size the bar
7162 if (m_muiBar) {
7163 // We estimate the piano width based on the canvas width
7164 int pianoWidth = GetClientSize().x * 0.6f;
7165 // If the piano already exists, we can use its exact width
7166 // if(m_Piano)
7167 // pianoWidth = m_Piano->GetWidth();
7168
7169 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7170 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7171 delete m_muiBar;
7172 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7173 m_muiBar->SetColorScheme(m_cs);
7174 }
7175 }
7176
7177 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7178 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7179 delete m_muiBar;
7180 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7181 m_muiBar->SetColorScheme(m_cs);
7182 }
7183 }
7184
7185 m_muiBar->SetBestPosition();
7186 }
7187}
7188
7189void ChartCanvas::DestroyMuiBar() {
7190 if (m_muiBar) {
7191 delete m_muiBar;
7192 m_muiBar = NULL;
7193 }
7194}
7195
7196void ChartCanvas::ShowCompositeInfoWindow(
7197 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7198 if (n_charts > 0) {
7199 if (NULL == m_pCIWin) {
7200 m_pCIWin = new ChInfoWin(this);
7201 m_pCIWin->Hide();
7202 }
7203
7204 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7205 wxString s;
7206
7207 s = _("Composite of ");
7208
7209 wxString s1;
7210 s1.Printf("%d ", n_charts);
7211 if (n_charts > 1)
7212 s1 += _("charts");
7213 else
7214 s1 += _("chart");
7215 s += s1;
7216 s += '\n';
7217
7218 s1.Printf(_("Chart scale"));
7219 s1 += ": ";
7220 wxString s2;
7221 s2.Printf("1:%d\n", scale);
7222 s += s1;
7223 s += s2;
7224
7225 s1 = _("Zoom in for more information");
7226 s += s1;
7227 s += '\n';
7228
7229 int char_width = s1.Length();
7230 int char_height = 3;
7231
7232 if (g_bChartBarEx) {
7233 s += '\n';
7234 int j = 0;
7235 for (int i : index_vector) {
7236 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7237 wxString path = cte.GetFullSystemPath();
7238 s += path;
7239 s += '\n';
7240 char_height++;
7241 char_width = wxMax(char_width, path.Length());
7242 if (j++ >= 9) break;
7243 }
7244 if (j >= 9) {
7245 s += " .\n .\n .\n";
7246 char_height += 3;
7247 }
7248 s += '\n';
7249 char_height += 1;
7250
7251 char_width += 4; // Fluff
7252 }
7253
7254 m_pCIWin->SetString(s);
7255
7256 m_pCIWin->FitToChars(char_width, char_height);
7257
7258 wxPoint p;
7259 p.x = x / GetContentScaleFactor();
7260 if ((p.x + m_pCIWin->GetWinSize().x) >
7261 (m_canvas_width / GetContentScaleFactor()))
7262 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7263 m_pCIWin->GetWinSize().x) /
7264 2; // centered
7265
7266 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7267 4 - m_pCIWin->GetWinSize().y;
7268
7269 m_pCIWin->dbIndex = 0;
7270 m_pCIWin->chart_scale = 0;
7271 m_pCIWin->SetPosition(p);
7272 m_pCIWin->SetBitmap();
7273 m_pCIWin->Refresh();
7274 m_pCIWin->Show();
7275 }
7276 } else {
7277 HideChartInfoWindow();
7278 }
7279}
7280
7281void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7282 if (dbIndex >= 0) {
7283 if (NULL == m_pCIWin) {
7284 m_pCIWin = new ChInfoWin(this);
7285 m_pCIWin->Hide();
7286 }
7287
7288 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7289 wxString s;
7290 ChartBase *pc = NULL;
7291
7292 // TOCTOU race but worst case will reload chart.
7293 // need to lock it or the background spooler may evict charts in
7294 // OpenChartFromDBAndLock
7295 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7296 pc = ChartData->OpenChartFromDBAndLock(
7297 dbIndex, FULL_INIT); // this must come from cache
7298
7299 int char_width, char_height;
7300 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7301 if (pc) ChartData->UnLockCacheChart(dbIndex);
7302
7303 m_pCIWin->SetString(s);
7304 m_pCIWin->FitToChars(char_width, char_height);
7305
7306 wxPoint p;
7307 p.x = x / GetContentScaleFactor();
7308 if ((p.x + m_pCIWin->GetWinSize().x) >
7309 (m_canvas_width / GetContentScaleFactor()))
7310 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7311 m_pCIWin->GetWinSize().x) /
7312 2; // centered
7313
7314 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7315 4 - m_pCIWin->GetWinSize().y;
7316
7317 m_pCIWin->dbIndex = dbIndex;
7318 m_pCIWin->SetPosition(p);
7319 m_pCIWin->SetBitmap();
7320 m_pCIWin->Refresh();
7321 m_pCIWin->Show();
7322 }
7323 } else {
7324 HideChartInfoWindow();
7325 }
7326}
7327
7328void ChartCanvas::HideChartInfoWindow() {
7329 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7330 m_pCIWin->Hide();
7331 m_pCIWin->Destroy();
7332 m_pCIWin = NULL;
7333
7334#ifdef __ANDROID__
7335 androidForceFullRepaint();
7336#endif
7337 }
7338}
7339
7340void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7341 wxMouseEvent ev(wxEVT_MOTION);
7342 ev.m_x = mouse_x;
7343 ev.m_y = mouse_y;
7344 ev.m_leftDown = mouse_leftisdown;
7345
7346 wxEvtHandler *evthp = GetEventHandler();
7347
7348 ::wxPostEvent(evthp, ev);
7349}
7350
7351void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7352 if ((m_panx_target_final - m_panx_target_now) ||
7353 (m_pany_target_final - m_pany_target_now)) {
7354 DoTimedMovementTarget();
7355 } else
7356 DoTimedMovement();
7357}
7358
7359void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7360
7361bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7362 int delta) {
7363 if (m_disable_edge_pan) return false;
7364
7365 bool bft = false;
7366 int pan_margin = m_canvas_width * margin / 100;
7367 int pan_timer_set = 200;
7368 double pan_delta = GetVP().pix_width * delta / 100;
7369 int pan_x = 0;
7370 int pan_y = 0;
7371
7372 if (x > m_canvas_width - pan_margin) {
7373 bft = true;
7374 pan_x = pan_delta;
7375 }
7376
7377 else if (x < pan_margin) {
7378 bft = true;
7379 pan_x = -pan_delta;
7380 }
7381
7382 if (y < pan_margin) {
7383 bft = true;
7384 pan_y = -pan_delta;
7385 }
7386
7387 else if (y > m_canvas_height - pan_margin) {
7388 bft = true;
7389 pan_y = pan_delta;
7390 }
7391
7392 // Of course, if dragging, and the mouse left button is not down, we must
7393 // stop the event injection
7394 if (bdragging) {
7395 if (!g_btouch) {
7396 wxMouseState state = ::wxGetMouseState();
7397#if wxCHECK_VERSION(3, 0, 0)
7398 if (!state.LeftIsDown())
7399#else
7400 if (!state.LeftDown())
7401#endif
7402 bft = false;
7403 }
7404 }
7405 if ((bft) && !pPanTimer->IsRunning()) {
7406 PanCanvas(pan_x, pan_y);
7407 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7408 return true;
7409 }
7410
7411 // This mouse event must not be due to pan timer event injector
7412 // Mouse is out of the pan zone, so prevent any orphan event injection
7413 if ((!bft) && pPanTimer->IsRunning()) {
7414 pPanTimer->Stop();
7415 }
7416
7417 return (false);
7418}
7419
7420// Look for waypoints at the current position.
7421// Used to determine what a mouse event should act on.
7422
7423void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7424 bool setBeingEdited) {
7425 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7426 m_pRoutePointEditTarget = NULL;
7427 m_pFoundPoint = NULL;
7428
7429 SelectItem *pFind = NULL;
7430 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7431 SelectableItemList SelList = pSelect->FindSelectionList(
7432 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7433 for (SelectItem *pFind : SelList) {
7434 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7435
7436 // Get an array of all routes using this point
7437 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7438 // TODO: delete m_pEditRouteArray after use?
7439
7440 // Use route array to determine actual visibility for the point
7441 bool brp_viz = false;
7442 if (m_pEditRouteArray) {
7443 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7444 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7445 if (pr->IsVisible()) {
7446 brp_viz = true;
7447 break;
7448 }
7449 }
7450 } else
7451 brp_viz = frp->IsVisible(); // isolated point
7452
7453 if (brp_viz) {
7454 // Use route array to rubberband all affected routes
7455 if (m_pEditRouteArray) // Editing Waypoint as part of route
7456 {
7457 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7458 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7459 pr->m_bIsBeingEdited = setBeingEdited;
7460 }
7461 m_bRouteEditing = setBeingEdited;
7462 } else // editing Mark
7463 {
7464 frp->m_bRPIsBeingEdited = setBeingEdited;
7465 m_bMarkEditing = setBeingEdited;
7466 }
7467
7468 m_pRoutePointEditTarget = frp;
7469 m_pFoundPoint = pFind;
7470 break; // out of the while(node)
7471 }
7472 } // for (SelectItem...
7473}
7474std::shared_ptr<HostApi121::PiPointContext>
7475ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7476 // General Right Click
7477 // Look for selectable objects
7478 double slat, slon;
7479 GetCanvasPixPoint(x, y, slat, slon);
7480
7481 SelectItem *pFindAIS;
7482 SelectItem *pFindRP;
7483 SelectItem *pFindRouteSeg;
7484 SelectItem *pFindTrackSeg;
7485 SelectItem *pFindCurrent = NULL;
7486 SelectItem *pFindTide = NULL;
7487
7488 // Get all the selectable things at the selected point
7489 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7490 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7491 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7492 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7493 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7494
7495 if (m_bShowCurrent)
7496 pFindCurrent =
7497 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7498
7499 if (m_bShowTide) // look for tide stations
7500 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7501
7502 int seltype = 0;
7503
7504 // Try for AIS targets first
7505 int FoundAIS_MMSI = 0;
7506 if (pFindAIS) {
7507 FoundAIS_MMSI = pFindAIS->GetUserData();
7508
7509 // Make sure the target data is available
7510 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7511 seltype |= SELTYPE_AISTARGET;
7512 }
7513
7514 // Now the various Route Parts
7515
7516 RoutePoint *FoundRoutePoint = NULL;
7517 Route *SelectedRoute = NULL;
7518
7519 if (pFindRP) {
7520 RoutePoint *pFirstVizPoint = NULL;
7521 RoutePoint *pFoundActiveRoutePoint = NULL;
7522 RoutePoint *pFoundVizRoutePoint = NULL;
7523 Route *pSelectedActiveRoute = NULL;
7524 Route *pSelectedVizRoute = NULL;
7525
7526 // There is at least one routepoint, so get the whole list
7527 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7528 SelectableItemList SelList =
7529 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7530 for (SelectItem *pFindSel : SelList) {
7531 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7532
7533 // Get an array of all routes using this point
7534 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7535
7536 // Use route array (if any) to determine actual visibility for this point
7537 bool brp_viz = false;
7538 if (proute_array) {
7539 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7540 Route *pr = (Route *)proute_array->Item(ir);
7541 if (pr->IsVisible()) {
7542 brp_viz = true;
7543 break;
7544 }
7545 }
7546 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7547 // but still exists as a waypoint
7548 brp_viz = prp->IsVisible(); // so treat as isolated point
7549
7550 } else
7551 brp_viz = prp->IsVisible(); // isolated point
7552
7553 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7554
7555 // Use route array to choose the appropriate route
7556 // Give preference to any active route, otherwise select the first visible
7557 // route in the array for this point
7558 if (proute_array) {
7559 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7560 Route *pr = (Route *)proute_array->Item(ir);
7561 if (pr->m_bRtIsActive) {
7562 pSelectedActiveRoute = pr;
7563 pFoundActiveRoutePoint = prp;
7564 break;
7565 }
7566 }
7567
7568 if (NULL == pSelectedVizRoute) {
7569 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7570 Route *pr = (Route *)proute_array->Item(ir);
7571 if (pr->IsVisible()) {
7572 pSelectedVizRoute = pr;
7573 pFoundVizRoutePoint = prp;
7574 break;
7575 }
7576 }
7577 }
7578
7579 delete proute_array;
7580 }
7581 }
7582
7583 // Now choose the "best" selections
7584 if (pFoundActiveRoutePoint) {
7585 FoundRoutePoint = pFoundActiveRoutePoint;
7586 SelectedRoute = pSelectedActiveRoute;
7587 } else if (pFoundVizRoutePoint) {
7588 FoundRoutePoint = pFoundVizRoutePoint;
7589 SelectedRoute = pSelectedVizRoute;
7590 } else
7591 // default is first visible point in list
7592 FoundRoutePoint = pFirstVizPoint;
7593
7594 if (SelectedRoute) {
7595 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7596 } else if (FoundRoutePoint) {
7597 seltype |= SELTYPE_MARKPOINT;
7598 }
7599
7600 // Highlight the selected point, to verify the proper right click selection
7601#if 0
7602 if (m_pFoundRoutePoint) {
7603 m_pFoundRoutePoint->m_bPtIsSelected = true;
7604 wxRect wp_rect;
7605 RoutePointGui(*m_pFoundRoutePoint)
7606 .CalculateDCRect(m_dc_route, this, &wp_rect);
7607 RefreshRect(wp_rect, true);
7608 }
7609#endif
7610 }
7611
7612 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7613 // routes But call the popup handler with identifier appropriate to the type
7614 if (pFindRouteSeg) // there is at least one select item
7615 {
7616 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7617 SelectableItemList SelList =
7618 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7619
7620 if (NULL == SelectedRoute) // the case where a segment only is selected
7621 {
7622 // Choose the first visible route containing segment in the list
7623 for (SelectItem *pFindSel : SelList) {
7624 Route *pr = (Route *)pFindSel->m_pData3;
7625 if (pr->IsVisible()) {
7626 SelectedRoute = pr;
7627 break;
7628 }
7629 }
7630 }
7631
7632 if (SelectedRoute) {
7633 if (NULL == FoundRoutePoint)
7634 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7635
7636 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7637 seltype |= SELTYPE_ROUTESEGMENT;
7638 }
7639 }
7640
7641 if (pFindTrackSeg) {
7642 m_pSelectedTrack = NULL;
7643 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7644 SelectableItemList SelList =
7645 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7646
7647 // Choose the first visible track containing segment in the list
7648 for (SelectItem *pFindSel : SelList) {
7649 Track *pt = (Track *)pFindSel->m_pData3;
7650 if (pt->IsVisible()) {
7651 m_pSelectedTrack = pt;
7652 break;
7653 }
7654 }
7655 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7656 }
7657
7658 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7659
7660 // Populate the return struct
7661 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7662 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7663 rstruct->object_ident = "";
7664
7665 if (seltype == SELTYPE_AISTARGET) {
7666 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7667 wxString val;
7668 val.Printf("%d", FoundAIS_MMSI);
7669 rstruct->object_ident = val.ToStdString();
7670 } else if (seltype & SELTYPE_MARKPOINT) {
7671 if (FoundRoutePoint) {
7672 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7673 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7674 }
7675 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7676 if (SelectedRoute) {
7677 rstruct->object_type =
7678 HostApi121::PiContextObjectType::kObjectRoutesegment;
7679 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7680 }
7681 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7682 if (m_pSelectedTrack) {
7683 rstruct->object_type =
7684 HostApi121::PiContextObjectType::kObjectTracksegment;
7685 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7686 }
7687 }
7688
7689 return rstruct;
7690}
7691
7692void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7693 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7694 singleClickEventIsValid = false;
7695 m_DoubleClickTimer->Stop();
7696}
7697
7698bool leftIsDown;
7699
7700bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7701 if (!m_bChartDragging && !m_bDrawingRoute) {
7702 /*
7703 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7704 * mouse event coordinates are in logical pixels.
7705 */
7706 if (m_Compass && m_Compass->IsShown()) {
7707 wxRect logicalRect = m_Compass->GetLogicalRect();
7708 bool isInCompass = logicalRect.Contains(event.GetPosition());
7709 if (isInCompass || m_mouseWasInCompass) {
7710 if (m_Compass->MouseEvent(event)) {
7711 cursor_region = CENTER;
7712 if (!g_btouch) SetCanvasCursor(event);
7713 m_mouseWasInCompass = isInCompass;
7714 return true;
7715 }
7716 }
7717 m_mouseWasInCompass = isInCompass;
7718 }
7719
7720 if (m_notification_button && m_notification_button->IsShown()) {
7721 wxRect logicalRect = m_notification_button->GetLogicalRect();
7722 bool isinButton = logicalRect.Contains(event.GetPosition());
7723 if (isinButton) {
7724 SetCursor(*pCursorArrow);
7725 if (event.LeftDown()) HandleNotificationMouseClick();
7726 return true;
7727 }
7728 }
7729
7730 if (MouseEventToolbar(event)) return true;
7731
7732 if (MouseEventChartBar(event)) return true;
7733
7734 if (MouseEventMUIBar(event)) return true;
7735
7736 if (MouseEventIENCBar(event)) return true;
7737 }
7738 return false;
7739}
7740
7741void ChartCanvas::HandleNotificationMouseClick() {
7742 if (!m_NotificationsList) {
7743 m_NotificationsList = new NotificationsList(this);
7744
7745 // calculate best size for Notification list
7746 m_NotificationsList->RecalculateSize();
7747 m_NotificationsList->Hide();
7748 }
7749
7750 if (m_NotificationsList->IsShown()) {
7751 m_NotificationsList->Hide();
7752 } else {
7753 m_NotificationsList->RecalculateSize();
7754 m_NotificationsList->ReloadNotificationList();
7755 m_NotificationsList->Show();
7756 }
7757}
7758bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7759 if (!g_bShowChartBar) return false;
7760
7761 if (!m_Piano->MouseEvent(event)) return false;
7762
7763 cursor_region = CENTER;
7764 if (!g_btouch) SetCanvasCursor(event);
7765 return true;
7766}
7767
7768bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7769 if (!IsPrimaryCanvas()) return false;
7770
7771 if (g_MainToolbar) {
7772 if (!g_MainToolbar->MouseEvent(event))
7773 return false;
7774 else
7775 g_MainToolbar->RefreshToolbar();
7776 }
7777
7778 cursor_region = CENTER;
7779 if (!g_btouch) SetCanvasCursor(event);
7780 return true;
7781}
7782
7783bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7784 if (!IsPrimaryCanvas()) return false;
7785
7786 if (g_iENCToolbar) {
7787 if (!g_iENCToolbar->MouseEvent(event))
7788 return false;
7789 else {
7790 g_iENCToolbar->RefreshToolbar();
7791 return true;
7792 }
7793 }
7794 return false;
7795}
7796
7797bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7798 if (m_muiBar) {
7799 if (!m_muiBar->MouseEvent(event)) return false;
7800 }
7801
7802 cursor_region = CENTER;
7803 if (!g_btouch) SetCanvasCursor(event);
7804 if (m_muiBar)
7805 return true;
7806 else
7807 return false;
7808}
7809
7810bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7811 int x, y;
7812
7813 bool bret = false;
7814
7815 event.GetPosition(&x, &y);
7816
7817 x *= m_displayScale;
7818 y *= m_displayScale;
7819
7820 m_MouseDragging = event.Dragging();
7821
7822 // Some systems produce null drag events, where the pointer position has not
7823 // changed from the previous value. Detect this case, and abort further
7824 // processing (FS#1748)
7825#ifdef __WXMSW__
7826 if (event.Dragging()) {
7827 if ((x == mouse_x) && (y == mouse_y)) return true;
7828 }
7829#endif
7830
7831 mouse_x = x;
7832 mouse_y = y;
7833 mouse_leftisdown = event.LeftDown();
7835
7836 // Establish the event region
7837 cursor_region = CENTER;
7838
7839 int chartbar_height = GetChartbarHeight();
7840
7841 if (m_Compass && m_Compass->IsShown() &&
7842 m_Compass->GetRect().Contains(event.GetPosition())) {
7843 cursor_region = CENTER;
7844 } else if (x > xr_margin) {
7845 cursor_region = MID_RIGHT;
7846 } else if (x < xl_margin) {
7847 cursor_region = MID_LEFT;
7848 } else if (y > yb_margin - chartbar_height &&
7849 y < m_canvas_height - chartbar_height) {
7850 cursor_region = MID_TOP;
7851 } else if (y < yt_margin) {
7852 cursor_region = MID_BOT;
7853 } else {
7854 cursor_region = CENTER;
7855 }
7856
7857 if (!g_btouch) SetCanvasCursor(event);
7858
7859 // Protect from leftUp's coming from event handlers in child
7860 // windows who return focus to the canvas.
7861 leftIsDown = event.LeftDown();
7862
7863#ifndef __WXOSX__
7864 if (event.LeftDown()) {
7865 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7866 // The menu bar is temporarily visible due to alt having been pressed.
7867 // Clicking will hide it, and do nothing else.
7868 g_bTempShowMenuBar = false;
7869 top_frame::Get()->ApplyGlobalSettings(false);
7870 return (true);
7871 }
7872 }
7873#endif
7874
7875 // Update modifiers here; some window managers never send the key event
7876 m_modkeys = 0;
7877 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7878 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7879
7880#ifdef __WXMSW__
7881 // TODO Test carefully in other platforms, remove ifdef....
7882 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7883 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7884#endif
7885
7886 event.SetEventObject(this);
7887 if (SendMouseEventToPlugins(event))
7888 return (true); // PlugIn did something, and does not want the canvas to
7889 // do anything else
7890
7891 // Capture LeftUp's and time them, unless it already came from the timer.
7892
7893 // Detect end of chart dragging
7894 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7895 StartChartDragInertia();
7896 }
7897
7898 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7899 !singleClickEventIsValid) {
7900 // Ignore the second LeftUp after the DClick.
7901 if (m_DoubleClickTimer->IsRunning()) {
7902 m_DoubleClickTimer->Stop();
7903 return (true);
7904 }
7905
7906 // Save the event for later running if there is no DClick.
7907 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7908 singleClickEvent = event;
7909 singleClickEventIsValid = true;
7910 return (true);
7911 }
7912
7913 // This logic is necessary on MSW to handle the case where
7914 // a context (right-click) menu is dismissed without action
7915 // by clicking on the chart surface.
7916 // We need to avoid an unintentional pan by eating some clicks...
7917#ifdef __WXMSW__
7918 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7919 if (g_click_stop > 0) {
7920 g_click_stop--;
7921 return (true);
7922 }
7923 }
7924#endif
7925
7926 // Kick off the Rotation control timer
7927 if (GetUpMode() == COURSE_UP_MODE) {
7928 m_b_rot_hidef = false;
7929 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7930 } else
7931 pRotDefTimer->Stop();
7932
7933 // Retrigger the route leg / AIS target popup timer
7934 bool bRoll = !g_btouch;
7935#ifdef __ANDROID__
7936 bRoll = g_bRollover;
7937#endif
7938 if (bRoll) {
7939 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7940 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7941 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7942 m_RolloverPopupTimer.Start(
7943 10,
7944 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7945 else
7946 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7947 }
7948
7949 // Retrigger the cursor tracking timer
7950 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7951
7952// Show cursor position on Status Bar, if present
7953// except for GTK, under which status bar updates are very slow
7954// due to Update() call.
7955// In this case, as a workaround, update the status window
7956// after an interval timer (pCurTrackTimer) pops, which will happen
7957// whenever the mouse has stopped moving for specified interval.
7958// See the method OnCursorTrackTimerEvent()
7959#if !defined(__WXGTK__) && !defined(__WXQT__)
7960 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7961#endif
7962
7963 // Send the current cursor lat/lon to all PlugIns requesting it
7964 if (g_pi_manager) {
7965 // Occasionally, MSW will produce nonsense events on right click....
7966 // This results in an error in cursor geo position, so we skip this case
7967 if ((x >= 0) && (y >= 0))
7968 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7969 }
7970
7971 if (!g_btouch) {
7972 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7973 wxPoint p = ClientToScreen(wxPoint(x, y));
7974 }
7975 }
7976
7977 if (1 ) {
7978 // Route Creation Rubber Banding
7979 if (m_routeState >= 2) {
7980 r_rband.x = x;
7981 r_rband.y = y;
7982 m_bDrawingRoute = true;
7983
7984 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7985 Refresh(false);
7986 }
7987
7988 // Measure Tool Rubber Banding
7989 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7990 r_rband.x = x;
7991 r_rband.y = y;
7992 m_bDrawingRoute = true;
7993
7994 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7995 Refresh(false);
7996 }
7997 }
7998 return bret;
7999}
8000
8001int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8002 // On general Right Click
8003 // Look for selectable objects
8004 double slat = lat;
8005 double slon = lon;
8006
8007#if defined(__WXMAC__) || defined(__ANDROID__)
8008 wxScreenDC sdc;
8009 ocpnDC dc(sdc);
8010#else
8011 wxClientDC cdc(GetParent());
8012 ocpnDC dc(cdc);
8013#endif
8014
8015 SelectItem *pFindAIS;
8016 SelectItem *pFindRP;
8017 SelectItem *pFindRouteSeg;
8018 SelectItem *pFindTrackSeg;
8019 SelectItem *pFindCurrent = NULL;
8020 SelectItem *pFindTide = NULL;
8021
8022 // Deselect any current objects
8023 if (m_pSelectedRoute) {
8024 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8025 m_pSelectedRoute->DeSelectRoute();
8026#ifdef ocpnUSE_GL
8027 if (g_bopengl && m_glcc) {
8028 InvalidateGL();
8029 Update();
8030 } else
8031#endif
8032 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8033 }
8034
8035 if (m_pFoundRoutePoint) {
8036 m_pFoundRoutePoint->m_bPtIsSelected = false;
8037 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8038 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8039 }
8040
8043 if (g_btouch && m_pRoutePointEditTarget) {
8044 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8045 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8046 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8047 }
8048
8049 // Get all the selectable things at the cursor
8050 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8051 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8052 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8053 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8054 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8055
8056 if (m_bShowCurrent)
8057 pFindCurrent =
8058 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8059
8060 if (m_bShowTide) // look for tide stations
8061 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8062
8063 int seltype = 0;
8064
8065 // Try for AIS targets first
8066 if (pFindAIS) {
8067 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8068
8069 // Make sure the target data is available
8070 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8071 seltype |= SELTYPE_AISTARGET;
8072 }
8073
8074 // Now examine the various Route parts
8075
8076 m_pFoundRoutePoint = NULL;
8077 if (pFindRP) {
8078 RoutePoint *pFirstVizPoint = NULL;
8079 RoutePoint *pFoundActiveRoutePoint = NULL;
8080 RoutePoint *pFoundVizRoutePoint = NULL;
8081 Route *pSelectedActiveRoute = NULL;
8082 Route *pSelectedVizRoute = NULL;
8083
8084 // There is at least one routepoint, so get the whole list
8085 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8086 SelectableItemList SelList =
8087 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8088 for (SelectItem *pFindSel : SelList) {
8089 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8090
8091 // Get an array of all routes using this point
8092 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8093
8094 // Use route array (if any) to determine actual visibility for this point
8095 bool brp_viz = false;
8096 if (proute_array) {
8097 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8098 Route *pr = (Route *)proute_array->Item(ir);
8099 if (pr->IsVisible()) {
8100 brp_viz = true;
8101 break;
8102 }
8103 }
8104 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8105 // but still exists as a waypoint
8106 brp_viz = prp->IsVisible(); // so treat as isolated point
8107
8108 } else
8109 brp_viz = prp->IsVisible(); // isolated point
8110
8111 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8112
8113 // Use route array to choose the appropriate route
8114 // Give preference to any active route, otherwise select the first visible
8115 // route in the array for this point
8116 m_pSelectedRoute = NULL;
8117 if (proute_array) {
8118 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8119 Route *pr = (Route *)proute_array->Item(ir);
8120 if (pr->m_bRtIsActive) {
8121 pSelectedActiveRoute = pr;
8122 pFoundActiveRoutePoint = prp;
8123 break;
8124 }
8125 }
8126
8127 if (NULL == pSelectedVizRoute) {
8128 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8129 Route *pr = (Route *)proute_array->Item(ir);
8130 if (pr->IsVisible()) {
8131 pSelectedVizRoute = pr;
8132 pFoundVizRoutePoint = prp;
8133 break;
8134 }
8135 }
8136 }
8137
8138 delete proute_array;
8139 }
8140 }
8141
8142 // Now choose the "best" selections
8143 if (pFoundActiveRoutePoint) {
8144 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8145 m_pSelectedRoute = pSelectedActiveRoute;
8146 } else if (pFoundVizRoutePoint) {
8147 m_pFoundRoutePoint = pFoundVizRoutePoint;
8148 m_pSelectedRoute = pSelectedVizRoute;
8149 } else
8150 // default is first visible point in list
8151 m_pFoundRoutePoint = pFirstVizPoint;
8152
8153 if (m_pSelectedRoute) {
8154 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8155 } else if (m_pFoundRoutePoint) {
8156 seltype |= SELTYPE_MARKPOINT;
8157 }
8158
8159 // Highlight the selected point, to verify the proper right click selection
8160 if (m_pFoundRoutePoint) {
8161 m_pFoundRoutePoint->m_bPtIsSelected = true;
8162 wxRect wp_rect;
8163 RoutePointGui(*m_pFoundRoutePoint)
8164 .CalculateDCRect(m_dc_route, this, &wp_rect);
8165 RefreshRect(wp_rect, true);
8166 }
8167 }
8168
8169 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8170 // routes But call the popup handler with identifier appropriate to the type
8171 if (pFindRouteSeg) // there is at least one select item
8172 {
8173 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8174 SelectableItemList SelList =
8175 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8176
8177 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8178 {
8179 // Choose the first visible route containing segment in the list
8180 for (SelectItem *pFindSel : SelList) {
8181 Route *pr = (Route *)pFindSel->m_pData3;
8182 if (pr->IsVisible()) {
8183 m_pSelectedRoute = pr;
8184 break;
8185 }
8186 }
8187 }
8188
8189 if (m_pSelectedRoute) {
8190 if (NULL == m_pFoundRoutePoint)
8191 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8192
8193 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8194 if (m_pSelectedRoute->m_bRtIsSelected) {
8195#ifdef ocpnUSE_GL
8196 if (g_bopengl && m_glcc) {
8197 InvalidateGL();
8198 Update();
8199 } else
8200#endif
8201 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8202 }
8203 seltype |= SELTYPE_ROUTESEGMENT;
8204 }
8205 }
8206
8207 if (pFindTrackSeg) {
8208 m_pSelectedTrack = NULL;
8209 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8210 SelectableItemList SelList =
8211 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8212
8213 // Choose the first visible track containing segment in the list
8214 for (SelectItem *pFindSel : SelList) {
8215 Track *pt = (Track *)pFindSel->m_pData3;
8216 if (pt->IsVisible()) {
8217 m_pSelectedTrack = pt;
8218 break;
8219 }
8220 }
8221 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8222 }
8223
8224#if 0 // disable tide and current graph on right click
8225 {
8226 if (pFindCurrent) {
8227 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8228 seltype |= SELTYPE_CURRENTPOINT;
8229 }
8230
8231 else if (pFindTide) {
8232 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8233 seltype |= SELTYPE_TIDEPOINT;
8234 }
8235 }
8236#endif
8237
8238 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8239
8240 return seltype;
8241}
8242
8243IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8244 // There may be multiple current entries at the same point.
8245 // For example, there often is a current substation (with directions
8246 // specified) co-located with its master. We want to select the
8247 // substation, so that the direction will be properly indicated on the
8248 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8249 // substation)
8250 IDX_entry *pIDX_best_candidate;
8251
8252 SelectItem *pFind = NULL;
8253 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8254 SelectableItemList SelList =
8255 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8256
8257 // Default is first entry
8258 pFind = *SelList.begin();
8259 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8260
8261 auto node = SelList.begin();
8262 if (SelList.size() > 1) {
8263 for (++node; node != SelList.end(); ++node) {
8264 pFind = *node;
8265 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8266 if (pIDX_candidate->IDX_type == 'c') {
8267 pIDX_best_candidate = pIDX_candidate;
8268 break;
8269 }
8270 } // while (node)
8271 } else {
8272 pFind = *SelList.begin();
8273 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8274 }
8275
8276 return pIDX_best_candidate;
8277}
8278void ChartCanvas::CallPopupMenu(int x, int y) {
8279 last_drag.x = x;
8280 last_drag.y = y;
8281 if (m_routeState) { // creating route?
8282 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8283 return;
8284 }
8285
8287
8288 // If tide or current point is selected, then show the TC dialog immediately
8289 // without context menu
8290 if (SELTYPE_CURRENTPOINT == seltype) {
8291 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8292 Refresh(false);
8293 return;
8294 }
8295
8296 if (SELTYPE_TIDEPOINT == seltype) {
8297 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8298 Refresh(false);
8299 return;
8300 }
8301
8302 InvokeCanvasMenu(x, y, seltype);
8303
8304 // Clean up if not deleted in InvokeCanvasMenu
8305 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8306 m_pSelectedRoute->m_bRtIsSelected = false;
8307 }
8308
8309 m_pSelectedRoute = NULL;
8310
8311 if (m_pFoundRoutePoint) {
8312 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8313 m_pFoundRoutePoint->m_bPtIsSelected = false;
8314 }
8315 m_pFoundRoutePoint = NULL;
8316
8317 Refresh(true);
8318 // Refresh(false); // needed for MSW, not GTK Why??
8319}
8320
8321bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8322 // For now just bail out completely if the point clicked is not on the chart
8323 if (std::isnan(m_cursor_lat)) return false;
8324
8325 // Mouse Clicks
8326 bool ret = false; // return true if processed
8327
8328 int x, y, mx, my;
8329 event.GetPosition(&x, &y);
8330 mx = x;
8331 my = y;
8332
8333 // Calculate meaningful SelectRadius
8334 float SelectRadius;
8335 SelectRadius = g_Platform->GetSelectRadiusPix() /
8336 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8337
8339 // We start with Double Click processing. The first left click just starts a
8340 // timer and is remembered, then we actually do something if there is a
8341 // LeftDClick. If there is, the two single clicks are ignored.
8342
8343 if (event.LeftDClick() && (cursor_region == CENTER)) {
8344 m_DoubleClickTimer->Start();
8345 singleClickEventIsValid = false;
8346
8347 double zlat, zlon;
8349 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8350
8351 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8352 if (m_bShowAIS) {
8353 SelectItem *pFindAIS;
8354 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8355
8356 if (pFindAIS) {
8357 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8358 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8359 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8360 }
8361 return true;
8362 }
8363 }
8364
8365 SelectableItemList rpSelList =
8366 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8367 bool b_onRPtarget = false;
8368 for (SelectItem *pFind : rpSelList) {
8369 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8370 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8371 b_onRPtarget = true;
8372 break;
8373 }
8374 }
8375
8376 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8377
8378 // Get and honor the plugin API ContextMenuMask
8379 std::unique_ptr<HostApi> host_api = GetHostApi();
8380 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8381
8382 if (m_pRoutePointEditTarget) {
8383 if (b_onRPtarget) {
8384 if ((api_121->GetContextMenuMask() &
8385 api_121->kContextMenuDisableWaypoint))
8386 return true;
8387 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8388 return true;
8389 } else {
8390 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8391 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8392 if (g_btouch)
8393 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8394 wxRect wp_rect;
8395 RoutePointGui(*m_pRoutePointEditTarget)
8396 .CalculateDCRect(m_dc_route, this, &wp_rect);
8397 m_pRoutePointEditTarget = NULL; // cancel selection
8398 RefreshRect(wp_rect, true);
8399 return true;
8400 }
8401 } else {
8402 auto node = rpSelList.begin();
8403 if (node != rpSelList.end()) {
8404 SelectItem *pFind = *node;
8405 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8406 if (frp) {
8407 wxArrayPtrVoid *proute_array =
8409
8410 // Use route array (if any) to determine actual visibility for this
8411 // point
8412 bool brp_viz = false;
8413 if (proute_array) {
8414 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8415 Route *pr = (Route *)proute_array->Item(ir);
8416 if (pr->IsVisible()) {
8417 brp_viz = true;
8418 break;
8419 }
8420 }
8421 delete proute_array;
8422 if (!brp_viz &&
8423 frp->IsShared()) // is not visible as part of route, but
8424 // still exists as a waypoint
8425 brp_viz = frp->IsVisible(); // so treat as isolated point
8426 } else
8427 brp_viz = frp->IsVisible(); // isolated point
8428
8429 if (brp_viz) {
8430 if ((api_121->GetContextMenuMask() &
8431 api_121->kContextMenuDisableWaypoint))
8432 return true;
8433
8434 ShowMarkPropertiesDialog(frp);
8435 return true;
8436 }
8437 }
8438 }
8439 }
8440
8441 SelectItem *cursorItem;
8442
8443 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8444 if (cursorItem) {
8445 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8446 return true;
8447 Route *pr = (Route *)cursorItem->m_pData3;
8448 if (pr->IsVisible()) {
8449 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8450 return true;
8451 }
8452 }
8453
8454 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8455 if (cursorItem) {
8456 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8457 return true;
8458 Track *pt = (Track *)cursorItem->m_pData3;
8459 if (pt->IsVisible()) {
8460 ShowTrackPropertiesDialog(pt);
8461 return true;
8462 }
8463 }
8464
8465 // Tide and current points
8466 SelectItem *pFindCurrent = NULL;
8467 SelectItem *pFindTide = NULL;
8468
8469 if (m_bShowCurrent) { // look for current stations
8470 pFindCurrent =
8471 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8472 if (pFindCurrent) {
8473 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8474 // Check for plugin graphic override
8475 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8476 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8477 PlugInContainer *pic = plugin_array->Item(i);
8478 if (pic->m_enabled && pic->m_init_state &&
8479 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8480 if (ptcmgr) {
8481 TCClickInfo info;
8482 if (m_pIDXCandidate) {
8483 info.point_type = CURRENT_STATION;
8484 info.index = m_pIDXCandidate->IDX_rec_num;
8485 info.name = m_pIDXCandidate->IDX_station_name;
8486 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8487 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8488 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8489 };
8490 auto plugin =
8491 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8492 if (plugin) plugin->OnTideCurrentClick(info);
8493 return true;
8494 }
8495 }
8496 }
8497 }
8498
8499 // Default action
8500 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8501 Refresh(false);
8502 return true;
8503 }
8504 }
8505
8506 if (m_bShowTide) { // look for tide stations
8507 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8508 if (pFindTide) {
8509 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8510 // Check for plugin graphic override
8511 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8512 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8513 PlugInContainer *pic = plugin_array->Item(i);
8514 if (pic->m_enabled && pic->m_init_state &&
8515 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8516 if (ptcmgr) {
8517 TCClickInfo info;
8518 if (m_pIDXCandidate) {
8519 info.point_type = TIDE_STATION;
8520 info.index = m_pIDXCandidate->IDX_rec_num;
8521 info.name = m_pIDXCandidate->IDX_station_name;
8522 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8523 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8524 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8525 };
8526 auto plugin =
8527 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8528 if (plugin) plugin->OnTideCurrentClick(info);
8529 return true;
8530 }
8531 }
8532 }
8533 }
8534
8535 // Default action
8536 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8537 Refresh(false);
8538 return true;
8539 }
8540 }
8541
8542 // Found no object to act on, so show chart info.
8543 ShowObjectQueryWindow(x, y, zlat, zlon);
8544 return true;
8545 }
8546
8548 if (event.LeftDown()) {
8549 // This really should not be needed, but....
8550 // on Windows, when using wxAUIManager, sometimes the focus is lost
8551 // when clicking into another pane, e.g.the AIS target list, and then back
8552 // to this pane. Oddly, some mouse events are not lost, however. Like this
8553 // one....
8554 SetFocus();
8555
8556 last_drag.x = mx;
8557 last_drag.y = my;
8558 leftIsDown = true;
8559
8560 if (!g_btouch) {
8561 if (m_routeState) // creating route?
8562 {
8563 double rlat, rlon;
8564 bool appending = false;
8565 bool inserting = false;
8566 Route *tail = 0;
8567
8568 SetCursor(*pCursorPencil);
8569 rlat = m_cursor_lat;
8570 rlon = m_cursor_lon;
8571
8572 m_bRouteEditing = true;
8573
8574 if (m_routeState == 1) {
8575 m_pMouseRoute = new Route();
8576 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8577 pRouteList->push_back(m_pMouseRoute);
8578 r_rband.x = x;
8579 r_rband.y = y;
8580 }
8581
8582 // Check to see if there is a nearby point which may be reused
8583 RoutePoint *pMousePoint = NULL;
8584
8585 // Calculate meaningful SelectRadius
8586 double nearby_radius_meters =
8587 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8588
8589 RoutePoint *pNearbyPoint =
8590 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8591 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8592 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8593 wxArrayPtrVoid *proute_array =
8594 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8595
8596 // Use route array (if any) to determine actual visibility for this
8597 // point
8598 bool brp_viz = false;
8599 if (proute_array) {
8600 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8601 Route *pr = (Route *)proute_array->Item(ir);
8602 if (pr->IsVisible()) {
8603 brp_viz = true;
8604 break;
8605 }
8606 }
8607 delete proute_array;
8608 if (!brp_viz &&
8609 pNearbyPoint->IsShared()) // is not visible as part of route,
8610 // but still exists as a waypoint
8611 brp_viz =
8612 pNearbyPoint->IsVisible(); // so treat as isolated point
8613 } else
8614 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8615
8616 if (brp_viz) {
8617 wxString msg = _("Use nearby waypoint?");
8618 // Don't add a mark without name to the route. Name it if needed
8619 const bool noname(pNearbyPoint->GetName() == "");
8620 if (noname) {
8621 msg =
8622 _("Use nearby nameless waypoint and name it M with"
8623 " a unique number?");
8624 }
8625 // Avoid route finish on focus change for message dialog
8626 m_FinishRouteOnKillFocus = false;
8627 int dlg_return =
8628 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8629 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8630 m_FinishRouteOnKillFocus = true;
8631 if (dlg_return == wxID_YES) {
8632 if (noname) {
8633 if (m_pMouseRoute) {
8634 int last_wp_num = m_pMouseRoute->GetnPoints();
8635 // AP-ECRMB will truncate to 6 characters
8636 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8637 wxString wp_name = wxString::Format(
8638 "M%002i-%s", last_wp_num + 1, guid_short);
8639 pNearbyPoint->SetName(wp_name);
8640 } else
8641 pNearbyPoint->SetName("WPXX");
8642 }
8643 pMousePoint = pNearbyPoint;
8644
8645 // Using existing waypoint, so nothing to delete for undo.
8646 if (m_routeState > 1)
8647 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8648 Undo_HasParent, NULL);
8649
8650 tail =
8651 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8652 bool procede = false;
8653 if (tail) {
8654 procede = true;
8655 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8656 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8657 procede = false;
8658 }
8659
8660 if (procede) {
8661 int dlg_return;
8662 m_FinishRouteOnKillFocus = false;
8663 if (m_routeState ==
8664 1) { // first point in new route, preceeding route to be
8665 // added? Not touch case
8666
8667 wxString dmsg =
8668 _("Insert first part of this route in the new route?");
8669 if (tail->GetIndexOf(pMousePoint) ==
8670 tail->GetnPoints()) // Starting on last point of another
8671 // route?
8672 dmsg = _("Insert this route in the new route?");
8673
8674 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8675 dlg_return = OCPNMessageBox(
8676 this, dmsg, _("OpenCPN Route Create"),
8677 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8678 m_FinishRouteOnKillFocus = true;
8679
8680 if (dlg_return == wxID_YES) {
8681 inserting = true; // part of the other route will be
8682 // preceeding the new route
8683 }
8684 }
8685 } else {
8686 wxString dmsg =
8687 _("Append last part of this route to the new route?");
8688 if (tail->GetIndexOf(pMousePoint) == 1)
8689 dmsg = _(
8690 "Append this route to the new route?"); // Picking the
8691 // first point
8692 // of another
8693 // route?
8694
8695 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8696 dlg_return = OCPNMessageBox(
8697 this, dmsg, _("OpenCPN Route Create"),
8698 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8699 m_FinishRouteOnKillFocus = true;
8700
8701 if (dlg_return == wxID_YES) {
8702 appending = true; // part of the other route will be
8703 // appended to the new route
8704 }
8705 }
8706 }
8707 }
8708
8709 // check all other routes to see if this point appears in any
8710 // other route If it appears in NO other route, then it should e
8711 // considered an isolated mark
8712 if (!FindRouteContainingWaypoint(pMousePoint))
8713 pMousePoint->SetShared(true);
8714 }
8715 }
8716 }
8717
8718 if (NULL == pMousePoint) { // need a new point
8719 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8720 "", wxEmptyString);
8721 pMousePoint->SetNameShown(false);
8722
8723 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8724
8725 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8726
8727 if (m_routeState > 1)
8728 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8729 Undo_IsOrphanded, NULL);
8730 }
8731
8732 if (m_pMouseRoute) {
8733 if (m_routeState == 1) {
8734 // First point in the route.
8735 m_pMouseRoute->AddPoint(pMousePoint);
8736 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8737 } else {
8738 if (m_pMouseRoute->m_NextLegGreatCircle) {
8739 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8740 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8741 &rhumbBearing, &rhumbDist);
8742 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8743 rlat, &gcDist, &gcBearing, NULL);
8744 double gcDistNM = gcDist / 1852.0;
8745
8746 // Empirically found expression to get reasonable route segments.
8747 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8748 pow(rhumbDist - gcDistNM - 1, 0.5);
8749
8750 wxString msg;
8751 msg << _("For this leg the Great Circle route is ")
8752 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8753 << _(" shorter than rhumbline.\n\n")
8754 << _("Would you like include the Great Circle routing points "
8755 "for this leg?");
8756
8757 m_FinishRouteOnKillFocus = false;
8758 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8759 // does not fully capture mouse
8760
8761 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8762 wxYES_NO | wxNO_DEFAULT);
8763
8764 m_disable_edge_pan = false;
8765 m_FinishRouteOnKillFocus = true;
8766
8767 if (answer == wxID_YES) {
8768 RoutePoint *gcPoint;
8769 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8770 wxRealPoint gcCoord;
8771
8772 for (int i = 1; i <= segmentCount; i++) {
8773 double fraction = (double)i * (1.0 / (double)segmentCount);
8774 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8775 gcDist * fraction, gcBearing,
8776 &gcCoord.x, &gcCoord.y, NULL);
8777
8778 if (i < segmentCount) {
8779 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8780 wxEmptyString);
8781 gcPoint->SetNameShown(false);
8782 // pConfig->AddNewWayPoint(gcPoint, -1);
8783 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8784
8785 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8786 gcPoint);
8787 } else {
8788 gcPoint = pMousePoint; // Last point, previously exsisting!
8789 }
8790
8791 m_pMouseRoute->AddPoint(gcPoint);
8792 pSelect->AddSelectableRouteSegment(
8793 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8794 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8795 prevGcPoint = gcPoint;
8796 }
8797
8798 undo->CancelUndoableAction(true);
8799
8800 } else {
8801 m_pMouseRoute->AddPoint(pMousePoint);
8802 pSelect->AddSelectableRouteSegment(
8803 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8804 pMousePoint, m_pMouseRoute);
8805 undo->AfterUndoableAction(m_pMouseRoute);
8806 }
8807 } else {
8808 // Ordinary rhumblinesegment.
8809 m_pMouseRoute->AddPoint(pMousePoint);
8810 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8811 rlon, m_prev_pMousePoint,
8812 pMousePoint, m_pMouseRoute);
8813 undo->AfterUndoableAction(m_pMouseRoute);
8814 }
8815 }
8816 }
8817 m_prev_rlat = rlat;
8818 m_prev_rlon = rlon;
8819 m_prev_pMousePoint = pMousePoint;
8820 if (m_pMouseRoute)
8821 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8822
8823 m_routeState++;
8824
8825 if (appending ||
8826 inserting) { // Appending a route or making a new route
8827 int connect = tail->GetIndexOf(pMousePoint);
8828 if (connect == 1) {
8829 inserting = false; // there is nothing to insert
8830 appending = true; // so append
8831 }
8832 int length = tail->GetnPoints();
8833
8834 int i;
8835 int start, stop;
8836 if (appending) {
8837 start = connect + 1;
8838 stop = length;
8839 } else { // inserting
8840 start = 1;
8841 stop = connect;
8842 m_pMouseRoute->RemovePoint(
8843 m_pMouseRoute
8844 ->GetLastPoint()); // Remove the first and only point
8845 }
8846 for (i = start; i <= stop; i++) {
8847 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8848 if (m_pMouseRoute)
8849 m_pMouseRoute->m_lastMousePointIndex =
8850 m_pMouseRoute->GetnPoints();
8851 m_routeState++;
8852 top_frame::Get()->RefreshAllCanvas();
8853 ret = true;
8854 }
8855 m_prev_rlat =
8856 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8857 m_prev_rlon =
8858 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8859 m_pMouseRoute->FinalizeForRendering();
8860 }
8861 top_frame::Get()->RefreshAllCanvas();
8862 ret = true;
8863 }
8864
8865 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8866 {
8867 SetCursor(*pCursorPencil);
8868
8869 if (!m_pMeasureRoute) {
8870 m_pMeasureRoute = new Route();
8871 pRouteList->push_back(m_pMeasureRoute);
8872 }
8873
8874 if (m_nMeasureState == 1) {
8875 r_rband.x = x;
8876 r_rband.y = y;
8877 }
8878
8879 RoutePoint *pMousePoint =
8880 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8881 wxEmptyString, wxEmptyString);
8882 pMousePoint->m_bShowName = false;
8883 pMousePoint->SetShowWaypointRangeRings(false);
8884
8885 m_pMeasureRoute->AddPoint(pMousePoint);
8886
8887 m_prev_rlat = m_cursor_lat;
8888 m_prev_rlon = m_cursor_lon;
8889 m_prev_pMousePoint = pMousePoint;
8890 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8891
8892 m_nMeasureState++;
8893 top_frame::Get()->RefreshAllCanvas();
8894 ret = true;
8895 }
8896
8897 else {
8898 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8899 }
8900 } // !g_btouch
8901 else { // g_btouch
8902 m_last_touch_down_pos = event.GetPosition();
8903
8904 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8905 // if near screen edge, pan with injection
8906 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8907 // return;
8908 // }
8909 }
8910 }
8911
8912 if (ret) return true;
8913 }
8914
8915 if (event.Dragging()) {
8916 // in touch screen mode ensure the finger/cursor is on the selected point's
8917 // radius to allow dragging
8918 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8919 if (g_btouch) {
8920 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8921 SelectItem *pFind = NULL;
8922 SelectableItemList SelList = pSelect->FindSelectionList(
8923 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8924 for (SelectItem *pFind : SelList) {
8925 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8926 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8927 }
8928 }
8929
8930 // Check for use of dragHandle
8931 if (m_pRoutePointEditTarget &&
8932 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8933 SelectItem *pFind = NULL;
8934 SelectableItemList SelList = pSelect->FindSelectionList(
8935 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8936 for (SelectItem *pFind : SelList) {
8937 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8938 if (m_pRoutePointEditTarget == frp) {
8939 m_bIsInRadius = true;
8940 break;
8941 }
8942 }
8943
8944 if (!m_dragoffsetSet) {
8945 RoutePointGui(*m_pRoutePointEditTarget)
8946 .PresetDragOffset(this, mouse_x, mouse_y);
8947 m_dragoffsetSet = true;
8948 }
8949 }
8950 }
8951
8952 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8953 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8954
8955 if (NULL == g_pMarkInfoDialog) {
8956 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8957 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8958 DraggingAllowed = false;
8959
8960 if (m_pRoutePointEditTarget &&
8961 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8962 DraggingAllowed = false;
8963
8964 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8965
8966 if (DraggingAllowed) {
8967 if (!undo->InUndoableAction()) {
8968 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8969 Undo_NeedsCopy, m_pFoundPoint);
8970 }
8971
8972 // Get the update rectangle for the union of the un-edited routes
8973 wxRect pre_rect;
8974
8975 if (!g_bopengl && m_pEditRouteArray) {
8976 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8977 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8978 // Need to validate route pointer
8979 // Route may be gone due to drgging close to ownship with
8980 // "Delete On Arrival" state set, as in the case of
8981 // navigating to an isolated waypoint on a temporary route
8982 if (g_pRouteMan->IsRouteValid(pr)) {
8983 wxRect route_rect;
8984 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8985 pre_rect.Union(route_rect);
8986 }
8987 }
8988 }
8989
8990 double new_cursor_lat = m_cursor_lat;
8991 double new_cursor_lon = m_cursor_lon;
8992
8993 if (CheckEdgePan(x, y, true, 5, 2))
8994 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8995
8996 // update the point itself
8997 if (g_btouch) {
8998 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8999 // new_cursor_lat, new_cursor_lon);
9000 RoutePointGui(*m_pRoutePointEditTarget)
9001 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9002 // update the Drag Handle entry in the pSelect list
9003 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9004 m_pRoutePointEditTarget,
9005 SELTYPE_DRAGHANDLE);
9006 m_pFoundPoint->m_slat =
9007 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9008 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9009 } else {
9010 m_pRoutePointEditTarget->m_lat =
9011 new_cursor_lat; // update the RoutePoint entry
9012 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9013 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9014 m_pFoundPoint->m_slat =
9015 new_cursor_lat; // update the SelectList entry
9016 m_pFoundPoint->m_slon = new_cursor_lon;
9017 }
9018
9019 // Update the MarkProperties Dialog, if currently shown
9020 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9021 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9022 g_pMarkInfoDialog->UpdateProperties(true);
9023 }
9024
9025 if (g_bopengl) {
9026 // InvalidateGL();
9027 Refresh(false);
9028 } else {
9029 // Get the update rectangle for the edited route
9030 wxRect post_rect;
9031
9032 if (m_pEditRouteArray) {
9033 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9034 ir++) {
9035 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9036 if (g_pRouteMan->IsRouteValid(pr)) {
9037 wxRect route_rect;
9038 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9039 post_rect.Union(route_rect);
9040 }
9041 }
9042 }
9043
9044 // Invalidate the union region
9045 pre_rect.Union(post_rect);
9046 RefreshRect(pre_rect, false);
9047 }
9048 top_frame::Get()->RefreshCanvasOther(this);
9049 m_bRoutePoinDragging = true;
9050 }
9051 ret = true;
9052 } // if Route Editing
9053
9054 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9055 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9056
9057 if (NULL == g_pMarkInfoDialog) {
9058 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9059 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9060 DraggingAllowed = false;
9061
9062 if (m_pRoutePointEditTarget &&
9063 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9064 DraggingAllowed = false;
9065
9066 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9067
9068 if (DraggingAllowed) {
9069 if (!undo->InUndoableAction()) {
9070 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9071 Undo_NeedsCopy, m_pFoundPoint);
9072 }
9073
9074 // The mark may be an anchorwatch
9075 double lpp1 = 0.;
9076 double lpp2 = 0.;
9077 double lppmax;
9078
9079 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9080 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9081 }
9082 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9083 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9084 }
9085 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9086
9087 // Get the update rectangle for the un-edited mark
9088 wxRect pre_rect;
9089 if (!g_bopengl) {
9090 RoutePointGui(*m_pRoutePointEditTarget)
9091 .CalculateDCRect(m_dc_route, this, &pre_rect);
9092 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9093 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9094 (int)(lppmax - (pre_rect.height / 2)));
9095 }
9096
9097 // update the point itself
9098 if (g_btouch) {
9099 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9100 // m_cursor_lat, m_cursor_lon);
9101 RoutePointGui(*m_pRoutePointEditTarget)
9102 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9103 // update the Drag Handle entry in the pSelect list
9104 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9105 m_pRoutePointEditTarget,
9106 SELTYPE_DRAGHANDLE);
9107 m_pFoundPoint->m_slat =
9108 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9109 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9110 } else {
9111 m_pRoutePointEditTarget->m_lat =
9112 m_cursor_lat; // update the RoutePoint entry
9113 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9114 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9115 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9116 m_pFoundPoint->m_slon = m_cursor_lon;
9117 }
9118
9119 // Update the MarkProperties Dialog, if currently shown
9120 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9121 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9122 g_pMarkInfoDialog->UpdateProperties(true);
9123 }
9124
9125 // Invalidate the union region
9126 if (g_bopengl) {
9127 if (!g_btouch) InvalidateGL();
9128 Refresh(false);
9129 } else {
9130 // Get the update rectangle for the edited mark
9131 wxRect post_rect;
9132 RoutePointGui(*m_pRoutePointEditTarget)
9133 .CalculateDCRect(m_dc_route, this, &post_rect);
9134 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9135 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9136 (int)(lppmax - (post_rect.height / 2)));
9137
9138 // Invalidate the union region
9139 pre_rect.Union(post_rect);
9140 RefreshRect(pre_rect, false);
9141 }
9142 top_frame::Get()->RefreshCanvasOther(this);
9143 m_bRoutePoinDragging = true;
9144 }
9145 ret = g_btouch ? m_bRoutePoinDragging : true;
9146 }
9147
9148 if (ret) return true;
9149 } // dragging
9150
9151 if (event.LeftUp()) {
9152 bool b_startedit_route = false;
9153 m_dragoffsetSet = false;
9154
9155 if (g_btouch) {
9156 m_bChartDragging = false;
9157 m_bIsInRadius = false;
9158
9159 if (m_routeState) // creating route?
9160 {
9161 if (m_ignore_next_leftup) {
9162 m_ignore_next_leftup = false;
9163 return false;
9164 }
9165
9166 if (m_bedge_pan) {
9167 m_bedge_pan = false;
9168 return false;
9169 }
9170
9171 double rlat, rlon;
9172 bool appending = false;
9173 bool inserting = false;
9174 Route *tail = 0;
9175
9176 rlat = m_cursor_lat;
9177 rlon = m_cursor_lon;
9178
9179 if (m_pRoutePointEditTarget) {
9180 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9181 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9182 if (!g_bopengl) {
9183 wxRect wp_rect;
9184 RoutePointGui(*m_pRoutePointEditTarget)
9185 .CalculateDCRect(m_dc_route, this, &wp_rect);
9186 RefreshRect(wp_rect, true);
9187 }
9188 m_pRoutePointEditTarget = NULL;
9189 }
9190 m_bRouteEditing = true;
9191
9192 if (m_routeState == 1) {
9193 m_pMouseRoute = new Route();
9194 m_pMouseRoute->SetHiLite(50);
9195 pRouteList->push_back(m_pMouseRoute);
9196 r_rband.x = x;
9197 r_rband.y = y;
9198 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9199 }
9200
9201 // Check to see if there is a nearby point which may be reused
9202 RoutePoint *pMousePoint = NULL;
9203
9204 // Calculate meaningful SelectRadius
9205 double nearby_radius_meters =
9206 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9207
9208 RoutePoint *pNearbyPoint =
9209 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9210 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9211 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9212 int dlg_return;
9213#ifndef __WXOSX__
9214 m_FinishRouteOnKillFocus =
9215 false; // Avoid route finish on focus change for message dialog
9216 dlg_return = OCPNMessageBox(
9217 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9218 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9219 m_FinishRouteOnKillFocus = true;
9220#else
9221 dlg_return = wxID_YES;
9222#endif
9223 if (dlg_return == wxID_YES) {
9224 pMousePoint = pNearbyPoint;
9225
9226 // Using existing waypoint, so nothing to delete for undo.
9227 if (m_routeState > 1)
9228 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9229 Undo_HasParent, NULL);
9230 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9231
9232 bool procede = false;
9233 if (tail) {
9234 procede = true;
9235 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9236 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9237 procede = false;
9238 }
9239
9240 if (procede) {
9241 int dlg_return;
9242 m_FinishRouteOnKillFocus = false;
9243 if (m_routeState == 1) { // first point in new route, preceeding
9244 // route to be added? touch case
9245
9246 wxString dmsg =
9247 _("Insert first part of this route in the new route?");
9248 if (tail->GetIndexOf(pMousePoint) ==
9249 tail->GetnPoints()) // Starting on last point of another
9250 // route?
9251 dmsg = _("Insert this route in the new route?");
9252
9253 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9254 dlg_return =
9255 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9256 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9257 m_FinishRouteOnKillFocus = true;
9258
9259 if (dlg_return == wxID_YES) {
9260 inserting = true; // part of the other route will be
9261 // preceeding the new route
9262 }
9263 }
9264 } else {
9265 wxString dmsg =
9266 _("Append last part of this route to the new route?");
9267 if (tail->GetIndexOf(pMousePoint) == 1)
9268 dmsg = _(
9269 "Append this route to the new route?"); // Picking the
9270 // first point of
9271 // another route?
9272
9273 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9274 dlg_return =
9275 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9276 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9277 m_FinishRouteOnKillFocus = true;
9278
9279 if (dlg_return == wxID_YES) {
9280 appending = true; // part of the other route will be
9281 // appended to the new route
9282 }
9283 }
9284 }
9285 }
9286
9287 // check all other routes to see if this point appears in any other
9288 // route If it appears in NO other route, then it should e
9289 // considered an isolated mark
9290 if (!FindRouteContainingWaypoint(pMousePoint))
9291 pMousePoint->SetShared(true);
9292 }
9293 }
9294
9295 if (NULL == pMousePoint) { // need a new point
9296 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9297 "", wxEmptyString);
9298 pMousePoint->SetNameShown(false);
9299
9300 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9301
9302 if (m_routeState > 1)
9303 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9304 Undo_IsOrphanded, NULL);
9305 }
9306
9307 if (m_routeState == 1) {
9308 // First point in the route.
9309 m_pMouseRoute->AddPoint(pMousePoint);
9310 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9311
9312 } else {
9313 if (m_pMouseRoute->m_NextLegGreatCircle) {
9314 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9315 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9316 &rhumbBearing, &rhumbDist);
9317 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9318 &gcDist, &gcBearing, NULL);
9319 double gcDistNM = gcDist / 1852.0;
9320
9321 // Empirically found expression to get reasonable route segments.
9322 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9323 pow(rhumbDist - gcDistNM - 1, 0.5);
9324
9325 wxString msg;
9326 msg << _("For this leg the Great Circle route is ")
9327 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9328 << _(" shorter than rhumbline.\n\n")
9329 << _("Would you like include the Great Circle routing points "
9330 "for this leg?");
9331
9332#ifndef __WXOSX__
9333 m_FinishRouteOnKillFocus = false;
9334 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9335 wxYES_NO | wxNO_DEFAULT);
9336 m_FinishRouteOnKillFocus = true;
9337#else
9338 int answer = wxID_NO;
9339#endif
9340
9341 if (answer == wxID_YES) {
9342 RoutePoint *gcPoint;
9343 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9344 wxRealPoint gcCoord;
9345
9346 for (int i = 1; i <= segmentCount; i++) {
9347 double fraction = (double)i * (1.0 / (double)segmentCount);
9348 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9349 gcDist * fraction, gcBearing,
9350 &gcCoord.x, &gcCoord.y, NULL);
9351
9352 if (i < segmentCount) {
9353 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9354 wxEmptyString);
9355 gcPoint->SetNameShown(false);
9356 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9357 gcPoint);
9358 } else {
9359 gcPoint = pMousePoint; // Last point, previously exsisting!
9360 }
9361
9362 m_pMouseRoute->AddPoint(gcPoint);
9363 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9364
9365 pSelect->AddSelectableRouteSegment(
9366 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9367 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9368 prevGcPoint = gcPoint;
9369 }
9370
9371 undo->CancelUndoableAction(true);
9372
9373 } else {
9374 m_pMouseRoute->AddPoint(pMousePoint);
9375 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9376 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9377 rlon, m_prev_pMousePoint,
9378 pMousePoint, m_pMouseRoute);
9379 undo->AfterUndoableAction(m_pMouseRoute);
9380 }
9381 } else {
9382 // Ordinary rhumblinesegment.
9383 m_pMouseRoute->AddPoint(pMousePoint);
9384 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9385
9386 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9387 rlon, m_prev_pMousePoint,
9388 pMousePoint, m_pMouseRoute);
9389 undo->AfterUndoableAction(m_pMouseRoute);
9390 }
9391 }
9392
9393 m_prev_rlat = rlat;
9394 m_prev_rlon = rlon;
9395 m_prev_pMousePoint = pMousePoint;
9396 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9397
9398 m_routeState++;
9399
9400 if (appending ||
9401 inserting) { // Appending a route or making a new route
9402 int connect = tail->GetIndexOf(pMousePoint);
9403 if (connect == 1) {
9404 inserting = false; // there is nothing to insert
9405 appending = true; // so append
9406 }
9407 int length = tail->GetnPoints();
9408
9409 int i;
9410 int start, stop;
9411 if (appending) {
9412 start = connect + 1;
9413 stop = length;
9414 } else { // inserting
9415 start = 1;
9416 stop = connect;
9417 m_pMouseRoute->RemovePoint(
9418 m_pMouseRoute
9419 ->GetLastPoint()); // Remove the first and only point
9420 }
9421 for (i = start; i <= stop; i++) {
9422 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9423 if (m_pMouseRoute)
9424 m_pMouseRoute->m_lastMousePointIndex =
9425 m_pMouseRoute->GetnPoints();
9426 m_routeState++;
9427 top_frame::Get()->RefreshAllCanvas();
9428 ret = true;
9429 }
9430 m_prev_rlat =
9431 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9432 m_prev_rlon =
9433 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9434 m_pMouseRoute->FinalizeForRendering();
9435 }
9436
9437 Refresh(true);
9438 ret = true;
9439 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9440 {
9441 if (m_bedge_pan) {
9442 m_bedge_pan = false;
9443 return false;
9444 }
9445
9446 if (m_ignore_next_leftup) {
9447 m_ignore_next_leftup = false;
9448 return false;
9449 }
9450
9451 if (m_nMeasureState == 1) {
9452 m_pMeasureRoute = new Route();
9453 pRouteList->push_back(m_pMeasureRoute);
9454 r_rband.x = x;
9455 r_rband.y = y;
9456 }
9457
9458 if (m_pMeasureRoute) {
9459 RoutePoint *pMousePoint =
9460 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9461 wxEmptyString, wxEmptyString);
9462 pMousePoint->m_bShowName = false;
9463
9464 m_pMeasureRoute->AddPoint(pMousePoint);
9465
9466 m_prev_rlat = m_cursor_lat;
9467 m_prev_rlon = m_cursor_lon;
9468 m_prev_pMousePoint = pMousePoint;
9469 m_pMeasureRoute->m_lastMousePointIndex =
9470 m_pMeasureRoute->GetnPoints();
9471
9472 m_nMeasureState++;
9473 } else {
9474 CancelMeasureRoute();
9475 }
9476
9477 Refresh(true);
9478 ret = true;
9479 } else {
9480 bool bSelectAllowed = true;
9481 if (NULL == g_pMarkInfoDialog) {
9482 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9483 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9484 bSelectAllowed = false;
9485
9486 // Avoid accidental selection of routepoint if last touchdown started
9487 // a significant chart drag operation
9488 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9489 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9490 significant_drag) ||
9491 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9492 significant_drag)) {
9493 bSelectAllowed = false;
9494 }
9495
9496 /*if this left up happens at the end of a route point dragging and if
9497 the cursor/thumb is on the draghandle icon, not on the point iself a new
9498 selection will select nothing and the drag will never be ended, so the
9499 legs around this point never selectable. At this step we don't need a
9500 new selection, just keep the previoulsly selected and dragged point */
9501 if (m_bRoutePoinDragging) bSelectAllowed = false;
9502
9503 if (bSelectAllowed) {
9504 bool b_was_editing_mark = m_bMarkEditing;
9505 bool b_was_editing_route = m_bRouteEditing;
9506 FindRoutePointsAtCursor(SelectRadius,
9507 true); // Possibly selecting a point in a
9508 // route for later dragging
9509
9510 /*route and a mark points in layer can't be dragged so should't be
9511 * selected and no draghandle icon*/
9512 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9513 m_pRoutePointEditTarget = NULL;
9514
9515 if (!b_was_editing_route) {
9516 if (m_pEditRouteArray) {
9517 b_startedit_route = true;
9518
9519 // Hide the track and route rollover during route point edit, not
9520 // needed, and may be confusing
9521 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9522 m_pTrackRolloverWin->IsActive(false);
9523 }
9524 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9525 m_pRouteRolloverWin->IsActive(false);
9526 }
9527
9528 wxRect pre_rect;
9529 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9530 ir++) {
9531 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9532 // Need to validate route pointer
9533 // Route may be gone due to drgging close to ownship with
9534 // "Delete On Arrival" state set, as in the case of
9535 // navigating to an isolated waypoint on a temporary route
9536 if (g_pRouteMan->IsRouteValid(pr)) {
9537 // pr->SetHiLite(50);
9538 wxRect route_rect;
9539 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9540 pre_rect.Union(route_rect);
9541 }
9542 }
9543 RefreshRect(pre_rect, true);
9544 }
9545 } else {
9546 b_startedit_route = false;
9547 }
9548
9549 // Mark editing in touch mode, left-up event.
9550 if (m_pRoutePointEditTarget) {
9551 if (b_was_editing_mark ||
9552 b_was_editing_route) { // kill previous hilight
9553 if (m_lastRoutePointEditTarget) {
9554 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9555 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9556 RoutePointGui(*m_lastRoutePointEditTarget)
9557 .EnableDragHandle(false);
9558 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9559 SELTYPE_DRAGHANDLE);
9560 }
9561 }
9562
9563 if (m_pRoutePointEditTarget) {
9564 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9565 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9566 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9567 wxPoint2DDouble dragHandlePoint =
9568 RoutePointGui(*m_pRoutePointEditTarget)
9569 .GetDragHandlePoint(this);
9570 pSelect->AddSelectablePoint(
9571 dragHandlePoint.m_y, dragHandlePoint.m_x,
9572 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9573 }
9574 } else { // Deselect everything
9575 if (m_lastRoutePointEditTarget) {
9576 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9577 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9578 RoutePointGui(*m_lastRoutePointEditTarget)
9579 .EnableDragHandle(false);
9580 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9581 SELTYPE_DRAGHANDLE);
9582
9583 // Clear any routes being edited, probably orphans
9584 wxArrayPtrVoid *lastEditRouteArray =
9586 m_lastRoutePointEditTarget);
9587 if (lastEditRouteArray) {
9588 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9589 ir++) {
9590 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9591 if (g_pRouteMan->IsRouteValid(pr)) {
9592 pr->m_bIsBeingEdited = false;
9593 }
9594 }
9595 delete lastEditRouteArray;
9596 }
9597 }
9598 }
9599
9600 // Do the refresh
9601
9602 if (g_bopengl) {
9603 InvalidateGL();
9604 Refresh(false);
9605 } else {
9606 if (m_lastRoutePointEditTarget) {
9607 wxRect wp_rect;
9608 RoutePointGui(*m_lastRoutePointEditTarget)
9609 .CalculateDCRect(m_dc_route, this, &wp_rect);
9610 RefreshRect(wp_rect, true);
9611 }
9612
9613 if (m_pRoutePointEditTarget) {
9614 wxRect wp_rect;
9615 RoutePointGui(*m_pRoutePointEditTarget)
9616 .CalculateDCRect(m_dc_route, this, &wp_rect);
9617 RefreshRect(wp_rect, true);
9618 }
9619 }
9620 }
9621 } // bSelectAllowed
9622
9623 // Check to see if there is a route or AIS target under the cursor
9624 // If so, start the rollover timer which creates the popup
9625 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9626 bool b_start_rollover = false;
9627 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9628 SelectItem *pFind = pSelectAIS->FindSelection(
9629 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9630 if (pFind) b_start_rollover = true;
9631 }
9632
9633 if (!b_start_rollover && !b_startedit_route) {
9634 SelectableItemList SelList = pSelect->FindSelectionList(
9635 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9636 for (SelectItem *pFindSel : SelList) {
9637 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9638 if (pr && pr->IsVisible()) {
9639 b_start_rollover = true;
9640 break;
9641 }
9642 } // while
9643 }
9644
9645 if (!b_start_rollover && !b_startedit_route) {
9646 SelectableItemList SelList = pSelect->FindSelectionList(
9647 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9648 for (SelectItem *pFindSel : SelList) {
9649 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9650 if (tr && tr->IsVisible()) {
9651 b_start_rollover = true;
9652 break;
9653 }
9654 } // while
9655 }
9656
9657 if (b_start_rollover)
9658 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9659 wxTIMER_ONE_SHOT);
9660 Route *tail = 0;
9661 Route *current = 0;
9662 bool appending = false;
9663 bool inserting = false;
9664 int connect = 0;
9665 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9666 // drag
9667 if (m_pRoutePointEditTarget) {
9668 // Check to see if there is a nearby point which may replace the
9669 // dragged one
9670 RoutePoint *pMousePoint = NULL;
9671
9672 int index_last;
9673 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9674 double nearby_radius_meters =
9675 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9676 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9677 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9678 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9679 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9680 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9681 bool duplicate =
9682 false; // ensure we won't create duplicate point in routes
9683 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9684 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9685 ir++) {
9686 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9687 if (pr && pr->pRoutePointList) {
9688 auto *list = pr->pRoutePointList;
9689 auto pos =
9690 std::find(list->begin(), list->end(), pNearbyPoint);
9691 if (pos != list->end()) {
9692 duplicate = true;
9693 break;
9694 }
9695 }
9696 }
9697 }
9698
9699 // Special case:
9700 // Allow "re-use" of a route's waypoints iff it is a simple
9701 // isolated route. This allows, for instance, creation of a closed
9702 // polygon route
9703 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9704
9705 if (!duplicate) {
9706 int dlg_return;
9707 dlg_return =
9708 OCPNMessageBox(this,
9709 _("Replace this RoutePoint by the nearby "
9710 "Waypoint?"),
9711 _("OpenCPN RoutePoint change"),
9712 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9713 if (dlg_return == wxID_YES) {
9714 /*double confirmation if the dragged point has been manually
9715 * created which can be important and could be deleted
9716 * unintentionally*/
9717
9718 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9719 pNearbyPoint);
9720 current =
9721 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9722
9723 if (tail && current && (tail != current)) {
9724 int dlg_return1;
9725 connect = tail->GetIndexOf(pNearbyPoint);
9726 int index_current_route =
9727 current->GetIndexOf(m_pRoutePointEditTarget);
9728 index_last = current->GetIndexOf(current->GetLastPoint());
9729 dlg_return1 = wxID_NO;
9730 if (index_last ==
9731 index_current_route) { // we are dragging the last
9732 // point of the route
9733 if (connect != tail->GetnPoints()) { // anything to do?
9734
9735 wxString dmsg(
9736 _("Last part of route to be appended to dragged "
9737 "route?"));
9738 if (connect == 1)
9739 dmsg =
9740 _("Full route to be appended to dragged route?");
9741
9742 dlg_return1 = OCPNMessageBox(
9743 this, dmsg, _("OpenCPN Route Create"),
9744 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9745 if (dlg_return1 == wxID_YES) {
9746 appending = true;
9747 }
9748 }
9749 } else if (index_current_route ==
9750 1) { // dragging the first point of the route
9751 if (connect != 1) { // anything to do?
9752
9753 wxString dmsg(
9754 _("First part of route to be inserted into dragged "
9755 "route?"));
9756 if (connect == tail->GetnPoints())
9757 dmsg = _(
9758 "Full route to be inserted into dragged route?");
9759
9760 dlg_return1 = OCPNMessageBox(
9761 this, dmsg, _("OpenCPN Route Create"),
9762 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9763 if (dlg_return1 == wxID_YES) {
9764 inserting = true;
9765 }
9766 }
9767 }
9768 }
9769
9770 if (m_pRoutePointEditTarget->IsShared()) {
9771 // dlg_return = wxID_NO;
9772 dlg_return = OCPNMessageBox(
9773 this,
9774 _("Do you really want to delete and replace this "
9775 "WayPoint") +
9776 "\n" + _("which has been created manually?"),
9777 ("OpenCPN RoutePoint warning"),
9778 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9779 }
9780 }
9781 if (dlg_return == wxID_YES) {
9782 pMousePoint = pNearbyPoint;
9783 if (pMousePoint->m_bIsolatedMark) {
9784 pMousePoint->SetShared(true);
9785 }
9786 pMousePoint->m_bIsolatedMark =
9787 false; // definitely no longer isolated
9788 pMousePoint->m_bIsInRoute = true;
9789 }
9790 }
9791 }
9792 }
9793 if (!pMousePoint)
9794 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9795
9796 if (m_pEditRouteArray) {
9797 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9798 ir++) {
9799 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9800 if (g_pRouteMan->IsRouteValid(pr)) {
9801 if (pMousePoint) { // remove the dragged point and insert the
9802 // nearby
9803 auto *list = pr->pRoutePointList;
9804 auto pos = std::find(list->begin(), list->end(),
9805 m_pRoutePointEditTarget);
9806
9807 pSelect->DeleteAllSelectableRoutePoints(pr);
9808 pSelect->DeleteAllSelectableRouteSegments(pr);
9809
9810 pr->pRoutePointList->insert(pos, pMousePoint);
9811 pos = std::find(list->begin(), list->end(),
9812 m_pRoutePointEditTarget);
9813 pr->pRoutePointList->erase(pos);
9814
9815 pSelect->AddAllSelectableRouteSegments(pr);
9816 pSelect->AddAllSelectableRoutePoints(pr);
9817 }
9818 pr->FinalizeForRendering();
9819 pr->UpdateSegmentDistances();
9820 if (m_bRoutePoinDragging) {
9821 // pConfig->UpdateRoute(pr);
9822 NavObj_dB::GetInstance().UpdateRoute(pr);
9823 }
9824 }
9825 }
9826 }
9827
9828 // Update the RouteProperties Dialog, if currently shown
9829 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9830 if (m_pEditRouteArray) {
9831 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9832 ir++) {
9833 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9834 if (g_pRouteMan->IsRouteValid(pr)) {
9835 if (pRoutePropDialog->GetRoute() == pr) {
9836 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9837 }
9838 /* cannot edit track points anyway
9839 else if ( ( NULL !=
9840 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9841 pTrackPropDialog->m_pTrack == pr ) {
9842 pTrackPropDialog->SetTrackAndUpdate(
9843 pr );
9844 }
9845 */
9846 }
9847 }
9848 }
9849 }
9850 if (pMousePoint) { // clear all about the dragged point
9851 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9852 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9853 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9854 // Hide mark properties dialog if open on the replaced point
9855 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9856 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9857 g_pMarkInfoDialog->Hide();
9858
9859 delete m_pRoutePointEditTarget;
9860 m_lastRoutePointEditTarget = NULL;
9861 m_pRoutePointEditTarget = NULL;
9862 undo->AfterUndoableAction(pMousePoint);
9863 undo->InvalidateUndo();
9864 }
9865 }
9866 }
9867
9868 else if (m_bMarkEditing) { // End of way point drag
9869 if (m_pRoutePointEditTarget)
9870 if (m_bRoutePoinDragging) {
9871 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9872 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9873 }
9874 }
9875
9876 if (m_pRoutePointEditTarget)
9877 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9878
9879 if (!m_pRoutePointEditTarget) {
9880 delete m_pEditRouteArray;
9881 m_pEditRouteArray = NULL;
9882 m_bRouteEditing = false;
9883 }
9884 m_bRoutePoinDragging = false;
9885
9886 if (appending) { // Appending to the route of which the last point is
9887 // dragged onto another route
9888
9889 // copy tail from connect until length to end of current after dragging
9890
9891 int length = tail->GetnPoints();
9892 for (int i = connect + 1; i <= length; i++) {
9893 current->AddPointAndSegment(tail->GetPoint(i), false);
9894 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9895 m_routeState++;
9896 top_frame::Get()->RefreshAllCanvas();
9897 ret = true;
9898 }
9899 current->FinalizeForRendering();
9900 current->m_bIsBeingEdited = false;
9901 FinishRoute();
9902 g_pRouteMan->DeleteRoute(tail);
9903 }
9904 if (inserting) {
9905 pSelect->DeleteAllSelectableRoutePoints(current);
9906 pSelect->DeleteAllSelectableRouteSegments(current);
9907 for (int i = 1; i < connect; i++) { // numbering in the tail route
9908 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9909 }
9910 pSelect->AddAllSelectableRouteSegments(current);
9911 pSelect->AddAllSelectableRoutePoints(current);
9912 current->FinalizeForRendering();
9913 current->m_bIsBeingEdited = false;
9914 g_pRouteMan->DeleteRoute(tail);
9915 }
9916
9917 // Update the RouteProperties Dialog, if currently shown
9918 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9919 if (m_pEditRouteArray) {
9920 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9921 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9922 if (g_pRouteMan->IsRouteValid(pr)) {
9923 if (pRoutePropDialog->GetRoute() == pr) {
9924 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9925 }
9926 }
9927 }
9928 }
9929 }
9930
9931 } // g_btouch
9932
9933 else { // !g_btouch
9934 if (m_bRouteEditing) { // End of RoutePoint drag
9935 Route *tail = 0;
9936 Route *current = 0;
9937 bool appending = false;
9938 bool inserting = false;
9939 int connect = 0;
9940 int index_last;
9941 if (m_pRoutePointEditTarget) {
9942 m_pRoutePointEditTarget->m_bBlink = false;
9943 // Check to see if there is a nearby point which may replace the
9944 // dragged one
9945 RoutePoint *pMousePoint = NULL;
9946 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9947 double nearby_radius_meters =
9948 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9949 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9950 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9951 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9952 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9953 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9954 bool duplicate = false; // don't create duplicate point in routes
9955 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9956 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9957 ir++) {
9958 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9959 if (pr && pr->pRoutePointList) {
9960 auto *list = pr->pRoutePointList;
9961 auto pos =
9962 std::find(list->begin(), list->end(), pNearbyPoint);
9963 if (pos != list->end()) {
9964 duplicate = true;
9965 break;
9966 }
9967 }
9968 }
9969 }
9970
9971 // Special case:
9972 // Allow "re-use" of a route's waypoints iff it is a simple
9973 // isolated route. This allows, for instance, creation of a closed
9974 // polygon route
9975 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9976
9977 if (!duplicate) {
9978 int dlg_return;
9979 dlg_return =
9980 OCPNMessageBox(this,
9981 _("Replace this RoutePoint by the nearby "
9982 "Waypoint?"),
9983 _("OpenCPN RoutePoint change"),
9984 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9985 if (dlg_return == wxID_YES) {
9986 /*double confirmation if the dragged point has been manually
9987 * created which can be important and could be deleted
9988 * unintentionally*/
9989 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9990 pNearbyPoint);
9991 current =
9992 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9993
9994 if (tail && current && (tail != current)) {
9995 int dlg_return1;
9996 connect = tail->GetIndexOf(pNearbyPoint);
9997 int index_current_route =
9998 current->GetIndexOf(m_pRoutePointEditTarget);
9999 index_last = current->GetIndexOf(current->GetLastPoint());
10000 dlg_return1 = wxID_NO;
10001 if (index_last ==
10002 index_current_route) { // we are dragging the last
10003 // point of the route
10004 if (connect != tail->GetnPoints()) { // anything to do?
10005
10006 wxString dmsg(
10007 _("Last part of route to be appended to dragged "
10008 "route?"));
10009 if (connect == 1)
10010 dmsg =
10011 _("Full route to be appended to dragged route?");
10012
10013 dlg_return1 = OCPNMessageBox(
10014 this, dmsg, _("OpenCPN Route Create"),
10015 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10016 if (dlg_return1 == wxID_YES) {
10017 appending = true;
10018 }
10019 }
10020 } else if (index_current_route ==
10021 1) { // dragging the first point of the route
10022 if (connect != 1) { // anything to do?
10023
10024 wxString dmsg(
10025 _("First part of route to be inserted into dragged "
10026 "route?"));
10027 if (connect == tail->GetnPoints())
10028 dmsg = _(
10029 "Full route to be inserted into dragged route?");
10030
10031 dlg_return1 = OCPNMessageBox(
10032 this, dmsg, _("OpenCPN Route Create"),
10033 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10034 if (dlg_return1 == wxID_YES) {
10035 inserting = true;
10036 }
10037 }
10038 }
10039 }
10040
10041 if (m_pRoutePointEditTarget->IsShared()) {
10042 dlg_return = wxID_NO;
10043 dlg_return = OCPNMessageBox(
10044 this,
10045 _("Do you really want to delete and replace this "
10046 "WayPoint") +
10047 "\n" + _("which has been created manually?"),
10048 ("OpenCPN RoutePoint warning"),
10049 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10050 }
10051 }
10052 if (dlg_return == wxID_YES) {
10053 pMousePoint = pNearbyPoint;
10054 if (pMousePoint->m_bIsolatedMark) {
10055 pMousePoint->SetShared(true);
10056 }
10057 pMousePoint->m_bIsolatedMark =
10058 false; // definitely no longer isolated
10059 pMousePoint->m_bIsInRoute = true;
10060 }
10061 }
10062 }
10063 }
10064 if (!pMousePoint)
10065 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10066
10067 if (m_pEditRouteArray) {
10068 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10069 ir++) {
10070 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10071 if (g_pRouteMan->IsRouteValid(pr)) {
10072 if (pMousePoint) { // replace dragged point by nearby one
10073 auto *list = pr->pRoutePointList;
10074 auto pos = std::find(list->begin(), list->end(),
10075 m_pRoutePointEditTarget);
10076
10077 pSelect->DeleteAllSelectableRoutePoints(pr);
10078 pSelect->DeleteAllSelectableRouteSegments(pr);
10079
10080 pr->pRoutePointList->insert(pos, pMousePoint);
10081 pos = std::find(list->begin(), list->end(),
10082 m_pRoutePointEditTarget);
10083 if (pos != list->end()) list->erase(pos);
10084 // pr->pRoutePointList->erase(pos + 1);
10085
10086 pSelect->AddAllSelectableRouteSegments(pr);
10087 pSelect->AddAllSelectableRoutePoints(pr);
10088 }
10089 pr->FinalizeForRendering();
10090 pr->UpdateSegmentDistances();
10091 pr->m_bIsBeingEdited = false;
10092
10093 if (m_bRoutePoinDragging) {
10094 // Special case optimization.
10095 // Dragging a single point of a route
10096 // without any point additions or re-ordering
10097 if (!pMousePoint)
10098 NavObj_dB::GetInstance().UpdateRoutePoint(
10099 m_pRoutePointEditTarget);
10100 else
10101 NavObj_dB::GetInstance().UpdateRoute(pr);
10102 }
10103 pr->SetHiLite(0);
10104 }
10105 }
10106 Refresh(false);
10107 }
10108
10109 if (appending) {
10110 // copy tail from connect until length to end of current after
10111 // dragging
10112
10113 int length = tail->GetnPoints();
10114 for (int i = connect + 1; i <= length; i++) {
10115 current->AddPointAndSegment(tail->GetPoint(i), false);
10116 if (current)
10117 current->m_lastMousePointIndex = current->GetnPoints();
10118 m_routeState++;
10119 top_frame::Get()->RefreshAllCanvas();
10120 ret = true;
10121 }
10122 current->FinalizeForRendering();
10123 current->m_bIsBeingEdited = false;
10124 FinishRoute();
10125 g_pRouteMan->DeleteRoute(tail);
10126 }
10127 if (inserting) {
10128 pSelect->DeleteAllSelectableRoutePoints(current);
10129 pSelect->DeleteAllSelectableRouteSegments(current);
10130 for (int i = 1; i < connect; i++) { // numbering in the tail route
10131 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10132 }
10133 pSelect->AddAllSelectableRouteSegments(current);
10134 pSelect->AddAllSelectableRoutePoints(current);
10135 current->FinalizeForRendering();
10136 current->m_bIsBeingEdited = false;
10137 g_pRouteMan->DeleteRoute(tail);
10138 }
10139
10140 // Update the RouteProperties Dialog, if currently shown
10141 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10142 if (m_pEditRouteArray) {
10143 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10144 ir++) {
10145 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10146 if (g_pRouteMan->IsRouteValid(pr)) {
10147 if (pRoutePropDialog->GetRoute() == pr) {
10148 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10149 }
10150 }
10151 }
10152 }
10153 }
10154
10155 if (pMousePoint) {
10156 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10157 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10158 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10159 // Hide mark properties dialog if open on the replaced point
10160 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10161 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10162 g_pMarkInfoDialog->Hide();
10163
10164 delete m_pRoutePointEditTarget;
10165 m_lastRoutePointEditTarget = NULL;
10166 undo->AfterUndoableAction(pMousePoint);
10167 undo->InvalidateUndo();
10168 } else {
10169 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10170 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10171
10172 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10173 }
10174
10175 delete m_pEditRouteArray;
10176 m_pEditRouteArray = NULL;
10177 }
10178
10179 InvalidateGL();
10180 m_bRouteEditing = false;
10181 m_pRoutePointEditTarget = NULL;
10182
10183 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10184 ret = true;
10185 }
10186
10187 else if (m_bMarkEditing) { // end of Waypoint drag
10188 if (m_pRoutePointEditTarget) {
10189 if (m_bRoutePoinDragging) {
10190 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10191 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10192 }
10193 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10194 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10195 if (!g_bopengl) {
10196 wxRect wp_rect;
10197 RoutePointGui(*m_pRoutePointEditTarget)
10198 .CalculateDCRect(m_dc_route, this, &wp_rect);
10199 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10200 RefreshRect(wp_rect, true);
10201 }
10202 }
10203 m_pRoutePointEditTarget = NULL;
10204 m_bMarkEditing = false;
10205 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10206 ret = true;
10207 }
10208
10209 else if (leftIsDown) { // left click for chart center
10210 leftIsDown = false;
10211 ret = false;
10212
10213 if (!g_btouch) {
10214 if (!m_bChartDragging && !m_bMeasure_Active) {
10215 } else {
10216 m_bChartDragging = false;
10217 }
10218 }
10219 }
10220 m_bRoutePoinDragging = false;
10221 } // !btouch
10222
10223 if (ret) return true;
10224 } // left up
10225
10226 if (event.RightDown()) {
10227 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10228 last_drag.x = mx;
10229 last_drag.y = my;
10230
10231 if (g_btouch) {
10232 // if( m_pRoutePointEditTarget )
10233 // return false;
10234 }
10235
10236 ret = true;
10237 m_FinishRouteOnKillFocus = false;
10238 CallPopupMenu(mx, my);
10239 m_FinishRouteOnKillFocus = true;
10240 } // Right down
10241
10242 return ret;
10243}
10244
10245bool panleftIsDown;
10246bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10247 // Skip all mouse processing if shift is held.
10248 // This allows plugins to implement shift+drag behaviors.
10249 if (event.ShiftDown()) {
10250 return false;
10251 }
10252 int x, y;
10253 event.GetPosition(&x, &y);
10254
10255 x *= m_displayScale;
10256 y *= m_displayScale;
10257
10258 // Check for wheel rotation
10259 // ideally, should be just longer than the time between
10260 // processing accumulated mouse events from the event queue
10261 // as would happen during screen redraws.
10262 int wheel_dir = event.GetWheelRotation();
10263
10264 if (wheel_dir) {
10265 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10266 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10267
10268 double factor = g_mouse_zoom_sensitivity;
10269 if (wheel_dir < 0) factor = 1 / factor;
10270
10271 if (g_bsmoothpanzoom) {
10272 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10273 if (wheel_dir == m_last_wheel_dir) {
10274 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10275 // m_zoom_target /= factor;
10276 } else
10277 StopMovement();
10278 } else {
10279 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10280 m_wheelstopwatch.Start(0);
10281 // m_zoom_target = VPoint.chart_scale / factor;
10282 }
10283 }
10284
10285 m_last_wheel_dir = wheel_dir;
10286
10287 ZoomCanvas(factor, true, false);
10288 }
10289
10290 if (event.LeftDown()) {
10291 // Skip the first left click if it will cause a canvas focus shift
10292 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10293 return false;
10294 }
10295
10296 last_drag.x = x, last_drag.y = y;
10297 panleftIsDown = true;
10298 }
10299
10300 if (event.LeftUp()) {
10301 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10302 // seen here.
10303 panleftIsDown = false;
10304
10305 if (!g_btouch) {
10306 if (!m_bChartDragging && !m_bMeasure_Active) {
10307 switch (cursor_region) {
10308 case MID_RIGHT: {
10309 PanCanvas(100, 0);
10310 break;
10311 }
10312
10313 case MID_LEFT: {
10314 PanCanvas(-100, 0);
10315 break;
10316 }
10317
10318 case MID_TOP: {
10319 PanCanvas(0, 100);
10320 break;
10321 }
10322
10323 case MID_BOT: {
10324 PanCanvas(0, -100);
10325 break;
10326 }
10327
10328 case CENTER: {
10329 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10330 break;
10331 }
10332 }
10333 } else {
10334 m_bChartDragging = false;
10335 }
10336 }
10337 }
10338 }
10339
10340 if (event.Dragging() && event.LeftIsDown()) {
10341 /*
10342 * fixed dragging.
10343 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10344 * before the drag event. Hence, as there is no mouse down event, last_drag
10345 * is not reset before the drag. And that results in one single drag
10346 * session, meaning you cannot drag the map a few miles north, lift your
10347 * finger, and the go even further north. Instead, the map resets itself
10348 * always to the very first drag start (since there is not reset of
10349 * last_drag).
10350 *
10351 * Besides, should not left down and dragging be enough of a situation to
10352 * start a drag procedure?
10353 *
10354 * Anyways, guarded it to be active in touch situations only.
10355 */
10356 if (g_btouch && !m_inPinch) {
10357 struct timespec now;
10358 clock_gettime(CLOCK_MONOTONIC, &now);
10359 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10360
10361 bool trigger_hold = false;
10362 if (false == m_bChartDragging) {
10363 if (m_DragTrigger < 0) {
10364 // printf("\ntrigger1\n");
10365 m_DragTrigger = 0;
10366 m_DragTriggerStartTime = tnow;
10367 trigger_hold = true;
10368 } else {
10369 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10370 m_DragTrigger = -1; // Reset trigger
10371 // printf("trigger fired\n");
10372 }
10373 }
10374 }
10375 if (trigger_hold) return true;
10376
10377 if (false == m_bChartDragging) {
10378 // printf("starting drag\n");
10379 // Reset drag calculation members
10380 last_drag.x = x - 1, last_drag.y = y - 1;
10381 m_bChartDragging = true;
10382 m_chart_drag_total_time = 0;
10383 m_chart_drag_total_x = 0;
10384 m_chart_drag_total_y = 0;
10385 m_inertia_last_drag_x = x;
10386 m_inertia_last_drag_y = y;
10387 m_drag_vec_x.clear();
10388 m_drag_vec_y.clear();
10389 m_drag_vec_t.clear();
10390 m_last_drag_time = tnow;
10391 }
10392
10393 // Calculate and store drag dynamics.
10394 uint64_t delta_t = tnow - m_last_drag_time;
10395 double delta_tf = delta_t / 1e9;
10396
10397 m_chart_drag_total_time += delta_tf;
10398 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10399 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10400
10401 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10402 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10403 m_drag_vec_t.push_back(delta_tf);
10404
10405 m_inertia_last_drag_x = x;
10406 m_inertia_last_drag_y = y;
10407 m_last_drag_time = tnow;
10408
10409 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10410 m_bChartDragging = true;
10411 StartTimedMovement();
10412 m_pan_drag.x += last_drag.x - x;
10413 m_pan_drag.y += last_drag.y - y;
10414 last_drag.x = x, last_drag.y = y;
10415 }
10416 } else if (!g_btouch) {
10417 if ((last_drag.x != x) || (last_drag.y != y)) {
10418 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10419 // dragging on route create.
10420 // github #2994
10421 m_bChartDragging = true;
10422 StartTimedMovement();
10423 m_pan_drag.x += last_drag.x - x;
10424 m_pan_drag.y += last_drag.y - y;
10425 last_drag.x = x, last_drag.y = y;
10426 }
10427 }
10428 }
10429
10430 // Handle some special cases
10431 if (g_btouch) {
10432 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10433 // deactivate next LeftUp to ovoid creating an unexpected point
10434 m_ignore_next_leftup = true;
10435 m_DoubleClickTimer->Start();
10436 singleClickEventIsValid = false;
10437 }
10438 }
10439 }
10440
10441 return true;
10442}
10443
10444void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10445 if (MouseEventOverlayWindows(event)) return;
10446
10447 if (MouseEventSetup(event)) return; // handled, no further action required
10448
10449 bool nm = MouseEventProcessObjects(event);
10450 if (!nm) MouseEventProcessCanvas(event);
10451}
10452
10453void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10454 // Switch to the appropriate cursor on mouse movement
10455
10456 wxCursor *ptarget_cursor = pCursorArrow;
10457 if (!pPlugIn_Cursor) {
10458 ptarget_cursor = pCursorArrow;
10459 if ((!m_routeState) &&
10460 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10461 if (cursor_region == MID_RIGHT) {
10462 ptarget_cursor = pCursorRight;
10463 } else if (cursor_region == MID_LEFT) {
10464 ptarget_cursor = pCursorLeft;
10465 } else if (cursor_region == MID_TOP) {
10466 ptarget_cursor = pCursorDown;
10467 } else if (cursor_region == MID_BOT) {
10468 ptarget_cursor = pCursorUp;
10469 } else {
10470 ptarget_cursor = pCursorArrow;
10471 }
10472 } else if (m_bMeasure_Active ||
10473 m_routeState) // If Measure tool use Pencil Cursor
10474 ptarget_cursor = pCursorPencil;
10475 } else {
10476 ptarget_cursor = pPlugIn_Cursor;
10477 }
10478
10479 SetCursor(*ptarget_cursor);
10480}
10481
10482void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10483 SetCursor(*pCursorArrow);
10484}
10485
10486void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10487 ChartPlugInWrapper *target_plugin_chart = NULL;
10488 s57chart *Chs57 = NULL;
10489 wxFileName file;
10490 wxArrayString files;
10491
10492 ChartBase *target_chart = GetChartAtCursor();
10493 if (target_chart) {
10494 file.Assign(target_chart->GetFullPath());
10495 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10496 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10497 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10498 else
10499 Chs57 = dynamic_cast<s57chart *>(target_chart);
10500 } else { // target_chart = null, might be mbtiles
10501 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10502 unsigned int im = stackIndexArray.size();
10503 int scale = 2147483647; // max 32b integer
10504 if (VPoint.b_quilt && im > 0) {
10505 for (unsigned int is = 0; is < im; is++) {
10506 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10507 CHART_TYPE_MBTILES) {
10508 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10509 double lat, lon;
10510 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10511 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10512 .GetBBox()
10513 .Contains(lat, lon)) {
10514 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10515 scale) {
10516 scale =
10517 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10518 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10519 }
10520 }
10521 }
10522 }
10523 }
10524 }
10525
10526 std::vector<Ais8_001_22 *> area_notices;
10527
10528 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10529 float vp_scale = GetVPScale();
10530
10531 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10532 auto target_data = target.second;
10533 if (!target_data->area_notices.empty()) {
10534 for (auto &ani : target_data->area_notices) {
10535 Ais8_001_22 &area_notice = ani.second;
10536
10537 BoundingBox bbox;
10538
10539 for (Ais8_001_22_SubAreaList::iterator sa =
10540 area_notice.sub_areas.begin();
10541 sa != area_notice.sub_areas.end(); ++sa) {
10542 switch (sa->shape) {
10543 case AIS8_001_22_SHAPE_CIRCLE: {
10544 wxPoint target_point;
10545 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10546 bbox.Expand(target_point);
10547 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10548 break;
10549 }
10550 case AIS8_001_22_SHAPE_RECT: {
10551 wxPoint target_point;
10552 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10553 bbox.Expand(target_point);
10554 if (sa->e_dim_m > sa->n_dim_m)
10555 bbox.EnLarge(sa->e_dim_m * vp_scale);
10556 else
10557 bbox.EnLarge(sa->n_dim_m * vp_scale);
10558 break;
10559 }
10560 case AIS8_001_22_SHAPE_POLYGON:
10561 case AIS8_001_22_SHAPE_POLYLINE: {
10562 for (int i = 0; i < 4; ++i) {
10563 double lat = sa->latitude;
10564 double lon = sa->longitude;
10565 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10566 &lat, &lon);
10567 wxPoint target_point;
10568 GetCanvasPointPix(lat, lon, &target_point);
10569 bbox.Expand(target_point);
10570 }
10571 break;
10572 }
10573 case AIS8_001_22_SHAPE_SECTOR: {
10574 double lat1 = sa->latitude;
10575 double lon1 = sa->longitude;
10576 double lat, lon;
10577 wxPoint target_point;
10578 GetCanvasPointPix(lat1, lon1, &target_point);
10579 bbox.Expand(target_point);
10580 for (int i = 0; i < 18; ++i) {
10581 ll_gc_ll(
10582 lat1, lon1,
10583 sa->left_bound_deg +
10584 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10585 sa->radius_m / 1852.0, &lat, &lon);
10586 GetCanvasPointPix(lat, lon, &target_point);
10587 bbox.Expand(target_point);
10588 }
10589 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10590 &lat, &lon);
10591 GetCanvasPointPix(lat, lon, &target_point);
10592 bbox.Expand(target_point);
10593 break;
10594 }
10595 }
10596 }
10597
10598 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10599 area_notices.push_back(&area_notice);
10600 }
10601 }
10602 }
10603 }
10604 }
10605
10606 if (target_chart || !area_notices.empty() || file.HasName()) {
10607 // Go get the array of all objects at the cursor lat/lon
10608 int sel_rad_pix = 5;
10609 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10610
10611 // Make sure we always get the lights from an object, even if we are
10612 // currently not displaying lights on the chart.
10613
10614 SetCursor(wxCURSOR_WAIT);
10615 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10616 if (!lightsVis) SetShowENCLights(true);
10617 ;
10618
10619 ListOfObjRazRules *rule_list = NULL;
10620 ListOfPI_S57Obj *pi_rule_list = NULL;
10621 if (Chs57)
10622 rule_list =
10623 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10624 else if (target_plugin_chart)
10625 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10626 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10627
10628 ListOfObjRazRules *overlay_rule_list = NULL;
10629 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10630 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10631
10632 if (CHs57_Overlay) {
10633 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10634 zlat, zlon, SelectRadius, &GetVP());
10635 }
10636
10637 if (!lightsVis) SetShowENCLights(false);
10638
10639 wxString objText;
10640 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10641 wxString face = dFont->GetFaceName();
10642
10643 if (NULL == g_pObjectQueryDialog) {
10645 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10646 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10647 }
10648
10649 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10650 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10651
10652#ifdef __WXOSX__
10653 // Auto Adjustment for dark mode
10654 fg = g_pObjectQueryDialog->GetForegroundColour();
10655#endif
10656
10657 objText.Printf(
10658 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10659 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10660
10661#ifdef __WXOSX__
10662 int points = dFont->GetPointSize();
10663#else
10664 int points = dFont->GetPointSize() + 1;
10665#endif
10666
10667 int sizes[7];
10668 for (int i = -2; i < 5; i++) {
10669 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10670 }
10671 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10672
10673 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10674
10675 if (overlay_rule_list && CHs57_Overlay) {
10676 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10677 objText << "<hr noshade>";
10678 }
10679
10680 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10681 an != area_notices.end(); ++an) {
10682 objText << "<b>AIS Area Notice:</b> ";
10683 objText << ais8_001_22_notice_names[(*an)->notice_type];
10684 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10685 (*an)->sub_areas.begin();
10686 sa != (*an)->sub_areas.end(); ++sa)
10687 if (!sa->text.empty()) objText << sa->text;
10688 objText << "<br>expires: " << (*an)->expiry_time.Format();
10689 objText << "<hr noshade>";
10690 }
10691
10692 if (Chs57)
10693 objText << Chs57->CreateObjDescriptions(rule_list);
10694 else if (target_plugin_chart)
10695 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10696 pi_rule_list);
10697
10698 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10699
10700 // Add the additional info files
10701 wxString AddFiles, filenameOK;
10702 int filecount = 0;
10703 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10704 // plugin
10705
10706 AddFiles = wxString::Format(
10707 "<hr noshade><br><b>Additional info files attached to: </b> "
10708 "<font "
10709 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10710 "cellpadding=3>",
10711 file.GetFullName());
10712 file.Normalize();
10713 file.Assign(file.GetPath(), "");
10714 wxDir dir(file.GetFullPath());
10715 wxString filename;
10716 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10717 while (cont) {
10718 file.Assign(dir.GetNameWithSep().append(filename));
10719 wxString FormatString =
10720 "<td valign=top><font size=-2><a "
10721 "href=\"%s\">%s</a></font></td>";
10722 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10723 filenameOK = file.GetFullPath(); // remember last valid name
10724 // we are making a 3 columns table. New row only every third file
10725 if (3 * ((int)filecount / 3) == filecount)
10726 FormatString.Prepend("<tr>"); // new row
10727 else
10728 FormatString.Prepend(
10729 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10730 // spacer column
10731
10732 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10733 file.GetFullName());
10734 filecount++;
10735 }
10736 cont = dir.GetNext(&filename);
10737 }
10738 objText << AddFiles << "</table>";
10739 }
10740 objText << "</font>";
10741 objText << "</body></html>";
10742
10743 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10744 g_pObjectQueryDialog->SetHTMLPage(objText);
10745 g_pObjectQueryDialog->Show();
10746 }
10747 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10748 // generate an event to avoid double code
10749 wxHtmlLinkInfo hli(filenameOK);
10750 wxHtmlLinkEvent hle(1, hli);
10751 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10752 }
10753
10754 if (rule_list) rule_list->Clear();
10755 delete rule_list;
10756
10757 if (overlay_rule_list) overlay_rule_list->Clear();
10758 delete overlay_rule_list;
10759
10760 if (pi_rule_list) pi_rule_list->Clear();
10761 delete pi_rule_list;
10762
10763 SetCursor(wxCURSOR_ARROW);
10764 }
10765}
10766
10767void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10768 bool bNew = false;
10769 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10770 // Dialog
10771 g_pMarkInfoDialog = new MarkInfoDlg(this);
10772 bNew = true;
10773 }
10774
10775 if (1 /*g_bresponsive*/) {
10776 wxSize canvas_size = GetSize();
10777
10778 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10779 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10780
10781 g_pMarkInfoDialog->Layout();
10782
10783 wxPoint canvas_pos = GetPosition();
10784 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10785
10786 bool newFit = false;
10787 if (canvas_size.x < fitted_size.x) {
10788 fitted_size.x = canvas_size.x - 40;
10789 if (canvas_size.y < fitted_size.y)
10790 fitted_size.y -= 40; // scrollbar added
10791 }
10792 if (canvas_size.y < fitted_size.y) {
10793 fitted_size.y = canvas_size.y - 40;
10794 if (canvas_size.x < fitted_size.x)
10795 fitted_size.x -= 40; // scrollbar added
10796 }
10797
10798 if (newFit) {
10799 g_pMarkInfoDialog->SetSize(fitted_size);
10800 g_pMarkInfoDialog->Centre();
10801 }
10802 }
10803
10804 markPoint->m_bRPIsBeingEdited = false;
10805
10806 wxString title_base = _("Mark Properties");
10807 if (markPoint->m_bIsInRoute) {
10808 title_base = _("Waypoint Properties");
10809 }
10810 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10811 g_pMarkInfoDialog->UpdateProperties();
10812 if (markPoint->m_bIsInLayer) {
10813 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10814 GetLayerName(markPoint->m_LayerID)));
10815 g_pMarkInfoDialog->SetDialogTitle(caption);
10816 } else
10817 g_pMarkInfoDialog->SetDialogTitle(title_base);
10818
10819 g_pMarkInfoDialog->Show();
10820 g_pMarkInfoDialog->Raise();
10821 g_pMarkInfoDialog->InitialFocus();
10822 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10823}
10824
10825void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10826 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10827 pRoutePropDialog->SetRouteAndUpdate(selected);
10828 // pNew->UpdateProperties();
10829 pRoutePropDialog->Show();
10830 pRoutePropDialog->Raise();
10831 return;
10832 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10833 this); // There is one global instance of the RouteProp Dialog
10834
10835 if (g_bresponsive) {
10836 wxSize canvas_size = GetSize();
10837 wxPoint canvas_pos = GetPosition();
10838 wxSize fitted_size = pRoutePropDialog->GetSize();
10839 ;
10840
10841 if (canvas_size.x < fitted_size.x) {
10842 fitted_size.x = canvas_size.x;
10843 if (canvas_size.y < fitted_size.y)
10844 fitted_size.y -= 20; // scrollbar added
10845 }
10846 if (canvas_size.y < fitted_size.y) {
10847 fitted_size.y = canvas_size.y;
10848 if (canvas_size.x < fitted_size.x)
10849 fitted_size.x -= 20; // scrollbar added
10850 }
10851
10852 pRoutePropDialog->SetSize(fitted_size);
10853 pRoutePropDialog->Centre();
10854
10855 // int xp = (canvas_size.x - fitted_size.x)/2;
10856 // int yp = (canvas_size.y - fitted_size.y)/2;
10857
10858 wxPoint xxp = ClientToScreen(canvas_pos);
10859 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10860 }
10861
10862 pRoutePropDialog->SetRouteAndUpdate(selected);
10863
10864 pRoutePropDialog->Show();
10865
10866 Refresh(false);
10867}
10868
10869void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10870 pTrackPropDialog = TrackPropDlg::getInstance(
10871 this); // There is one global instance of the RouteProp Dialog
10872
10873 pTrackPropDialog->SetTrackAndUpdate(selected);
10875
10876 pTrackPropDialog->Show();
10877
10878 Refresh(false);
10879}
10880
10881void pupHandler_PasteWaypoint() {
10882 Kml kml;
10883
10884 int pasteBuffer = kml.ParsePasteBuffer();
10885 RoutePoint *pasted = kml.GetParsedRoutePoint();
10886 if (!pasted) return;
10887
10888 double nearby_radius_meters =
10889 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10890
10891 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10892 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10893
10894 int answer = wxID_NO;
10895 if (nearPoint && !nearPoint->m_bIsInLayer) {
10896 wxString msg;
10897 msg << _(
10898 "There is an existing waypoint at the same location as the one you are "
10899 "pasting. Would you like to merge the pasted data with it?\n\n");
10900 msg << _("Answering 'No' will create a new waypoint at the same location.");
10901 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10902 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10903 }
10904
10905 if (answer == wxID_YES) {
10906 nearPoint->SetName(pasted->GetName());
10907 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10908 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10909 pRouteManagerDialog->UpdateWptListCtrl();
10910 }
10911
10912 if (answer == wxID_NO) {
10913 RoutePoint *newPoint = new RoutePoint(pasted);
10914 newPoint->m_bIsolatedMark = true;
10915 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10916 newPoint);
10917 // pConfig->AddNewWayPoint(newPoint, -1);
10918 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10919
10920 pWayPointMan->AddRoutePoint(newPoint);
10921 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10922 pRouteManagerDialog->UpdateWptListCtrl();
10923 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10924 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10925 }
10926
10927 top_frame::Get()->InvalidateAllGL();
10928 top_frame::Get()->RefreshAllCanvas(false);
10929}
10930
10931void pupHandler_PasteRoute() {
10932 Kml kml;
10933
10934 int pasteBuffer = kml.ParsePasteBuffer();
10935 Route *pasted = kml.GetParsedRoute();
10936 if (!pasted) return;
10937
10938 double nearby_radius_meters =
10939 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10940
10941 RoutePoint *curPoint;
10942 RoutePoint *nearPoint;
10943 RoutePoint *prevPoint = NULL;
10944
10945 bool mergepoints = false;
10946 bool createNewRoute = true;
10947 int existingWaypointCounter = 0;
10948
10949 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10950 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10951 nearPoint = pWayPointMan->GetNearbyWaypoint(
10952 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10953 if (nearPoint) {
10954 mergepoints = true;
10955 existingWaypointCounter++;
10956 // Small hack here to avoid both extending RoutePoint and repeating all
10957 // the GetNearbyWaypoint calculations. Use existin data field in
10958 // RoutePoint as temporary storage.
10959 curPoint->m_bPtIsSelected = true;
10960 }
10961 }
10962
10963 int answer = wxID_NO;
10964 if (mergepoints) {
10965 wxString msg;
10966 msg << _(
10967 "There are existing waypoints at the same location as some of the ones "
10968 "you are pasting. Would you like to just merge the pasted data into "
10969 "them?\n\n");
10970 msg << _("Answering 'No' will create all new waypoints for this route.");
10971 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10972 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10973
10974 if (answer == wxID_CANCEL) {
10975 return;
10976 }
10977 }
10978
10979 // If all waypoints exist since before, and a route with the same name, we
10980 // don't create a new route.
10981 if (mergepoints && answer == wxID_YES &&
10982 existingWaypointCounter == pasted->GetnPoints()) {
10983 for (Route *proute : *pRouteList) {
10984 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10985 createNewRoute = false;
10986 break;
10987 }
10988 }
10989 }
10990
10991 Route *newRoute = 0;
10992 RoutePoint *newPoint = 0;
10993
10994 if (createNewRoute) {
10995 newRoute = new Route();
10996 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10997 }
10998
10999 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11000 curPoint = pasted->GetPoint(i);
11001 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11002 curPoint->m_bPtIsSelected = false;
11003 newPoint = pWayPointMan->GetNearbyWaypoint(
11004 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11005 newPoint->SetName(curPoint->GetName());
11006 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11007
11008 if (createNewRoute) newRoute->AddPoint(newPoint);
11009 } else {
11010 curPoint->m_bPtIsSelected = false;
11011
11012 newPoint = new RoutePoint(curPoint);
11013 newPoint->m_bIsolatedMark = false;
11014 newPoint->SetIconName("circle");
11015 newPoint->m_bIsVisible = true;
11016 newPoint->m_bShowName = false;
11017 newPoint->SetShared(false);
11018
11019 newRoute->AddPoint(newPoint);
11020 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11021 newPoint);
11022 // pConfig->AddNewWayPoint(newPoint, -1);
11023 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11024 pWayPointMan->AddRoutePoint(newPoint);
11025 }
11026 if (i > 1 && createNewRoute)
11027 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11028 curPoint->m_lat, curPoint->m_lon,
11029 prevPoint, newPoint, newRoute);
11030 prevPoint = newPoint;
11031 }
11032
11033 if (createNewRoute) {
11034 pRouteList->push_back(newRoute);
11035 // pConfig->AddNewRoute(newRoute); // use auto next num
11036 NavObj_dB::GetInstance().InsertRoute(newRoute);
11037
11038 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11039 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11040 }
11041
11042 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11043 pRouteManagerDialog->UpdateRouteListCtrl();
11044 pRouteManagerDialog->UpdateWptListCtrl();
11045 }
11046 top_frame::Get()->InvalidateAllGL();
11047 top_frame::Get()->RefreshAllCanvas(false);
11048 }
11049 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11050 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11051}
11052
11053void pupHandler_PasteTrack() {
11054 Kml kml;
11055
11056 int pasteBuffer = kml.ParsePasteBuffer();
11057 Track *pasted = kml.GetParsedTrack();
11058 if (!pasted) return;
11059
11060 TrackPoint *curPoint;
11061
11062 Track *newTrack = new Track();
11063 TrackPoint *newPoint;
11064 TrackPoint *prevPoint = NULL;
11065
11066 newTrack->SetName(pasted->GetName());
11067
11068 for (int i = 0; i < pasted->GetnPoints(); i++) {
11069 curPoint = pasted->GetPoint(i);
11070
11071 newPoint = new TrackPoint(curPoint);
11072
11073 wxDateTime now = wxDateTime::Now();
11074 newPoint->SetCreateTime(curPoint->GetCreateTime());
11075
11076 newTrack->AddPoint(newPoint);
11077
11078 if (prevPoint)
11079 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11080 newPoint->m_lat, newPoint->m_lon,
11081 prevPoint, newPoint, newTrack);
11082
11083 prevPoint = newPoint;
11084 }
11085
11086 g_TrackList.push_back(newTrack);
11087 // pConfig->AddNewTrack(newTrack);
11088 NavObj_dB::GetInstance().InsertTrack(newTrack);
11089
11090 top_frame::Get()->InvalidateAllGL();
11091 top_frame::Get()->RefreshAllCanvas(false);
11092}
11093
11094bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11095 wxJSONValue v;
11096 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11097 v["CursorPosition_x"] = x;
11098 v["CursorPosition_y"] = y;
11099 // Send a limited set of selection types depending on what is
11100 // found under the mouse point.
11101 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11102 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11103 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11104
11105 wxJSONWriter w;
11106 wxString out;
11107 w.Write(v, out);
11108 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11109
11110 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11111
11112#if 0
11113#define SELTYPE_UNKNOWN 0x0001
11114#define SELTYPE_ROUTEPOINT 0x0002
11115#define SELTYPE_ROUTESEGMENT 0x0004
11116#define SELTYPE_TIDEPOINT 0x0008
11117#define SELTYPE_CURRENTPOINT 0x0010
11118#define SELTYPE_ROUTECREATE 0x0020
11119#define SELTYPE_AISTARGET 0x0040
11120#define SELTYPE_MARKPOINT 0x0080
11121#define SELTYPE_TRACKSEGMENT 0x0100
11122#define SELTYPE_DRAGHANDLE 0x0200
11123#endif
11124
11125 if (g_bhide_context_menus) return true;
11126 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11127 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11128 m_pIDXCandidate, m_nmea_log);
11129
11130 Connect(
11131 wxEVT_COMMAND_MENU_SELECTED,
11132 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11133
11134#ifdef __WXGTK__
11135 // Funny requirement here for gtk, to clear the menu trigger event
11136 // TODO
11137 // Causes a slight "flasH" of the menu,
11138 if (m_inLongPress) {
11139 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11140 m_inLongPress = false;
11141 }
11142#endif
11143
11144 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11145
11146 Disconnect(
11147 wxEVT_COMMAND_MENU_SELECTED,
11148 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11149
11150 delete m_canvasMenu;
11151 m_canvasMenu = NULL;
11152
11153#ifdef __WXQT__
11154 // gFrame->SurfaceToolbar();
11155 // g_MainToolbar->Raise();
11156#endif
11157
11158 return true;
11159}
11160
11161void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11162 // Pass menu events from the canvas to the menu handler
11163 // This is necessarily in ChartCanvas since that is the menu's parent.
11164 if (m_canvasMenu) {
11165 m_canvasMenu->PopupMenuHandler(event);
11166 }
11167 return;
11168}
11169
11170void ChartCanvas::StartRoute() {
11171 // Do not allow more than one canvas to create a route at one time.
11172 if (g_brouteCreating) return;
11173
11174 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11175
11176 g_brouteCreating = true;
11177 m_routeState = 1;
11178 m_bDrawingRoute = false;
11179 SetCursor(*pCursorPencil);
11180 // SetCanvasToolbarItemState(ID_ROUTE, true);
11181 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11182
11183 HideGlobalToolbar();
11184
11185#ifdef __ANDROID__
11186 androidSetRouteAnnunciator(true);
11187#endif
11188}
11189
11190wxString ChartCanvas::FinishRoute() {
11191 m_routeState = 0;
11192 m_prev_pMousePoint = NULL;
11193 m_bDrawingRoute = false;
11194 wxString rv = "";
11195 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11196
11197 // SetCanvasToolbarItemState(ID_ROUTE, false);
11198 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11199#ifdef __ANDROID__
11200 androidSetRouteAnnunciator(false);
11201#endif
11202
11203 SetCursor(*pCursorArrow);
11204
11205 if (m_pMouseRoute) {
11206 if (m_bAppendingRoute) {
11207 // pConfig->UpdateRoute(m_pMouseRoute);
11208 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11209 } else {
11210 if (m_pMouseRoute->GetnPoints() > 1) {
11211 // pConfig->AddNewRoute(m_pMouseRoute);
11212 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11213 } else {
11214 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11215 m_pMouseRoute = NULL;
11216 }
11217 }
11218 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11219
11220 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11221 (pRoutePropDialog->IsShown())) {
11222 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11223 }
11224
11225 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11226 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11227 pRouteManagerDialog->UpdateRouteListCtrl();
11228 }
11229 }
11230 m_bAppendingRoute = false;
11231 m_pMouseRoute = NULL;
11232
11233 m_pSelectedRoute = NULL;
11234
11235 undo->InvalidateUndo();
11236 top_frame::Get()->RefreshAllCanvas(true);
11237
11238 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11239
11240 ShowGlobalToolbar();
11241
11242 g_brouteCreating = false;
11243
11244 return rv;
11245}
11246
11247void ChartCanvas::HideGlobalToolbar() {
11248 if (m_canvasIndex == 0) {
11249 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11250 }
11251}
11252
11253void ChartCanvas::ShowGlobalToolbar() {
11254 if (m_canvasIndex == 0) {
11255 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11256 }
11257}
11258
11259void ChartCanvas::ShowAISTargetList() {
11260 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11261 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11262 }
11263
11264 g_pAISTargetList->UpdateAISTargetList();
11265}
11266
11267void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11268 if (!m_bShowOutlines) return;
11269
11270 if (!ChartData) return;
11271
11272 int nEntry = ChartData->GetChartTableEntries();
11273
11274 for (int i = 0; i < nEntry; i++) {
11275 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11276
11277 // Check to see if the candidate chart is in the currently active group
11278 bool b_group_draw = false;
11279 if (m_groupIndex > 0) {
11280 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11281 int index = pt->GetGroupArray()[ig];
11282 if (m_groupIndex == index) {
11283 b_group_draw = true;
11284 break;
11285 }
11286 }
11287 } else
11288 b_group_draw = true;
11289
11290 if (b_group_draw) RenderChartOutline(dc, i, vp);
11291 }
11292
11293 // On CM93 Composite Charts, draw the outlines of the next smaller
11294 // scale cell
11295 cm93compchart *pcm93 = NULL;
11296 if (VPoint.b_quilt) {
11297 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11298 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11299 pcm93 = (cm93compchart *)pch;
11300 break;
11301 }
11302 } else if (m_singleChart &&
11303 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11304 pcm93 = (cm93compchart *)m_singleChart;
11305
11306 if (pcm93) {
11307 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11308 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11309
11310 if (zoom_factor > 8.0) {
11311 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11312 dc.SetPen(mPen);
11313 } else {
11314 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11315 dc.SetPen(mPen);
11316 }
11317
11318 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11319 }
11320}
11321
11322void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11323#ifdef ocpnUSE_GL
11324 if (g_bopengl && m_glcc) {
11325 /* opengl version specially optimized */
11326 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11327 return;
11328 }
11329#endif
11330
11331 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11332 if (!ChartData->IsChartAvailable(dbIndex)) return;
11333 }
11334
11335 float plylat, plylon;
11336 float plylat1, plylon1;
11337
11338 int pixx, pixy, pixx1, pixy1;
11339
11340 LLBBox box;
11341 ChartData->GetDBBoundingBox(dbIndex, box);
11342
11343 // Don't draw an outline in the case where the chart covers the entire world
11344 // */
11345 if (box.GetLonRange() == 360) return;
11346
11347 double lon_bias = 0;
11348 // chart is outside of viewport lat/lon bounding box
11349 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11350
11351 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11352
11353 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11354 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11355
11356 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11357 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11358
11359 else
11360 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11361
11362 // Are there any aux ply entries?
11363 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11364 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11365 {
11366 wxPoint r, r1;
11367
11368 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11369 plylon += lon_bias;
11370
11371 GetCanvasPointPix(plylat, plylon, &r);
11372 pixx = r.x;
11373 pixy = r.y;
11374
11375 for (int i = 0; i < nPly - 1; i++) {
11376 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11377 plylon1 += lon_bias;
11378
11379 GetCanvasPointPix(plylat1, plylon1, &r1);
11380 pixx1 = r1.x;
11381 pixy1 = r1.y;
11382
11383 int pixxs1 = pixx1;
11384 int pixys1 = pixy1;
11385
11386 bool b_skip = false;
11387
11388 if (vp.chart_scale > 5e7) {
11389 // calculate projected distance between these two points in meters
11390 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11391 pow((double)(pixy1 - pixy), 2)) /
11392 vp.view_scale_ppm;
11393
11394 if (dist > 0.0) {
11395 // calculate GC distance between these two points in meters
11396 double distgc =
11397 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11398
11399 // If the distances are nonsense, it means that the scale is very
11400 // small and the segment wrapped the world So skip it....
11401 // TODO improve this to draw two segments
11402 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11403 b_skip = true;
11404 } else
11405 b_skip = true;
11406 }
11407
11408 ClipResult res = cohen_sutherland_line_clip_i(
11409 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11410 if (res != Invisible && !b_skip)
11411 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11412
11413 plylat = plylat1;
11414 plylon = plylon1;
11415 pixx = pixxs1;
11416 pixy = pixys1;
11417 }
11418
11419 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11420 plylon1 += lon_bias;
11421
11422 GetCanvasPointPix(plylat1, plylon1, &r1);
11423 pixx1 = r1.x;
11424 pixy1 = r1.y;
11425
11426 ClipResult res = cohen_sutherland_line_clip_i(
11427 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11428 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11429 }
11430
11431 else // Use Aux PlyPoints
11432 {
11433 wxPoint r, r1;
11434
11435 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11436 for (int j = 0; j < nAuxPlyEntries; j++) {
11437 int nAuxPly =
11438 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11439 GetCanvasPointPix(plylat, plylon, &r);
11440 pixx = r.x;
11441 pixy = r.y;
11442
11443 for (int i = 0; i < nAuxPly - 1; i++) {
11444 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11445
11446 GetCanvasPointPix(plylat1, plylon1, &r1);
11447 pixx1 = r1.x;
11448 pixy1 = r1.y;
11449
11450 int pixxs1 = pixx1;
11451 int pixys1 = pixy1;
11452
11453 bool b_skip = false;
11454
11455 if (vp.chart_scale > 5e7) {
11456 // calculate projected distance between these two points in meters
11457 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11458 ((pixy1 - pixy) * (pixy1 - pixy))) /
11459 vp.view_scale_ppm;
11460 if (dist > 0.0) {
11461 // calculate GC distance between these two points in meters
11462 double distgc =
11463 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11464
11465 // If the distances are nonsense, it means that the scale is very
11466 // small and the segment wrapped the world So skip it....
11467 // TODO improve this to draw two segments
11468 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11469 b_skip = true;
11470 } else
11471 b_skip = true;
11472 }
11473
11474 ClipResult res = cohen_sutherland_line_clip_i(
11475 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11476 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11477
11478 plylat = plylat1;
11479 plylon = plylon1;
11480 pixx = pixxs1;
11481 pixy = pixys1;
11482 }
11483
11484 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11485 GetCanvasPointPix(plylat1, plylon1, &r1);
11486 pixx1 = r1.x;
11487 pixy1 = r1.y;
11488
11489 ClipResult res = cohen_sutherland_line_clip_i(
11490 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11491 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11492 }
11493 }
11494}
11495
11496static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11497 const wxArrayString &legend) {
11498 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11499
11500 int pointsize = dFont->GetPointSize();
11501 pointsize /= OCPN_GetWinDIPScaleFactor();
11502
11503 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11504 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11505 false, dFont->GetFaceName());
11506
11507 dc.SetFont(*psRLI_font);
11508
11509 int h = 0;
11510 int w = 0;
11511 int hl, wl;
11512
11513 int xp, yp;
11514 int hilite_offset = 3;
11515
11516 for (wxString line : legend) {
11517#ifdef __WXMAC__
11518 wxScreenDC sdc;
11519 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11520#else
11521 dc.GetTextExtent(line, &wl, &hl);
11522 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11523 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11524#endif
11525 h += hl;
11526 w = wxMax(w, wl);
11527 }
11528 w += (hl / 2); // Add a little right pad
11529
11530 xp = ref_point.x - w;
11531 yp = ref_point.y;
11532 yp += hilite_offset;
11533
11534 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11535
11536 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11537 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11538
11539 for (wxString line : legend) {
11540 dc.DrawText(line, xp, yp);
11541 yp += hl;
11542 }
11543}
11544
11545void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11546 if (!g_bAllowShipToActive) return;
11547
11548 Route *rt = g_pRouteMan->GetpActiveRoute();
11549 if (!rt) return;
11550
11551 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11552 wxPoint2DDouble pa, pb;
11554 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11555
11556 // set pen
11557 int width =
11558 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11559 if (rt->m_width != wxPENSTYLE_INVALID)
11560 width = rt->m_width; // set route pen style if any
11561 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11562 g_shipToActiveStyle, 5)]; // get setting pen style
11563 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11564 wxColour color =
11565 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11566 : // set setting route pen color
11567 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11568 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11569
11570 dc.SetPen(*mypen);
11571 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11572
11573 if (!Use_Opengl)
11574 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11575 (int)pb.m_y, GetVP(), true);
11576
11577#ifdef ocpnUSE_GL
11578 else {
11579#ifdef USE_ANDROID_GLES2
11580 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11581#else
11582 if (style != wxPENSTYLE_SOLID) {
11583 if (glChartCanvas::dash_map.find(style) !=
11584 glChartCanvas::dash_map.end()) {
11585 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11586 dc.SetPen(*mypen);
11587 }
11588 }
11589 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11590#endif
11591
11592 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11593 (int)pb.m_x, (int)pb.m_y, GetVP());
11594 }
11595#endif
11596 }
11597}
11598
11599void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11600 Route *route = 0;
11601 if (m_routeState >= 2) route = m_pMouseRoute;
11602 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11603 route = m_pMeasureRoute;
11604
11605 if (!route) return;
11606
11607 // Validate route pointer
11608 if (!g_pRouteMan->IsRouteValid(route)) return;
11609
11610 double render_lat = m_cursor_lat;
11611 double render_lon = m_cursor_lon;
11612
11613 int np = route->GetnPoints();
11614 if (np) {
11615 if (g_btouch && (np > 1)) np--;
11616 RoutePoint rp = route->GetPoint(np);
11617 render_lat = rp.m_lat;
11618 render_lon = rp.m_lon;
11619 }
11620
11621 double rhumbBearing, rhumbDist;
11622 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11623 &rhumbBearing, &rhumbDist);
11624 double brg = rhumbBearing;
11625 double dist = rhumbDist;
11626
11627 // Skip GreatCircle rubberbanding on touch devices.
11628 if (!g_btouch) {
11629 double gcBearing, gcBearing2, gcDist;
11630 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11631 m_cursor_lat, &gcDist, &gcBearing,
11632 &gcBearing2);
11633 double gcDistm = gcDist / 1852.0;
11634
11635 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11636 rhumbBearing = 90.;
11637
11638 wxPoint destPoint, lastPoint;
11639
11640 route->m_NextLegGreatCircle = false;
11641 int milesDiff = rhumbDist - gcDistm;
11642 if (milesDiff > 1) {
11643 brg = gcBearing;
11644 dist = gcDistm;
11645 route->m_NextLegGreatCircle = true;
11646 }
11647
11648 // FIXME (MacOS, the first segment is rendered wrong)
11649 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11650 &lastPoint);
11651
11652 if (route->m_NextLegGreatCircle) {
11653 for (int i = 1; i <= milesDiff; i++) {
11654 double p = (double)i * (1.0 / (double)milesDiff);
11655 double pLat, pLon;
11656 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11657 &pLon, &pLat, &gcBearing2);
11658 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11659 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11660 false);
11661 lastPoint = destPoint;
11662 }
11663 } else {
11664 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11665 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11666 false);
11667 if (m_bMeasure_DistCircle) {
11668 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11669 powf((float)(r_rband.y - lastPoint.y), 2));
11670
11671 dc.SetPen(*g_pRouteMan->GetRoutePen());
11672 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11673 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11674 }
11675 }
11676 }
11677 }
11678
11679 wxString routeInfo;
11680 wxArrayString infoArray;
11681 double varBrg = 0;
11682 if (g_bShowTrue)
11683 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11684 0x00B0);
11685
11686 if (g_bShowMag) {
11687 double latAverage = (m_cursor_lat + render_lat) / 2;
11688 double lonAverage = (m_cursor_lon + render_lon) / 2;
11689 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11690
11691 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11692 (int)varBrg, 0x00B0);
11693 }
11694 routeInfo << " " << FormatDistanceAdaptive(dist);
11695 infoArray.Add(routeInfo);
11696 routeInfo.Clear();
11697
11698 // To make it easier to use a route as a bearing on a charted object add for
11699 // the first leg also the reverse bearing.
11700 if (np == 1) {
11701 routeInfo << "Reverse: ";
11702 if (g_bShowTrue)
11703 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11704 (int)(brg + 180.) % 360, 0x00B0);
11705 if (g_bShowMag)
11706 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11707 (int)(varBrg + 180.) % 360, 0x00B0);
11708 infoArray.Add(routeInfo);
11709 routeInfo.Clear();
11710 }
11711
11712 wxString s0;
11713 if (!route->m_bIsInLayer)
11714 s0.Append(_("Route") + ": ");
11715 else
11716 s0.Append(_("Layer Route: "));
11717
11718 double disp_length = route->m_route_length;
11719 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11720 s0 += FormatDistanceAdaptive(disp_length);
11721
11722 infoArray.Add(s0);
11723 routeInfo.Clear();
11724
11725 RouteLegInfo(dc, r_rband, infoArray);
11726
11727 m_brepaint_piano = true;
11728}
11729
11730void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11731 if (!m_bShowVisibleSectors) return;
11732
11733 if (g_bDeferredInitDone) {
11734 // need to re-evaluate sectors?
11735 double rhumbBearing, rhumbDist;
11736 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11737 &rhumbBearing, &rhumbDist);
11738
11739 if (rhumbDist > 0.05) // miles
11740 {
11741 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11742 m_sectorlegsVisible);
11743 m_sector_glat = gLat;
11744 m_sector_glon = gLon;
11745 }
11746 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11747 }
11748}
11749
11750void ChartCanvas::WarpPointerDeferred(int x, int y) {
11751 warp_x = x;
11752 warp_y = y;
11753 warp_flag = true;
11754}
11755
11756int s_msg;
11757
11758void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11759 if (!ps52plib) return;
11760
11761 if (VPoint.b_quilt) { // quilted
11762 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11763
11764 if (m_pQuilt->IsQuiltVector()) {
11765 if (ps52plib->GetStateHash() != m_s52StateHash) {
11766 UpdateS52State();
11767 m_s52StateHash = ps52plib->GetStateHash();
11768 }
11769 }
11770 } else {
11771 if (ps52plib->GetStateHash() != m_s52StateHash) {
11772 UpdateS52State();
11773 m_s52StateHash = ps52plib->GetStateHash();
11774 }
11775 }
11776
11777 // Plugin charts
11778 bool bSendPlibState = true;
11779 if (VPoint.b_quilt) { // quilted
11780 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11781 }
11782
11783 if (bSendPlibState) {
11784 wxJSONValue v;
11785 v["OpenCPN Version Major"] = VERSION_MAJOR;
11786 v["OpenCPN Version Minor"] = VERSION_MINOR;
11787 v["OpenCPN Version Patch"] = VERSION_PATCH;
11788 v["OpenCPN Version Date"] = VERSION_DATE;
11789 v["OpenCPN Version Full"] = VERSION_FULL;
11790
11791 // S52PLIB state
11792 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11793 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11794 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11795 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11796 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11797 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11798 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11799
11800 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11801
11802 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11803 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11804
11805 // Global S52 options
11806
11807 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11808 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11809 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11810 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11811 ps52plib->m_bShowS57ImportantTextOnly;
11812 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11813 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11814 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11815 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11816 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11817
11818 // Some global GUI parameters, for completeness
11819 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11820 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11821 v["OpenCPN Scale Factor Exp"] =
11822 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11823 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11824
11825 wxJSONWriter w;
11826 wxString out;
11827 w.Write(v, out);
11828
11829 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11830 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11831 g_lastS52PLIBPluginMessage = out;
11832 }
11833 }
11834}
11835int spaint;
11836int s_in_update;
11837void ChartCanvas::OnPaint(wxPaintEvent &event) {
11838 wxPaintDC dc(this);
11839
11840 // GetToolbar()->Show( m_bToolbarEnable );
11841
11842 // Paint updates may have been externally disabled (temporarily, to avoid
11843 // Yield() recursion performance loss) It is important that the wxPaintDC is
11844 // built, even if we elect to not process this paint message. Otherwise, the
11845 // paint message may not be removed from the message queue, esp on Windows.
11846 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11847
11848 if (!m_b_paint_enable) {
11849 return;
11850 }
11851
11852 // If necessary, reconfigure the S52 PLIB
11854
11855#ifdef ocpnUSE_GL
11856 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11857
11858 if (m_glcc && g_bopengl) {
11859 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11860 s_in_update++;
11861 m_glcc->Update();
11862 s_in_update--;
11863 }
11864
11865 return;
11866 }
11867#endif
11868
11869 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11870
11871 wxRegion ru = GetUpdateRegion();
11872
11873 int rx, ry, rwidth, rheight;
11874 ru.GetBox(rx, ry, rwidth, rheight);
11875
11876#ifdef ocpnUSE_DIBSECTION
11877 ocpnMemDC temp_dc;
11878#else
11879 wxMemoryDC temp_dc;
11880#endif
11881
11882 long height = GetVP().pix_height;
11883
11884#ifdef __WXMAC__
11885 // On OS X we have to explicitly extend the region for the piano area
11886 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11887 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11888 height += m_Piano->GetHeight();
11889#endif // __WXMAC__
11890 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11891
11892 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11893 if (pthumbwin) {
11894 int thumbx, thumby, thumbsx, thumbsy;
11895 pthumbwin->GetPosition(&thumbx, &thumby);
11896 pthumbwin->GetSize(&thumbsx, &thumbsy);
11897 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11898
11899 if (pthumbwin->IsShown()) {
11900 rgn_chart.Subtract(rgn_thumbwin);
11901 ru.Subtract(rgn_thumbwin);
11902 }
11903 }
11904
11905 // subtract the chart bar if it isn't transparent, and determine if we need to
11906 // paint it
11907 wxRegion rgn_blit = ru;
11908 if (g_bShowChartBar) {
11909 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11910 GetClientSize().x, m_Piano->GetHeight());
11911
11912 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11913 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11914 if (style->chartStatusWindowTransparent)
11915 m_brepaint_piano = true;
11916 else
11917 ru.Subtract(chart_bar_rect);
11918 }
11919 }
11920
11921 if (m_Compass && m_Compass->IsShown()) {
11922 wxRect compassRect = m_Compass->GetRect();
11923 if (ru.Contains(compassRect) != wxOutRegion) {
11924 ru.Subtract(compassRect);
11925 }
11926 }
11927
11928 if (m_notification_button) {
11929 wxRect noteRect = m_notification_button->GetRect();
11930 if (ru.Contains(noteRect) != wxOutRegion) {
11931 ru.Subtract(noteRect);
11932 }
11933 }
11934
11935 // Is this viewpoint the same as the previously painted one?
11936 bool b_newview = true;
11937
11938 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11939 (m_cache_vp.rotation == VPoint.rotation) &&
11940 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11941 m_cache_vp.IsValid()) {
11942 b_newview = false;
11943 }
11944
11945 // If the ViewPort is skewed or rotated, we may be able to use the cached
11946 // rotated bitmap.
11947 bool b_rcache_ok = false;
11948 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11949 b_rcache_ok = !b_newview;
11950
11951 // Make a special VP
11952 if (VPoint.b_MercatorProjectionOverride)
11953 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11954 ViewPort svp = VPoint;
11955
11956 svp.pix_width = svp.rv_rect.width;
11957 svp.pix_height = svp.rv_rect.height;
11958
11959 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11960 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11961 // VPoint.rv_rect.height);
11962
11963 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11964
11965 // If we are going to use the cached rotated image, there is no need to fetch
11966 // any chart data and this will do it...
11967 if (b_rcache_ok) chart_get_region.Clear();
11968
11969 // Blit pan acceleration
11970 if (VPoint.b_quilt) // quilted
11971 {
11972 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11973
11974 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11975
11976 bool busy = false;
11977 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11978 m_cache_vp.rotation != VPoint.rotation)) {
11979 AbstractPlatform::ShowBusySpinner();
11980 busy = true;
11981 }
11982
11983 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11984 (m_working_bm.GetHeight() != svp.pix_height))
11985 m_working_bm.Create(svp.pix_width, svp.pix_height,
11986 -1); // make sure the target is big enoug
11987
11988 if (fabs(VPoint.rotation) < 0.01) {
11989 bool b_save = true;
11990
11991 if (g_SencThreadManager) {
11992 if (g_SencThreadManager->GetJobCount()) {
11993 b_save = false;
11994 m_cache_vp.Invalidate();
11995 }
11996 }
11997
11998 // If the saved wxBitmap from last OnPaint is useable
11999 // calculate the blit parameters
12000
12001 // We can only do screen blit painting if subsequent ViewPorts differ by
12002 // whole pixels So, in small scale bFollow mode, force the full screen
12003 // render. This seems a hack....There may be better logic here.....
12004
12005 // if(m_bFollow)
12006 // b_save = false;
12007
12008 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12009 if (b_newview) {
12010 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12011 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12012
12013 int dy = c_new.y - c_old.y;
12014 int dx = c_new.x - c_old.x;
12015
12016 // printf("In OnPaint Trying Blit dx: %d
12017 // dy:%d\n\n", dx, dy);
12018
12019 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12020 if (dx || dy) {
12021 // Blit the reuseable portion of the cached wxBitmap to a working
12022 // bitmap
12023 temp_dc.SelectObject(m_working_bm);
12024
12025 wxMemoryDC cache_dc;
12026 cache_dc.SelectObject(m_cached_chart_bm);
12027
12028 if (dy > 0) {
12029 if (dx > 0) {
12030 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12031 VPoint.pix_height - dy, &cache_dc, dx, dy);
12032 } else {
12033 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12034 VPoint.pix_height - dy, &cache_dc, 0, dy);
12035 }
12036
12037 } else {
12038 if (dx > 0) {
12039 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12040 VPoint.pix_height + dy, &cache_dc, dx, 0);
12041 } else {
12042 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12043 VPoint.pix_height + dy, &cache_dc, 0, 0);
12044 }
12045 }
12046
12047 OCPNRegion update_region;
12048 if (dy) {
12049 if (dy > 0)
12050 update_region.Union(
12051 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12052 else
12053 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12054 }
12055
12056 if (dx) {
12057 if (dx > 0)
12058 update_region.Union(
12059 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12060 else
12061 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12062 }
12063
12064 // Render the new region
12065 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12066 update_region);
12067 cache_dc.SelectObject(wxNullBitmap);
12068 } else {
12069 // No sensible (dx, dy) change in the view, so use the cached
12070 // member bitmap
12071 temp_dc.SelectObject(m_cached_chart_bm);
12072 b_save = false;
12073 }
12074 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12075
12076 } else // not blitable
12077 {
12078 temp_dc.SelectObject(m_working_bm);
12079 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12080 chart_get_region);
12081 }
12082 } else {
12083 // No change in the view, so use the cached member bitmap2
12084 temp_dc.SelectObject(m_cached_chart_bm);
12085 b_save = false;
12086 }
12087 } else // cached bitmap is not yet valid
12088 {
12089 temp_dc.SelectObject(m_working_bm);
12090 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12091 chart_get_region);
12092 }
12093
12094 // Save the fully rendered quilt image as a wxBitmap member of this class
12095 if (b_save) {
12096 // if((m_cached_chart_bm.GetWidth() !=
12097 // svp.pix_width) ||
12098 // (m_cached_chart_bm.GetHeight() !=
12099 // svp.pix_height))
12100 // m_cached_chart_bm.Create(svp.pix_width,
12101 // svp.pix_height, -1); // target wxBitmap
12102 // is big enough
12103 wxMemoryDC scratch_dc_0;
12104 scratch_dc_0.SelectObject(m_cached_chart_bm);
12105 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12106
12107 scratch_dc_0.SelectObject(wxNullBitmap);
12108
12109 m_bm_cache_vp =
12110 VPoint; // save the ViewPort associated with the cached wxBitmap
12111 }
12112 }
12113
12114 else // quilted, rotated
12115 {
12116 temp_dc.SelectObject(m_working_bm);
12117 OCPNRegion chart_get_all_region(
12118 wxRect(0, 0, svp.pix_width, svp.pix_height));
12119 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12120 chart_get_all_region);
12121 }
12122
12123 AbstractPlatform::HideBusySpinner();
12124
12125 }
12126
12127 else // not quilted
12128 {
12129 if (!m_singleChart) {
12130 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12131 dc.Clear();
12132 return;
12133 }
12134
12135 if (!chart_get_region.IsEmpty()) {
12136 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12137 }
12138 }
12139
12140 if (temp_dc.IsOk()) {
12141 // Arrange to render the World Chart vector data behind the rendered
12142 // current chart so that uncovered canvas areas show at least the world
12143 // chart.
12144 OCPNRegion chartValidRegion;
12145 if (!VPoint.b_quilt) {
12146 // Make a region covering the current chart on the canvas
12147
12148 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12149 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12150 else {
12151 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12152 // require that the viewport passed here have pix_width and pix_height
12153 // set to the actual display, not the virtual (rv_rect) sizes
12154 // (the vector calculations require the virtual sizes in svp)
12155
12156 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12157 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12158 }
12159 } else
12160 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12161
12162 temp_dc.DestroyClippingRegion();
12163
12164 // Copy current chart region
12165 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12166
12167 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12168
12169 if (!backgroundRegion.IsEmpty()) {
12170 // Draw the Background Chart only in the areas NOT covered by the
12171 // current chart view
12172
12173 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12174 clipping regions with more than 1 rectangle so... */
12175 wxColour water = pWorldBackgroundChart->water;
12176 if (water.IsOk()) {
12177 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12178 temp_dc.SetBrush(wxBrush(water));
12179 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12180 while (upd.HaveRects()) {
12181 wxRect rect = upd.GetRect();
12182 temp_dc.DrawRectangle(rect);
12183 upd.NextRect();
12184 }
12185 }
12186 // Associate with temp_dc
12187 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12188 temp_dc.SetDeviceClippingRegion(*clip_region);
12189 delete clip_region;
12190
12191 ocpnDC bgdc(temp_dc);
12192 double r = VPoint.rotation;
12193 SetVPRotation(VPoint.skew);
12194
12195 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12196 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12197
12198 SetVPRotation(r);
12199 }
12200 } // temp_dc.IsOk();
12201
12202 wxMemoryDC *pChartDC = &temp_dc;
12203 wxMemoryDC rotd_dc;
12204
12205 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12206 // Can we use the current rotated image cache?
12207 if (!b_rcache_ok) {
12208#ifdef __WXMSW__
12209 wxMemoryDC tbase_dc;
12210 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12211 tbase_dc.SelectObject(bm_base);
12212 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12213 tbase_dc.SelectObject(wxNullBitmap);
12214#else
12215 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12216#endif
12217
12218 wxImage base_image;
12219 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12220
12221 // Use a local static image rotator to improve wxWidgets code profile
12222 // Especially, on GTK the wxRound and wxRealPoint functions are very
12223 // expensive.....
12224
12225 double angle = GetVP().skew - GetVP().rotation;
12226 wxImage ri;
12227 bool b_rot_ok = false;
12228 if (base_image.IsOk()) {
12229 ViewPort rot_vp = GetVP();
12230
12231 m_b_rot_hidef = false;
12232
12233 ri = Image_Rotate(
12234 base_image, angle,
12235 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12236 m_b_rot_hidef, &m_roffset);
12237
12238 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12239 (rot_vp.rotation == VPoint.rotation) &&
12240 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12241 rot_vp.IsValid() && (ri.IsOk())) {
12242 b_rot_ok = true;
12243 }
12244 }
12245
12246 if (b_rot_ok) {
12247 delete m_prot_bm;
12248 m_prot_bm = new wxBitmap(ri);
12249 }
12250
12251 m_roffset.x += VPoint.rv_rect.x;
12252 m_roffset.y += VPoint.rv_rect.y;
12253 }
12254
12255 if (m_prot_bm && m_prot_bm->IsOk()) {
12256 rotd_dc.SelectObject(*m_prot_bm);
12257 pChartDC = &rotd_dc;
12258 } else {
12259 pChartDC = &temp_dc;
12260 m_roffset = wxPoint(0, 0);
12261 }
12262 } else { // unrotated
12263 pChartDC = &temp_dc;
12264 m_roffset = wxPoint(0, 0);
12265 }
12266
12267 wxPoint offset = m_roffset;
12268
12269 // Save the PixelCache viewpoint for next time
12270 m_cache_vp = VPoint;
12271
12272 // Set up a scratch DC for overlay objects
12273 wxMemoryDC mscratch_dc;
12274 mscratch_dc.SelectObject(*pscratch_bm);
12275
12276 mscratch_dc.ResetBoundingBox();
12277 mscratch_dc.DestroyClippingRegion();
12278 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12279
12280 // Blit the externally invalidated areas of the chart onto the scratch dc
12281 wxRegionIterator upd(rgn_blit); // get the update rect list
12282 while (upd) {
12283 wxRect rect = upd.GetRect();
12284
12285 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12286 rect.x - offset.x, rect.y - offset.y);
12287 upd++;
12288 }
12289
12290 // If multi-canvas, indicate which canvas has keyboard focus
12291 // by drawing a simple blue bar at the top.
12292 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12293 if (this == wxWindow::FindFocus()) {
12294 g_focusCanvas = this;
12295
12296 wxColour colour = GetGlobalColor("BLUE4");
12297 mscratch_dc.SetPen(wxPen(colour));
12298 mscratch_dc.SetBrush(wxBrush(colour));
12299
12300 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12301 mscratch_dc.DrawRectangle(activeRect);
12302 }
12303 }
12304
12305 // Any MBtiles?
12306 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12307 unsigned int im = stackIndexArray.size();
12308 if (VPoint.b_quilt && im > 0) {
12309 std::vector<int> tiles_to_show;
12310 for (unsigned int is = 0; is < im; is++) {
12311 const ChartTableEntry &cte =
12312 ChartData->GetChartTableEntry(stackIndexArray[is]);
12313 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12314 continue;
12315 }
12316 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12317 tiles_to_show.push_back(stackIndexArray[is]);
12318 }
12319 }
12320
12321 if (tiles_to_show.size())
12322 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12323 }
12324
12325 // May get an unexpected OnPaint call while switching display modes
12326 // Guard for that.
12327 if (!g_bopengl) {
12328 ocpnDC scratch_dc(mscratch_dc);
12329 RenderAlertMessage(mscratch_dc, GetVP());
12330 }
12331
12332#if 0
12333 // quiting?
12334 if (g_bquiting) {
12335#ifdef ocpnUSE_DIBSECTION
12336 ocpnMemDC q_dc;
12337#else
12338 wxMemoryDC q_dc;
12339#endif
12340 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12341 q_dc.SelectObject(qbm);
12342
12343 // Get a copy of the screen
12344 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12345
12346 // Draw a rectangle over the screen with a stipple brush
12347 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12348 q_dc.SetBrush(qbr);
12349 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12350
12351 // Blit back into source
12352 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12353 wxCOPY);
12354
12355 q_dc.SelectObject(wxNullBitmap);
12356 }
12357#endif
12358
12359#if 0
12360 // It is possible that this two-step method may be reuired for some platforms.
12361 // So, retain in the code base to aid recovery if necessary
12362
12363 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12364 if( VPoint.b_quilt ) {
12365 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12366 ChartBase *chart = m_pQuilt->GetRefChart();
12367 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12368
12369 // Clear the text Global declutter list
12370 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12371 if(ChPI)
12372 ChPI->ClearPLIBTextList();
12373 else{
12374 if(ps52plib)
12375 ps52plib->ClearTextList();
12376 }
12377
12378 wxMemoryDC t_dc;
12379 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12380
12381 wxColor maskBackground = wxColour(1,0,0);
12382 t_dc.SelectObject( qbm );
12383 t_dc.SetBackground(wxBrush(maskBackground));
12384 t_dc.Clear();
12385
12386 // Copy the scratch DC into the new bitmap
12387 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12388
12389 // Render the text to the new bitmap
12390 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12391 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12392
12393 // Copy the new bitmap back to the scratch dc
12394 wxRegionIterator upd_final( ru );
12395 while( upd_final ) {
12396 wxRect rect = upd_final.GetRect();
12397 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12398 upd_final++;
12399 }
12400
12401 t_dc.SelectObject( wxNullBitmap );
12402 }
12403 }
12404 }
12405#endif
12406 // Direct rendering model...
12407 if (VPoint.b_quilt) {
12408 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12409 ChartBase *chart = m_pQuilt->GetRefChart();
12410 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12411 // Clear the text Global declutter list
12412 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12413 if (ChPI)
12414 ChPI->ClearPLIBTextList();
12415 else {
12416 if (ps52plib) ps52plib->ClearTextList();
12417 }
12418
12419 // Render the text directly to the scratch bitmap
12420 OCPNRegion chart_all_text_region(
12421 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12422
12423 if (g_bShowChartBar && m_Piano) {
12424 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12425 GetVP().pix_width, m_Piano->GetHeight());
12426
12427 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12428 if (!style->chartStatusWindowTransparent)
12429 chart_all_text_region.Subtract(chart_bar_rect);
12430 }
12431
12432 if (m_Compass && m_Compass->IsShown()) {
12433 wxRect compassRect = m_Compass->GetRect();
12434 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12435 chart_all_text_region.Subtract(compassRect);
12436 }
12437 }
12438
12439 mscratch_dc.DestroyClippingRegion();
12440
12441 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12442 chart_all_text_region);
12443 }
12444 }
12445 }
12446
12447 // Now that charts are fully rendered, apply the overlay objects as decals.
12448 ocpnDC scratch_dc(mscratch_dc);
12449 DrawOverlayObjects(scratch_dc, ru);
12450
12451 // And finally, blit the scratch dc onto the physical dc
12452 wxRegionIterator upd_final(rgn_blit);
12453 while (upd_final) {
12454 wxRect rect = upd_final.GetRect();
12455 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12456 rect.y);
12457 upd_final++;
12458 }
12459
12460 // Deselect the chart bitmap from the temp_dc, so that it will not be
12461 // destroyed in the temp_dc dtor
12462 temp_dc.SelectObject(wxNullBitmap);
12463 // And for the scratch bitmap
12464 mscratch_dc.SelectObject(wxNullBitmap);
12465
12466 dc.DestroyClippingRegion();
12467
12468 PaintCleanup();
12469}
12470
12471void ChartCanvas::PaintCleanup() {
12472 // Handle the current graphic window, if present
12473 if (m_inPinch) return;
12474
12475 if (pCwin) {
12476 pCwin->Show();
12477 if (m_bTCupdate) {
12478 pCwin->Refresh();
12479 pCwin->Update();
12480 }
12481 }
12482
12483 // And set flags for next time
12484 m_bTCupdate = false;
12485
12486 // Handle deferred WarpPointer
12487 if (warp_flag) {
12488 WarpPointer(warp_x, warp_y);
12489 warp_flag = false;
12490 }
12491
12492 // Start movement timers, this runs nearly immediately.
12493 // the reason we cannot simply call it directly is the
12494 // refresh events it emits may be blocked from this paint event
12495 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12496 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12497}
12498
12499#if 0
12500wxColour GetErrorGraphicColor(double val)
12501{
12502 /*
12503 double valm = wxMin(val_max, val);
12504
12505 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12506 unsigned char red = (unsigned char)(255 * (valm/val_max));
12507
12508 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12509
12510 hv.saturation = 1.0;
12511 hv.value = 1.0;
12512
12513 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12514 return wxColour(rv.red, rv.green, rv.blue);
12515 */
12516
12517 // HTML colors taken from NOAA WW3 Web representation
12518 wxColour c;
12519 if((val > 0) && (val < 1)) c.Set("#002ad9");
12520 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12521 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12522 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12523 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12524 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12525 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12526 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12527 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12528 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12529 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12530 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12531 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12532 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12533 else if((val >= 30) && (val < 36)) c.Set("#870000");
12534 else if((val >= 36) && (val < 42)) c.Set("#690000");
12535 else if((val >= 42) && (val < 48)) c.Set("#550000");
12536 else if( val >= 48) c.Set("#410000");
12537
12538 return c;
12539}
12540
12541void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12542{
12543 wxImage gr_image(vp->pix_width, vp->pix_height);
12544 gr_image.InitAlpha();
12545
12546 double maxval = -10000;
12547 double minval = 10000;
12548
12549 double rlat, rlon;
12550 double glat, glon;
12551
12552 GetCanvasPixPoint(0, 0, rlat, rlon);
12553
12554 for(int i=1; i < vp->pix_height-1; i++)
12555 {
12556 for(int j=0; j < vp->pix_width; j++)
12557 {
12558 // Reference mercator value
12559// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12560
12561 // Georef value
12562 GetCanvasPixPoint(j, i, glat, glon);
12563
12564 maxval = wxMax(maxval, (glat - rlat));
12565 minval = wxMin(minval, (glat - rlat));
12566
12567 }
12568 rlat = glat;
12569 }
12570
12571 GetCanvasPixPoint(0, 0, rlat, rlon);
12572 for(int i=1; i < vp->pix_height-1; i++)
12573 {
12574 for(int j=0; j < vp->pix_width; j++)
12575 {
12576 // Reference mercator value
12577// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12578
12579 // Georef value
12580 GetCanvasPixPoint(j, i, glat, glon);
12581
12582 double f = ((glat - rlat)-minval)/(maxval - minval);
12583
12584 double dy = (f * 40);
12585
12586 wxColour c = GetErrorGraphicColor(dy);
12587 unsigned char r = c.Red();
12588 unsigned char g = c.Green();
12589 unsigned char b = c.Blue();
12590
12591 gr_image.SetRGB(j, i, r,g,b);
12592 if((glat - rlat )!= 0)
12593 gr_image.SetAlpha(j, i, 128);
12594 else
12595 gr_image.SetAlpha(j, i, 255);
12596
12597 }
12598 rlat = glat;
12599 }
12600
12601 // Create a Bitmap
12602 wxBitmap *pbm = new wxBitmap(gr_image);
12603 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12604 pbm->SetMask(gr_mask);
12605
12606 pmdc->DrawBitmap(*pbm, 0,0);
12607
12608 delete pbm;
12609
12610}
12611
12612#endif
12613
12614void ChartCanvas::CancelMouseRoute() {
12615 m_routeState = 0;
12616 m_pMouseRoute = NULL;
12617 m_bDrawingRoute = false;
12618}
12619
12620int ChartCanvas::GetNextContextMenuId() {
12621 return CanvasMenuHandler::GetNextContextMenuId();
12622}
12623
12624bool ChartCanvas::SetCursor(const wxCursor &c) {
12625#ifdef ocpnUSE_GL
12626 if (g_bopengl && m_glcc)
12627 return m_glcc->SetCursor(c);
12628 else
12629#endif
12630 return wxWindow::SetCursor(c);
12631}
12632
12633void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12634 if (g_bquiting) return;
12635 // Keep the mouse position members up to date
12636 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12637
12638 // Retrigger the route leg popup timer
12639 // This handles the case when the chart is moving in auto-follow mode,
12640 // but no user mouse input is made. The timer handler may Hide() the
12641 // popup if the chart moved enough n.b. We use slightly longer oneshot
12642 // value to allow this method's Refresh() to complete before potentially
12643 // getting another Refresh() in the popup timer handler.
12644 if (!m_RolloverPopupTimer.IsRunning() &&
12645 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12646 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12647 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12648 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12649
12650#ifdef ocpnUSE_GL
12651 if (m_glcc && g_bopengl) {
12652 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12653 // overlay objects.
12654 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12655
12656 m_glcc->Refresh(eraseBackground,
12657 NULL); // We always are going to render the entire screen
12658 // anyway, so make
12659 // sure that the window managers understand the invalid area
12660 // is actually the entire client area.
12661
12662 // We need to selectively Refresh some child windows, if they are visible.
12663 // Note that some children are refreshed elsewhere on timer ticks, so don't
12664 // need attention here.
12665
12666 // Thumbnail chart
12667 if (pthumbwin && pthumbwin->IsShown()) {
12668 pthumbwin->Raise();
12669 pthumbwin->Refresh(false);
12670 }
12671
12672 // ChartInfo window
12673 if (m_pCIWin && m_pCIWin->IsShown()) {
12674 m_pCIWin->Raise();
12675 m_pCIWin->Refresh(false);
12676 }
12677
12678 // if(g_MainToolbar)
12679 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12680
12681 } else
12682#endif
12683 wxWindow::Refresh(eraseBackground, rect);
12684}
12685
12686void ChartCanvas::Update() {
12687 if (m_glcc && g_bopengl) {
12688#ifdef ocpnUSE_GL
12689 m_glcc->Update();
12690#endif
12691 } else
12692 wxWindow::Update();
12693}
12694
12695void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12696 if (!pemboss) return;
12697 int x = pemboss->x, y = pemboss->y;
12698 const double factor = 200;
12699
12700 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12701 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12702 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12703
12704 // Grab a snipped image out of the chart
12705 wxMemoryDC snip_dc;
12706 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12707 snip_dc.SelectObject(snip_bmp);
12708
12709 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12710 snip_dc.SelectObject(wxNullBitmap);
12711
12712 wxImage snip_img = snip_bmp.ConvertToImage();
12713
12714 // Apply Emboss map to the snip image
12715 unsigned char *pdata = snip_img.GetData();
12716 if (pdata) {
12717 for (int y = 0; y < pemboss->height; y++) {
12718 int map_index = (y * pemboss->width);
12719 for (int x = 0; x < pemboss->width; x++) {
12720 double val = (pemboss->pmap[map_index] * factor) / 256.;
12721
12722 int nred = (int)((*pdata) + val);
12723 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12724 *pdata++ = (unsigned char)nred;
12725
12726 int ngreen = (int)((*pdata) + val);
12727 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12728 *pdata++ = (unsigned char)ngreen;
12729
12730 int nblue = (int)((*pdata) + val);
12731 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12732 *pdata++ = (unsigned char)nblue;
12733
12734 map_index++;
12735 }
12736 }
12737 }
12738
12739 // Convert embossed snip to a bitmap
12740 wxBitmap emb_bmp(snip_img);
12741
12742 // Map to another memoryDC
12743 wxMemoryDC result_dc;
12744 result_dc.SelectObject(emb_bmp);
12745
12746 // Blit to target
12747 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12748
12749 result_dc.SelectObject(wxNullBitmap);
12750}
12751
12752emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12753 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12754
12755 if (GetQuiltMode()) {
12756 // disable Overzoom indicator for MBTiles
12757 int refIndex = GetQuiltRefChartdbIndex();
12758 if (refIndex >= 0) {
12759 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12760 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12761 if (current_type == CHART_TYPE_MBTILES) {
12762 ChartBase *pChart = m_pQuilt->GetRefChart();
12763 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12764 if (ptc) {
12765 zoom_factor = ptc->GetZoomFactor();
12766 }
12767 }
12768 }
12769
12770 if (zoom_factor <= 3.9) return NULL;
12771 } else {
12772 if (m_singleChart) {
12773 if (zoom_factor <= 3.9) return NULL;
12774 } else
12775 return NULL;
12776 }
12777
12778 if (m_pEM_OverZoom) {
12779 m_pEM_OverZoom->x = 4;
12780 m_pEM_OverZoom->y = 0;
12781 if (g_MainToolbar && IsPrimaryCanvas()) {
12782 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12783 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12784 }
12785 }
12786 return m_pEM_OverZoom;
12787}
12788
12789void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12790 GridDraw(dc);
12791
12792 // bool pluginOverlayRender = true;
12793 //
12794 // if(g_canvasConfig > 0){ // Multi canvas
12795 // if(IsPrimaryCanvas())
12796 // pluginOverlayRender = false;
12797 // }
12798
12799 g_overlayCanvas = this;
12800
12801 if (g_pi_manager) {
12802 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12803 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12805 }
12806
12807 AISDrawAreaNotices(dc, GetVP(), this);
12808
12809 wxDC *pdc = dc.GetDC();
12810 if (pdc) {
12811 pdc->DestroyClippingRegion();
12812 wxDCClipper(*pdc, ru);
12813 }
12814
12815 if (m_bShowNavobjects) {
12816 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12817 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12818 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12819 DrawAnchorWatchPoints(dc);
12820 } else {
12821 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12822 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12823 }
12824
12825 AISDraw(dc, GetVP(), this);
12826 ShipDraw(dc);
12827 AlertDraw(dc);
12828
12829 RenderVisibleSectorLights(dc);
12830
12831 RenderAllChartOutlines(dc, GetVP());
12832 RenderRouteLegs(dc);
12833 RenderShipToActive(dc, false);
12834 ScaleBarDraw(dc);
12835 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12836 if (g_pi_manager) {
12837 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12839 }
12840
12841 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12842 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12843
12844 if (g_pi_manager) {
12845 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12847 }
12848
12849 if (m_bShowTide) {
12850 RebuildTideSelectList(GetVP().GetBBox());
12851 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12852 }
12853
12854 if (m_bShowCurrent) {
12855 RebuildCurrentSelectList(GetVP().GetBBox());
12856 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12857 }
12858
12859 if (!g_PrintingInProgress) {
12860 if (IsPrimaryCanvas()) {
12861 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12862 }
12863
12864 if (IsPrimaryCanvas()) {
12865 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12866 }
12867
12868 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12869
12870 if (m_pTrackRolloverWin) {
12871 m_pTrackRolloverWin->Draw(dc);
12872 m_brepaint_piano = true;
12873 }
12874
12875 if (m_pRouteRolloverWin) {
12876 m_pRouteRolloverWin->Draw(dc);
12877 m_brepaint_piano = true;
12878 }
12879
12880 if (m_pAISRolloverWin) {
12881 m_pAISRolloverWin->Draw(dc);
12882 m_brepaint_piano = true;
12883 }
12884 if (m_brepaint_piano && g_bShowChartBar) {
12885 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12886 }
12887
12888 if (m_Compass) m_Compass->Paint(dc);
12889
12890 if (!g_CanvasHideNotificationIcon) {
12891 if (IsPrimaryCanvas()) {
12892 auto &noteman = NotificationManager::GetInstance();
12893 if (noteman.GetNotificationCount()) {
12894 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12895 if (m_notification_button->UpdateStatus()) Refresh();
12896 m_notification_button->Show(true);
12897 m_notification_button->Paint(dc);
12898 } else {
12899 m_notification_button->Show(false);
12900 }
12901 }
12902 }
12903 }
12904 if (g_pi_manager) {
12905 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12907 }
12908}
12909
12910emboss_data *ChartCanvas::EmbossDepthScale() {
12911 if (!m_bShowDepthUnits) return NULL;
12912
12913 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12914
12915 if (GetQuiltMode()) {
12916 wxString s = m_pQuilt->GetQuiltDepthUnit();
12917 s.MakeUpper();
12918 if (s == "FEET")
12919 depth_unit_type = DEPTH_UNIT_FEET;
12920 else if (s.StartsWith("FATHOMS"))
12921 depth_unit_type = DEPTH_UNIT_FATHOMS;
12922 else if (s.StartsWith("METERS"))
12923 depth_unit_type = DEPTH_UNIT_METERS;
12924 else if (s.StartsWith("METRES"))
12925 depth_unit_type = DEPTH_UNIT_METERS;
12926 else if (s.StartsWith("METRIC"))
12927 depth_unit_type = DEPTH_UNIT_METERS;
12928 else if (s.StartsWith("METER"))
12929 depth_unit_type = DEPTH_UNIT_METERS;
12930
12931 } else {
12932 if (m_singleChart) {
12933 depth_unit_type = m_singleChart->GetDepthUnitType();
12934 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12935 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12936 }
12937 }
12938
12939 emboss_data *ped = NULL;
12940 switch (depth_unit_type) {
12941 case DEPTH_UNIT_FEET:
12942 ped = m_pEM_Feet;
12943 break;
12944 case DEPTH_UNIT_METERS:
12945 ped = m_pEM_Meters;
12946 break;
12947 case DEPTH_UNIT_FATHOMS:
12948 ped = m_pEM_Fathoms;
12949 break;
12950 default:
12951 return NULL;
12952 }
12953
12954 ped->x = (GetVP().pix_width - ped->width);
12955
12956 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12957 wxRect r = m_Compass->GetRect();
12958 ped->y = r.y + r.height;
12959 } else {
12960 ped->y = 40;
12961 }
12962 return ped;
12963}
12964
12965void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12966 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12967 wxFont font;
12968 if (style->embossFont == wxEmptyString) {
12969 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12970 font = *dFont;
12971 font.SetPointSize(60);
12972 font.SetWeight(wxFONTWEIGHT_BOLD);
12973 } else
12974 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12975 wxFONTWEIGHT_BOLD, false, style->embossFont);
12976
12977 int emboss_width = 500;
12978 int emboss_height = 200;
12979
12980 // Free any existing emboss maps
12981 delete m_pEM_Feet;
12982 delete m_pEM_Meters;
12983 delete m_pEM_Fathoms;
12984
12985 // Create the 3 DepthUnit emboss map structures
12986 m_pEM_Feet =
12987 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12988 m_pEM_Meters =
12989 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12990 m_pEM_Fathoms =
12991 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12992}
12993
12994#define OVERZOOM_TEXT _("OverZoom")
12995
12996void ChartCanvas::SetOverzoomFont() {
12997 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12998 int w, h;
12999
13000 wxFont font;
13001 if (style->embossFont == wxEmptyString) {
13002 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13003 font = *dFont;
13004 font.SetPointSize(40);
13005 font.SetWeight(wxFONTWEIGHT_BOLD);
13006 } else
13007 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13008 wxFONTWEIGHT_BOLD, false, style->embossFont);
13009
13010 wxClientDC dc(this);
13011 dc.SetFont(font);
13012 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13013
13014 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13015 font.SetPointSize(font.GetPointSize() - 1);
13016 dc.SetFont(font);
13017 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13018 }
13019 m_overzoomFont = font;
13020 m_overzoomTextWidth = w;
13021 m_overzoomTextHeight = h;
13022}
13023
13024void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13025 delete m_pEM_OverZoom;
13026
13027 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13028 m_pEM_OverZoom =
13029 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13030 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13031}
13032
13033emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13034 int height, const wxString &str,
13035 ColorScheme cs) {
13036 int *pmap;
13037
13038 // Create a temporary bitmap
13039 wxBitmap bmp(width, height, -1);
13040
13041 // Create a memory DC
13042 wxMemoryDC temp_dc;
13043 temp_dc.SelectObject(bmp);
13044
13045 // Paint on it
13046 temp_dc.SetBackground(*wxWHITE_BRUSH);
13047 temp_dc.SetTextBackground(*wxWHITE);
13048 temp_dc.SetTextForeground(*wxBLACK);
13049
13050 temp_dc.Clear();
13051
13052 temp_dc.SetFont(font);
13053
13054 int str_w, str_h;
13055 temp_dc.GetTextExtent(str, &str_w, &str_h);
13056 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13057 temp_dc.DrawText(str, 1, 1);
13058
13059 // Deselect the bitmap
13060 temp_dc.SelectObject(wxNullBitmap);
13061
13062 // Convert bitmap the wxImage for manipulation
13063 wxImage img = bmp.ConvertToImage();
13064
13065 int image_width = str_w * 105 / 100;
13066 int image_height = str_h * 105 / 100;
13067 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13068 wxMin(image_height, img.GetHeight()));
13069 wxImage imgs = img.GetSubImage(r);
13070
13071 double val_factor;
13072 switch (cs) {
13073 case GLOBAL_COLOR_SCHEME_DAY:
13074 default:
13075 val_factor = 1;
13076 break;
13077 case GLOBAL_COLOR_SCHEME_DUSK:
13078 val_factor = .5;
13079 break;
13080 case GLOBAL_COLOR_SCHEME_NIGHT:
13081 val_factor = .25;
13082 break;
13083 }
13084
13085 int val;
13086 int index;
13087 const int w = imgs.GetWidth();
13088 const int h = imgs.GetHeight();
13089 pmap = (int *)calloc(w * h * sizeof(int), 1);
13090 // Create emboss map by differentiating the emboss image
13091 // and storing integer results in pmap
13092 // n.b. since the image is B/W, it is sufficient to check
13093 // one channel (i.e. red) only
13094 for (int y = 1; y < h - 1; y++) {
13095 for (int x = 1; x < w - 1; x++) {
13096 val =
13097 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13098 val = (int)(val * val_factor);
13099 index = (y * w) + x;
13100 pmap[index] = val;
13101 }
13102 }
13103
13104 emboss_data *pret = new emboss_data;
13105 pret->pmap = pmap;
13106 pret->width = w;
13107 pret->height = h;
13108
13109 return pret;
13110}
13111
13112void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13113 Track *active_track = NULL;
13114 for (Track *pTrackDraw : g_TrackList) {
13115 if (g_pActiveTrack == pTrackDraw) {
13116 active_track = pTrackDraw;
13117 continue;
13118 }
13119
13120 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13121 }
13122
13123 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13124}
13125
13126void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13127 Track *active_track = NULL;
13128 for (Track *pTrackDraw : g_TrackList) {
13129 if (g_pActiveTrack == pTrackDraw) {
13130 active_track = pTrackDraw;
13131 break;
13132 }
13133 }
13134 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13135}
13136
13137void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13138 Route *active_route = NULL;
13139 for (Route *pRouteDraw : *pRouteList) {
13140 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13141 active_route = pRouteDraw;
13142 continue;
13143 }
13144
13145 // if(m_canvasIndex == 1)
13146 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13147 }
13148
13149 // Draw any active or selected route (or track) last, so that is is always on
13150 // top
13151 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13152}
13153
13154void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13155 Route *active_route = NULL;
13156
13157 for (Route *pRouteDraw : *pRouteList) {
13158 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13159 active_route = pRouteDraw;
13160 break;
13161 }
13162 }
13163 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13164}
13165
13166void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13167 if (!pWayPointMan) return;
13168
13169 auto node = pWayPointMan->GetWaypointList()->begin();
13170
13171 while (node != pWayPointMan->GetWaypointList()->end()) {
13172 RoutePoint *pWP = *node;
13173 if (pWP) {
13174 if (pWP->m_bIsInRoute) {
13175 ++node;
13176 continue;
13177 }
13178
13179 /* technically incorrect... waypoint has bounding box */
13180 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13181 RoutePointGui(*pWP).Draw(dc, this, NULL);
13182 else {
13183 // Are Range Rings enabled?
13184 if (pWP->GetShowWaypointRangeRings() &&
13185 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13186 double factor = 1.00;
13187 if (pWP->GetWaypointRangeRingsStepUnits() ==
13188 1) // convert kilometers to NMi
13189 factor = 1 / 1.852;
13190
13191 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13192 pWP->GetWaypointRangeRingsStep() / 60.;
13193 radius *= 2; // Fudge factor
13194
13195 LLBBox radar_box;
13196 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13197 pWP->m_lat + radius, pWP->m_lon + radius);
13198 if (!BltBBox.IntersectOut(radar_box)) {
13199 RoutePointGui(*pWP).Draw(dc, this, NULL);
13200 }
13201 }
13202 }
13203 }
13204
13205 ++node;
13206 }
13207}
13208
13209void ChartCanvas::DrawBlinkObjects() {
13210 // All RoutePoints
13211 wxRect update_rect;
13212
13213 if (!pWayPointMan) return;
13214
13215 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13216 if (pWP) {
13217 if (pWP->m_bBlink) {
13218 update_rect.Union(pWP->CurrentRect_in_DC);
13219 }
13220 }
13221 }
13222 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13223}
13224
13225void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13226 // draw anchor watch rings, if activated
13227
13229 wxPoint r1, r2;
13230 wxPoint lAnchorPoint1, lAnchorPoint2;
13231 double lpp1 = 0.0;
13232 double lpp2 = 0.0;
13233 if (pAnchorWatchPoint1) {
13234 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13236 &lAnchorPoint1);
13237 }
13238 if (pAnchorWatchPoint2) {
13239 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13241 &lAnchorPoint2);
13242 }
13243
13244 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13245 wxPen ppPenr(GetGlobalColor("URED"), 2);
13246
13247 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13248 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13249 dc.SetBrush(*ppBrush);
13250
13251 if (lpp1 > 0) {
13252 dc.SetPen(ppPeng);
13253 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13254 }
13255
13256 if (lpp2 > 0) {
13257 dc.SetPen(ppPeng);
13258 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13259 }
13260
13261 if (lpp1 < 0) {
13262 dc.SetPen(ppPenr);
13263 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13264 }
13265
13266 if (lpp2 < 0) {
13267 dc.SetPen(ppPenr);
13268 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13269 }
13270 }
13271}
13272
13273double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13274 double lpp = 0.;
13275 wxPoint r1;
13276 wxPoint lAnchorPoint;
13277 double d1 = 0.0;
13278 double dabs;
13279 double tlat1, tlon1;
13280
13281 if (pAnchorWatchPoint) {
13282 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13283 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13284 dabs = fabs(d1 / 1852.);
13285 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13286 &tlat1, &tlon1);
13287 GetCanvasPointPix(tlat1, tlon1, &r1);
13288 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13289 &lAnchorPoint);
13290 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13291 pow((double)(lAnchorPoint.y - r1.y), 2));
13292
13293 // This is an entry watch
13294 if (d1 < 0) lpp = -lpp;
13295 }
13296 return lpp;
13297}
13298
13299//------------------------------------------------------------------------------------------
13300// Tides Support
13301//------------------------------------------------------------------------------------------
13302void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13303 if (!ptcmgr) return;
13304
13305 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13306
13307 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13308 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13309 double lon = pIDX->IDX_lon;
13310 double lat = pIDX->IDX_lat;
13311
13312 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13313 if ((type == 't') || (type == 'T')) {
13314 if (BBox.Contains(lat, lon)) {
13315 // Manage the point selection list
13316 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13317 }
13318 }
13319 }
13320}
13321
13322void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13323 if (!ptcmgr) return;
13324
13325 wxDateTime this_now = gTimeSource;
13326 bool cur_time = !gTimeSource.IsValid();
13327 if (cur_time) this_now = wxDateTime::Now();
13328 time_t t_this_now = this_now.GetTicks();
13329
13330 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13331 wxPENSTYLE_SOLID);
13332 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13333 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13334 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13335 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13336
13337 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13338 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13339 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13340 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13341 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13342 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13343
13344 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13345 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13346 int font_size = wxMax(10, dFont->GetPointSize());
13347 font_size /= g_Platform->GetDisplayDIPMult(this);
13348 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13349 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13350 false, dFont->GetFaceName());
13351
13352 dc.SetPen(*pblack_pen);
13353 dc.SetBrush(*pgreen_brush);
13354
13355 wxBitmap bm;
13356 switch (m_cs) {
13357 case GLOBAL_COLOR_SCHEME_DAY:
13358 bm = m_bmTideDay;
13359 break;
13360 case GLOBAL_COLOR_SCHEME_DUSK:
13361 bm = m_bmTideDusk;
13362 break;
13363 case GLOBAL_COLOR_SCHEME_NIGHT:
13364 bm = m_bmTideNight;
13365 break;
13366 default:
13367 bm = m_bmTideDay;
13368 break;
13369 }
13370
13371 int bmw = bm.GetWidth();
13372 int bmh = bm.GetHeight();
13373
13374 float scale_factor = 1.0;
13375
13376 // Set the onscreen size of the symbol
13377 // Compensate for various display resolutions
13378 float icon_pixelRefDim = 45;
13379
13380 // Tidal report graphic is scaled by the text size of the label in use
13381 wxScreenDC sdc;
13382 int height;
13383 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13384 height *= g_Platform->GetDisplayDIPMult(this);
13385 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13386
13387 scale_factor *= pix_factor;
13388
13389 float user_scale_factor = g_ChartScaleFactorExp;
13390 if (g_ChartScaleFactorExp > 1.0)
13391 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13392 1.2; // soften the scale factor a bit
13393
13394 scale_factor *= user_scale_factor;
13395 scale_factor *= GetContentScaleFactor();
13396
13397 {
13398 double marge = 0.05;
13399 std::vector<LLBBox> drawn_boxes;
13400 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13401 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13402
13403 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13404 if ((type == 't') || (type == 'T')) // only Tides
13405 {
13406 double lon = pIDX->IDX_lon;
13407 double lat = pIDX->IDX_lat;
13408
13409 if (BBox.ContainsMarge(lat, lon, marge)) {
13410 // Avoid drawing detailed graphic for duplicate tide stations
13411 if (GetVP().chart_scale < 500000) {
13412 bool bdrawn = false;
13413 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13414 if (drawn_boxes[i].Contains(lat, lon)) {
13415 bdrawn = true;
13416 break;
13417 }
13418 }
13419 if (bdrawn) continue; // the station loop
13420
13421 LLBBox this_box;
13422 this_box.Set(lat, lon, lat, lon);
13423 this_box.EnLarge(.005);
13424 drawn_boxes.push_back(this_box);
13425 }
13426
13427 wxPoint r;
13428 GetCanvasPointPix(lat, lon, &r);
13429 // draw standard icons
13430 if (GetVP().chart_scale > 500000) {
13431 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13432 }
13433 // draw "extended" icons
13434 else {
13435 dc.SetFont(*plabelFont);
13436 {
13437 {
13438 float val, nowlev;
13439 float ltleve = 0.;
13440 float htleve = 0.;
13441 time_t tctime;
13442 time_t lttime = 0;
13443 time_t httime = 0;
13444 bool wt;
13445 // define if flood or ebb in the last ten minutes and verify if
13446 // data are useable
13447 if (ptcmgr->GetTideFlowSens(
13448 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13449 pIDX->IDX_rec_num, nowlev, val, wt)) {
13450 // search forward the first HW or LW near "now" ( starting at
13451 // "now" - ten minutes )
13452 ptcmgr->GetHightOrLowTide(
13453 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13454 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13455 wt, pIDX->IDX_rec_num, val, tctime);
13456 if (wt) {
13457 httime = tctime;
13458 htleve = val;
13459 } else {
13460 lttime = tctime;
13461 ltleve = val;
13462 }
13463 wt = !wt;
13464
13465 // then search opposite tide near "now"
13466 if (tctime > t_this_now) // search backward
13467 ptcmgr->GetHightOrLowTide(
13468 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13469 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13470 pIDX->IDX_rec_num, val, tctime);
13471 else
13472 // or search forward
13473 ptcmgr->GetHightOrLowTide(
13474 t_this_now, FORWARD_TEN_MINUTES_STEP,
13475 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13476 val, tctime);
13477 if (wt) {
13478 httime = tctime;
13479 htleve = val;
13480 } else {
13481 lttime = tctime;
13482 ltleve = val;
13483 }
13484
13485 // draw the tide rectangle:
13486
13487 // tide icon rectangle has default pre-scaled width = 12 ,
13488 // height = 45
13489 int width = (int)(12 * scale_factor + 0.5);
13490 int height = (int)(45 * scale_factor + 0.5);
13491 int linew = wxMax(1, (int)(scale_factor));
13492 int xDraw = r.x - (width / 2);
13493 int yDraw = r.y - (height / 2);
13494
13495 // process tide state ( %height and flow sens )
13496 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13497 int hs = (httime > lttime) ? -4 : 4;
13498 hs *= (int)(scale_factor + 0.5);
13499 if (ts > 0.995 || ts < 0.005) hs = 0;
13500 int ht_y = (int)(height * ts);
13501
13502 // draw yellow tide rectangle outlined in black
13503 pblack_pen->SetWidth(linew);
13504 dc.SetPen(*pblack_pen);
13505 dc.SetBrush(*pyelo_brush);
13506 dc.DrawRectangle(xDraw, yDraw, width, height);
13507
13508 // draw blue rectangle as water height, smaller in width than
13509 // yellow rectangle
13510 dc.SetPen(*pblue_pen);
13511 dc.SetBrush(*pblue_brush);
13512 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13513 (width - (4 * linew)), height - ht_y);
13514
13515 // draw sens arrows (ensure they are not "under-drawn" by top
13516 // line of blue rectangle )
13517 int hl;
13518 wxPoint arrow[3];
13519 arrow[0].x = xDraw + 2 * linew;
13520 arrow[1].x = xDraw + width / 2;
13521 arrow[2].x = xDraw + width - 2 * linew;
13522 pyelo_pen->SetWidth(linew);
13523 pblue_pen->SetWidth(linew);
13524 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13525 {
13526 hl = (int)(height * 0.25) + yDraw;
13527 arrow[0].y = hl;
13528 arrow[1].y = hl + hs;
13529 arrow[2].y = hl;
13530 if (ts < 0.15)
13531 dc.SetPen(*pyelo_pen);
13532 else
13533 dc.SetPen(*pblue_pen);
13534 dc.DrawLines(3, arrow);
13535 }
13536 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13537 {
13538 hl = (int)(height * 0.5) + yDraw;
13539 arrow[0].y = hl;
13540 arrow[1].y = hl + hs;
13541 arrow[2].y = hl;
13542 if (ts < 0.40)
13543 dc.SetPen(*pyelo_pen);
13544 else
13545 dc.SetPen(*pblue_pen);
13546 dc.DrawLines(3, arrow);
13547 }
13548 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13549 {
13550 hl = (int)(height * 0.75) + yDraw;
13551 arrow[0].y = hl;
13552 arrow[1].y = hl + hs;
13553 arrow[2].y = hl;
13554 if (ts < 0.65)
13555 dc.SetPen(*pyelo_pen);
13556 else
13557 dc.SetPen(*pblue_pen);
13558 dc.DrawLines(3, arrow);
13559 }
13560 // draw tide level text
13561 wxString s;
13562 s.Printf("%3.1f", nowlev);
13563 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13564 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13565 int wx1;
13566 dc.GetTextExtent(s, &wx1, NULL);
13567 wx1 *= g_Platform->GetDisplayDIPMult(this);
13568 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13569 }
13570 }
13571 }
13572 }
13573 }
13574 }
13575 }
13576 }
13577}
13578
13579//------------------------------------------------------------------------------------------
13580// Currents Support
13581//------------------------------------------------------------------------------------------
13582
13583void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13584 if (!ptcmgr) return;
13585
13586 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13587
13588 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13589 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13590 double lon = pIDX->IDX_lon;
13591 double lat = pIDX->IDX_lat;
13592
13593 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13594 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13595 if ((BBox.Contains(lat, lon))) {
13596 // Manage the point selection list
13597 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13598 }
13599 }
13600 }
13601}
13602
13603void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13604 if (!ptcmgr) return;
13605
13606 float tcvalue, dir;
13607 bool bnew_val;
13608 char sbuf[20];
13609 wxFont *pTCFont;
13610 double lon_last = 0.;
13611 double lat_last = 0.;
13612 // arrow size for Raz Blanchard : 12 knots north
13613 double marge = 0.2;
13614 bool cur_time = !gTimeSource.IsValid();
13615
13616 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13617 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13618
13619 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13620 wxPENSTYLE_SOLID);
13621 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13622 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13623 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13624 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13625 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13626 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13627 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13628 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13629
13630 double skew_angle = GetVPRotation();
13631
13632 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13633 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13634 int font_size = wxMax(10, dFont->GetPointSize());
13635 font_size /= g_Platform->GetDisplayDIPMult(this);
13636 pTCFont = FontMgr::Get().FindOrCreateFont(
13637 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13638 false, dFont->GetFaceName());
13639
13640 float scale_factor = 1.0;
13641
13642 // Set the onscreen size of the symbol
13643 // Current report graphic is scaled by the text size of the label in use
13644 wxScreenDC sdc;
13645 int height;
13646 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13647 height *= g_Platform->GetDisplayDIPMult(this);
13648 float nominal_icon_size_pixels = 15;
13649 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13650
13651 scale_factor *= pix_factor;
13652
13653 float user_scale_factor = g_ChartScaleFactorExp;
13654 if (g_ChartScaleFactorExp > 1.0)
13655 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13656 1.2; // soften the scale factor a bit
13657
13658 scale_factor *= user_scale_factor;
13659
13660 scale_factor *= GetContentScaleFactor();
13661
13662 {
13663 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13664 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13665 double lon = pIDX->IDX_lon;
13666 double lat = pIDX->IDX_lat;
13667
13668 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13669 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13670 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13671 wxPoint r;
13672 GetCanvasPointPix(lat, lon, &r);
13673
13674 wxPoint d[4]; // points of a diamond at the current station location
13675 int dd = (int)(5.0 * scale_factor + 0.5);
13676 d[0].x = r.x;
13677 d[0].y = r.y + dd;
13678 d[1].x = r.x + dd;
13679 d[1].y = r.y;
13680 d[2].x = r.x;
13681 d[2].y = r.y - dd;
13682 d[3].x = r.x - dd;
13683 d[3].y = r.y;
13684
13685 if (1) {
13686 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13687 dc.SetPen(*pblack_pen);
13688 dc.SetBrush(*porange_brush);
13689 dc.DrawPolygon(4, d);
13690
13691 if (type == 'C') {
13692 dc.SetBrush(*pblack_brush);
13693 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13694 }
13695
13696 if (GetVP().chart_scale < 1000000) {
13697 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13698 continue;
13699 } else
13700 continue;
13701
13702 if (1 /*type == 'c'*/) {
13703 {
13704 // Get the display pixel location of the current station
13705 int pixxc, pixyc;
13706 pixxc = r.x;
13707 pixyc = r.y;
13708
13709 // Adjust drawing size using logarithmic scale. tcvalue is
13710 // current in knots
13711 double a1 = fabs(tcvalue) * 10.;
13712 // Current values <= 0.1 knot will have no arrow
13713 a1 = wxMax(1.0, a1);
13714 double a2 = log10(a1);
13715
13716 float cscale = scale_factor * a2 * 0.3;
13717
13718 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13719 dc.SetPen(*porange_pen);
13720 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13721 cscale);
13722 // Draw text, if enabled
13723
13724 if (bDrawCurrentValues) {
13725 dc.SetFont(*pTCFont);
13726 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13727 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13728 }
13729 }
13730 } // scale
13731 }
13732 /* This is useful for debugging the TC database
13733 else
13734 {
13735 dc.SetPen ( *porange_pen );
13736 dc.SetBrush ( *pgray_brush );
13737 dc.DrawPolygon ( 4, d );
13738 }
13739 */
13740 }
13741 lon_last = lon;
13742 lat_last = lat;
13743 }
13744 }
13745 }
13746}
13747
13748void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13749 ShowSingleTideDialog(x, y, pvIDX);
13750}
13751
13752void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13753 if (!pvIDX) return; // Validate input
13754
13755 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13756
13757 // Check if a tide dialog is already open and visible
13758 if (pCwin && pCwin->IsShown()) {
13759 // Same tide station: bring existing dialog to front (preserves user
13760 // context)
13761 if (pCwin->GetCurrentIDX() == pNewIDX) {
13762 pCwin->Raise();
13763 pCwin->SetFocus();
13764
13765 // Provide subtle visual feedback that dialog is already open
13766 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13767 return;
13768 }
13769
13770 // Different tide station: close current dialog before opening new one
13771 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13772 }
13773
13774 if (pCwin) {
13775 // This shouldn't happen but ensures clean state
13776 pCwin->Destroy();
13777 pCwin = NULL;
13778 }
13779
13780 // Create and display new tide dialog
13781 pCwin = new TCWin(this, x, y, pvIDX);
13782
13783 // Ensure the dialog is properly shown and focused
13784 if (pCwin) {
13785 pCwin->Show();
13786 pCwin->Raise();
13787 pCwin->SetFocus();
13788 }
13789}
13790
13791bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13792
13794 if (pCwin) {
13795 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13796 }
13797}
13798
13799#define NUM_CURRENT_ARROW_POINTS 9
13800static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13801 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13802 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13803 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13804
13805void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13806 double scale) {
13807 if (scale > 1e-2) {
13808 float sin_rot = sin(rot_angle * PI / 180.);
13809 float cos_rot = cos(rot_angle * PI / 180.);
13810
13811 // Move to the first point
13812
13813 float xt = CurrentArrowArray[0].x;
13814 float yt = CurrentArrowArray[0].y;
13815
13816 float xp = (xt * cos_rot) - (yt * sin_rot);
13817 float yp = (xt * sin_rot) + (yt * cos_rot);
13818 int x1 = (int)(xp * scale);
13819 int y1 = (int)(yp * scale);
13820
13821 // Walk thru the point list
13822 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13823 xt = CurrentArrowArray[ip].x;
13824 yt = CurrentArrowArray[ip].y;
13825
13826 float xp = (xt * cos_rot) - (yt * sin_rot);
13827 float yp = (xt * sin_rot) + (yt * cos_rot);
13828 int x2 = (int)(xp * scale);
13829 int y2 = (int)(yp * scale);
13830
13831 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13832
13833 x1 = x2;
13834 y1 = y2;
13835 }
13836 }
13837}
13838
13839wxString ChartCanvas::FindValidUploadPort() {
13840 wxString port;
13841 // Try to use the saved persistent upload port first
13842 if (!g_uploadConnection.IsEmpty() &&
13843 g_uploadConnection.StartsWith("Serial")) {
13844 port = g_uploadConnection;
13845 }
13846
13847 else {
13848 // If there is no persistent upload port recorded (yet)
13849 // then use the first available serial connection which has output defined.
13850 for (auto *cp : TheConnectionParams()) {
13851 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13852 port << "Serial:" << cp->Port;
13853 }
13854 }
13855 return port;
13856}
13857
13858void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13859 if (!win) return;
13860
13861 if (NULL == g_pais_query_dialog_active) {
13862 int pos_x = g_ais_query_dialog_x;
13863 int pos_y = g_ais_query_dialog_y;
13864
13865 if (g_pais_query_dialog_active) {
13866 g_pais_query_dialog_active->Destroy();
13867 g_pais_query_dialog_active = new AISTargetQueryDialog();
13868 } else {
13869 g_pais_query_dialog_active = new AISTargetQueryDialog();
13870 }
13871
13872 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13873 wxPoint(pos_x, pos_y));
13874
13875 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13876 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13877 g_pais_query_dialog_active->SetMMSI(mmsi);
13878 g_pais_query_dialog_active->UpdateText();
13879 wxSize sz = g_pais_query_dialog_active->GetSize();
13880
13881 bool b_reset_pos = false;
13882#ifdef __WXMSW__
13883 // Support MultiMonitor setups which an allow negative window positions.
13884 // If the requested window title bar does not intersect any installed
13885 // monitor, then default to simple primary monitor positioning.
13886 RECT frame_title_rect;
13887 frame_title_rect.left = pos_x;
13888 frame_title_rect.top = pos_y;
13889 frame_title_rect.right = pos_x + sz.x;
13890 frame_title_rect.bottom = pos_y + 30;
13891
13892 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13893 b_reset_pos = true;
13894#else
13895
13896 // Make sure drag bar (title bar) of window intersects wxClient Area of
13897 // screen, with a little slop...
13898 wxRect window_title_rect; // conservative estimate
13899 window_title_rect.x = pos_x;
13900 window_title_rect.y = pos_y;
13901 window_title_rect.width = sz.x;
13902 window_title_rect.height = 30;
13903
13904 wxRect ClientRect = wxGetClientDisplayRect();
13905 ClientRect.Deflate(
13906 60, 60); // Prevent the new window from being too close to the edge
13907 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13908
13909#endif
13910
13911 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13912
13913 } else {
13914 g_pais_query_dialog_active->SetMMSI(mmsi);
13915 g_pais_query_dialog_active->UpdateText();
13916 }
13917
13918 g_pais_query_dialog_active->Show();
13919}
13920
13921void ChartCanvas::ToggleCanvasQuiltMode() {
13922 bool cur_mode = GetQuiltMode();
13923
13924 if (!GetQuiltMode())
13925 SetQuiltMode(true);
13926 else if (GetQuiltMode()) {
13927 SetQuiltMode(false);
13928 g_sticky_chart = GetQuiltReferenceChartIndex();
13929 }
13930
13931 if (cur_mode != GetQuiltMode()) {
13932 SetupCanvasQuiltMode();
13933 DoCanvasUpdate();
13934 InvalidateGL();
13935 Refresh();
13936 }
13937 // TODO What to do about this?
13938 // g_bQuiltEnable = GetQuiltMode();
13939
13940 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13941 if (ps52plib) ps52plib->GenerateStateHash();
13942
13943 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13944 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13945}
13946
13947void ChartCanvas::DoCanvasStackDelta(int direction) {
13948 if (!GetQuiltMode()) {
13949 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13950 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13951 if ((current_stack_index + direction) < 0) return;
13952
13953 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13954 int new_dbIndex =
13955 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13956
13957 if (IsChartQuiltableRef(new_dbIndex)) {
13958 ToggleCanvasQuiltMode();
13959 SelectQuiltRefdbChart(new_dbIndex);
13960 m_bpersistent_quilt = false;
13961 }
13962 } else {
13963 SelectChartFromStack(current_stack_index + direction);
13964 }
13965 } else {
13966 std::vector<int> piano_chart_index_array =
13967 GetQuiltExtendedStackdbIndexArray();
13968 int refdb = GetQuiltRefChartdbIndex();
13969
13970 // Find the ref chart in the stack
13971 int current_index = -1;
13972 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13973 if (refdb == piano_chart_index_array[i]) {
13974 current_index = i;
13975 break;
13976 }
13977 }
13978 if (current_index == -1) return;
13979
13980 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13981 int target_family = ctet.GetChartFamily();
13982
13983 int new_index = -1;
13984 int check_index = current_index + direction;
13985 bool found = false;
13986 int check_dbIndex = -1;
13987 int new_dbIndex = -1;
13988
13989 // When quilted. switch within the same chart family
13990 while (!found &&
13991 (unsigned int)check_index < piano_chart_index_array.size() &&
13992 (check_index >= 0)) {
13993 check_dbIndex = piano_chart_index_array[check_index];
13994 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13995 if (target_family == cte.GetChartFamily()) {
13996 found = true;
13997 new_index = check_index;
13998 new_dbIndex = check_dbIndex;
13999 break;
14000 }
14001
14002 check_index += direction;
14003 }
14004
14005 if (!found) return;
14006
14007 if (!IsChartQuiltableRef(new_dbIndex)) {
14008 ToggleCanvasQuiltMode();
14009 SelectdbChart(new_dbIndex);
14010 m_bpersistent_quilt = true;
14011 } else {
14012 SelectQuiltRefChart(new_index);
14013 }
14014 }
14015
14016 // update the state of the menu items (checkmarks etc)
14017 top_frame::Get()->UpdateGlobalMenuItems();
14018 SetQuiltChartHiLiteIndex(-1);
14019
14020 ReloadVP();
14021}
14022
14023//--------------------------------------------------------------------------------------------------------
14024//
14025// Toolbar support
14026//
14027//--------------------------------------------------------------------------------------------------------
14028
14029void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14030 // Handle the per-canvas toolbar clicks here
14031
14032 switch (event.GetId()) {
14033 case ID_ZOOMIN: {
14034 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14035 break;
14036 }
14037
14038 case ID_ZOOMOUT: {
14039 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14040 break;
14041 }
14042
14043 case ID_STKUP:
14044 DoCanvasStackDelta(1);
14045 DoCanvasUpdate();
14046 break;
14047
14048 case ID_STKDN:
14049 DoCanvasStackDelta(-1);
14050 DoCanvasUpdate();
14051 break;
14052
14053 case ID_FOLLOW: {
14054 TogglebFollow();
14055 break;
14056 }
14057
14058 case ID_CURRENT: {
14059 ShowCurrents(!GetbShowCurrent());
14060 ReloadVP();
14061 Refresh(false);
14062 break;
14063 }
14064
14065 case ID_TIDE: {
14066 ShowTides(!GetbShowTide());
14067 ReloadVP();
14068 Refresh(false);
14069 break;
14070 }
14071
14072 case ID_ROUTE: {
14073 if (0 == m_routeState) {
14074 StartRoute();
14075 } else {
14076 FinishRoute();
14077 }
14078
14079#ifdef __ANDROID__
14080 androidSetRouteAnnunciator(m_routeState == 1);
14081#endif
14082 break;
14083 }
14084
14085 case ID_AIS: {
14086 SetAISCanvasDisplayStyle(-1);
14087 break;
14088 }
14089
14090 default:
14091 break;
14092 }
14093
14094 // And then let gFrame handle the rest....
14095 event.Skip();
14096}
14097
14098void ChartCanvas::SetShowAIS(bool show) {
14099 m_bShowAIS = show;
14100 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14101 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14102}
14103
14104void ChartCanvas::SetAttenAIS(bool show) {
14105 m_bShowAISScaled = show;
14106 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14107 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14108}
14109
14110void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14111 // make some arrays to hold the dfferences between cycle steps
14112 // show all, scaled, hide all
14113 bool bShowAIS_Array[3] = {true, true, false};
14114 bool bShowScaled_Array[3] = {false, true, true};
14115 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14116 _("Attenuate less critical AIS targets"),
14117 _("Hide AIS Targets")};
14118 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14119 int ArraySize = 3;
14120 int AIS_Toolbar_Switch = 0;
14121 if (StyleIndx == -1) { // -1 means coming from toolbar button
14122 // find current state of switch
14123 for (int i = 1; i < ArraySize; i++) {
14124 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14125 (bShowScaled_Array[i] == m_bShowAISScaled))
14126 AIS_Toolbar_Switch = i;
14127 }
14128 AIS_Toolbar_Switch++; // we did click so continu with next item
14129 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14130 AIS_Toolbar_Switch++;
14131
14132 } else { // coming from menu bar.
14133 AIS_Toolbar_Switch = StyleIndx;
14134 }
14135 // make sure we are not above array
14136 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14137
14138 int AIS_Toolbar_Switch_Next =
14139 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14140 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14141 AIS_Toolbar_Switch_Next++;
14142 if (AIS_Toolbar_Switch_Next >= ArraySize)
14143 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14144
14145 // Set found values to global and member variables
14146 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14147 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14148}
14149
14150void ChartCanvas::TouchAISToolActive() {}
14151
14152void ChartCanvas::UpdateAISTBTool() {}
14153
14154//---------------------------------------------------------------------------------
14155//
14156// Compass/GPS status icon support
14157//
14158//---------------------------------------------------------------------------------
14159
14160void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14161 // Look for change in overlap or positions
14162 bool b_update = false;
14163 int cc1_edge_comp = 2;
14164 wxRect rect = m_Compass->GetRect();
14165 wxSize parent_size = GetSize();
14166
14167 parent_size *= m_displayScale;
14168
14169 // check to see if it would overlap if it was in its home position (upper
14170 // right)
14171 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14172 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14173 wxRect compass_rect(compass_pt, rect.GetSize());
14174
14175 m_Compass->Move(compass_pt);
14176
14177 if (m_Compass && m_Compass->IsShown())
14178 m_Compass->UpdateStatus(b_force_new | b_update);
14179
14180 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14181 scaler = wxMax(scaler, 1.0);
14182 wxPoint note_point = wxPoint(
14183 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14184 if (m_notification_button) {
14185 m_notification_button->Move(note_point);
14186 m_notification_button->UpdateStatus();
14187 }
14188
14189 if (b_force_new | b_update) Refresh();
14190}
14191
14192void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14193 ChartTypeEnum New_Type,
14194 ChartFamilyEnum New_Family) {
14195 if (!GetpCurrentStack()) return;
14196 if (!ChartData) return;
14197
14198 if (index < GetpCurrentStack()->nEntry) {
14199 // Open the new chart
14200 ChartBase *pTentative_Chart;
14201 pTentative_Chart = ChartData->OpenStackChartConditional(
14202 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14203
14204 if (pTentative_Chart) {
14205 if (m_singleChart) m_singleChart->Deactivate();
14206
14207 m_singleChart = pTentative_Chart;
14208 m_singleChart->Activate();
14209
14210 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14211 GetpCurrentStack(), m_singleChart->GetFullPath());
14212 }
14213
14214 // Setup the view
14215 double zLat, zLon;
14216 if (m_bFollow) {
14217 zLat = gLat;
14218 zLon = gLon;
14219 } else {
14220 zLat = m_vLat;
14221 zLon = m_vLon;
14222 }
14223
14224 double best_scale_ppm = GetBestVPScale(m_singleChart);
14225 double rotation = GetVPRotation();
14226 double oldskew = GetVPSkew();
14227 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14228
14229 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14230 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14231 if (fabs(newskew) > 0.0001) rotation = newskew;
14232 }
14233
14234 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14235
14236 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14237 }
14238
14239 // refresh Piano
14240 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14241 if (idx < 0) return;
14242
14243 std::vector<int> piano_active_chart_index_array;
14244 piano_active_chart_index_array.push_back(
14245 GetpCurrentStack()->GetCurrentEntrydbIndex());
14246 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14247}
14248
14249void ChartCanvas::SelectdbChart(int dbindex) {
14250 if (!GetpCurrentStack()) return;
14251 if (!ChartData) return;
14252
14253 if (dbindex >= 0) {
14254 // Open the new chart
14255 ChartBase *pTentative_Chart;
14256 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14257
14258 if (pTentative_Chart) {
14259 if (m_singleChart) m_singleChart->Deactivate();
14260
14261 m_singleChart = pTentative_Chart;
14262 m_singleChart->Activate();
14263
14264 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14265 GetpCurrentStack(), m_singleChart->GetFullPath());
14266 }
14267
14268 // Setup the view
14269 double zLat, zLon;
14270 if (m_bFollow) {
14271 zLat = gLat;
14272 zLon = gLon;
14273 } else {
14274 zLat = m_vLat;
14275 zLon = m_vLon;
14276 }
14277
14278 double best_scale_ppm = GetBestVPScale(m_singleChart);
14279
14280 if (m_singleChart)
14281 SetViewPoint(zLat, zLon, best_scale_ppm,
14282 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14283
14284 // SetChartUpdatePeriod( );
14285
14286 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14287 }
14288
14289 // TODO refresh_Piano();
14290}
14291
14292void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14293 double target_scale = GetVP().view_scale_ppm;
14294
14295 if (!GetQuiltMode()) {
14296 if (GetpCurrentStack()) {
14297 int stack_index = -1;
14298 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14299 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14300 if (check_dbIndex < 0) continue;
14301 const ChartTableEntry &cte =
14302 ChartData->GetChartTableEntry(check_dbIndex);
14303 if (type == cte.GetChartType()) {
14304 stack_index = i;
14305 break;
14306 } else if (family == cte.GetChartFamily()) {
14307 stack_index = i;
14308 break;
14309 }
14310 }
14311
14312 if (stack_index >= 0) {
14313 SelectChartFromStack(stack_index);
14314 }
14315 }
14316 } else {
14317 int sel_dbIndex = -1;
14318 std::vector<int> piano_chart_index_array =
14319 GetQuiltExtendedStackdbIndexArray();
14320 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14321 int check_dbIndex = piano_chart_index_array[i];
14322 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14323 if (type == cte.GetChartType()) {
14324 if (IsChartQuiltableRef(check_dbIndex)) {
14325 sel_dbIndex = check_dbIndex;
14326 break;
14327 }
14328 } else if (family == cte.GetChartFamily()) {
14329 if (IsChartQuiltableRef(check_dbIndex)) {
14330 sel_dbIndex = check_dbIndex;
14331 break;
14332 }
14333 }
14334 }
14335
14336 if (sel_dbIndex >= 0) {
14337 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14338 // Re-qualify the quilt reference chart selection
14339 AdjustQuiltRefChart();
14340 }
14341
14342 // Now reset the scale to the target...
14343 SetVPScale(target_scale);
14344 }
14345
14346 SetQuiltChartHiLiteIndex(-1);
14347
14348 ReloadVP();
14349}
14350
14351bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14352 return std::find(m_tile_yesshow_index_array.begin(),
14353 m_tile_yesshow_index_array.end(),
14354 index) != m_tile_yesshow_index_array.end();
14355}
14356
14357bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14358 return std::find(m_tile_noshow_index_array.begin(),
14359 m_tile_noshow_index_array.end(),
14360 index) != m_tile_noshow_index_array.end();
14361}
14362
14363void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14364 if (std::find(m_tile_noshow_index_array.begin(),
14365 m_tile_noshow_index_array.end(),
14366 index) == m_tile_noshow_index_array.end()) {
14367 m_tile_noshow_index_array.push_back(index);
14368 }
14369}
14370
14371//-------------------------------------------------------------------------------------------------------
14372//
14373// Piano support
14374//
14375//-------------------------------------------------------------------------------------------------------
14376
14377void ChartCanvas::HandlePianoClick(
14378 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14379 if (g_options && g_options->IsShown())
14380 return; // Piano might be invalid due to chartset updates.
14381 if (!m_pCurrentStack) return;
14382 if (!ChartData) return;
14383
14384 // stop movement or on slow computer we may get something like :
14385 // zoom out with the wheel (timer is set)
14386 // quickly click and display a chart, which may zoom in
14387 // but the delayed timer fires first and it zooms out again!
14388 StopMovement();
14389
14390 // When switching by piano key click, we may appoint the new target chart to
14391 // be any chart in the composite array.
14392 // As an improvement to UX, find the chart that is "closest" to the current
14393 // vp,
14394 // and select that chart. This will cause a jump to the centroid of that
14395 // chart
14396
14397 double distance = 25000; // RTW
14398 int closest_index = -1;
14399 for (int chart_index : selected_dbIndex_array) {
14400 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14401 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14402 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14403
14404 // measure distance as Manhattan style
14405 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14406 if (test_distance < distance) {
14407 distance = test_distance;
14408 closest_index = chart_index;
14409 }
14410 }
14411
14412 int selected_dbIndex = selected_dbIndex_array[0];
14413 if (closest_index >= 0) selected_dbIndex = closest_index;
14414
14415 if (!GetQuiltMode()) {
14416 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14417 if (IsChartQuiltableRef(selected_dbIndex)) {
14418 ToggleCanvasQuiltMode();
14419 SelectQuiltRefdbChart(selected_dbIndex);
14420 m_bpersistent_quilt = false;
14421 } else {
14422 SelectChartFromStack(selected_index);
14423 }
14424 } else {
14425 SelectChartFromStack(selected_index);
14426 g_sticky_chart = selected_dbIndex;
14427 }
14428
14429 if (m_singleChart)
14430 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14431 } else {
14432 // Handle MBTiles overlays first
14433 // Left click simply toggles the noshow array index entry
14434 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14435 bool bfound = false;
14436 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14437 if (m_tile_noshow_index_array[i] ==
14438 selected_dbIndex) { // chart is in the noshow list
14439 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14440 i); // erase it
14441 bfound = true;
14442 break;
14443 }
14444 }
14445 if (!bfound) {
14446 m_tile_noshow_index_array.push_back(selected_dbIndex);
14447 }
14448
14449 // If not already present, add this tileset to the "yes_show" array.
14450 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14451 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14452 }
14453
14454 else {
14455 if (IsChartQuiltableRef(selected_dbIndex)) {
14456 // if( ChartData ) ChartData->PurgeCache();
14457
14458 // If the chart is a vector chart, and of very large scale,
14459 // then we had better set the new scale directly to avoid excessive
14460 // underzoom on, eg, Inland ENCs
14461 bool set_scale = false;
14462 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14463 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14464 set_scale = true;
14465 }
14466 }
14467
14468 if (!set_scale) {
14469 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14470 } else {
14471 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14472
14473 // Adjust scale so that the selected chart is underzoomed/overzoomed
14474 // by a controlled amount
14475 ChartBase *pc =
14476 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14477 if (pc) {
14478 double proposed_scale_onscreen =
14480
14481 if (g_bPreserveScaleOnX) {
14482 proposed_scale_onscreen =
14483 wxMin(proposed_scale_onscreen,
14484 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14485 GetCanvasWidth()));
14486 } else {
14487 proposed_scale_onscreen =
14488 wxMin(proposed_scale_onscreen,
14489 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14490 GetCanvasWidth()));
14491
14492 proposed_scale_onscreen =
14493 wxMax(proposed_scale_onscreen,
14494 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14496 }
14497
14498 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14499 }
14500 }
14501 } else {
14502 ToggleCanvasQuiltMode();
14503 SelectdbChart(selected_dbIndex);
14504 m_bpersistent_quilt = true;
14505 }
14506 }
14507 }
14508
14509 SetQuiltChartHiLiteIndex(-1);
14510 // update the state of the menu items (checkmarks etc)
14511 top_frame::Get()->UpdateGlobalMenuItems();
14512 HideChartInfoWindow();
14513 DoCanvasUpdate();
14514 ReloadVP(); // Pick up the new selections
14515}
14516
14517void ChartCanvas::HandlePianoRClick(
14518 int x, int y, int selected_index,
14519 const std::vector<int> &selected_dbIndex_array) {
14520 if (g_options && g_options->IsShown())
14521 return; // Piano might be invalid due to chartset updates.
14522 if (!GetpCurrentStack()) return;
14523
14524 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14525 UpdateCanvasControlBar();
14526
14527 SetQuiltChartHiLiteIndex(-1);
14528}
14529
14530void ChartCanvas::HandlePianoRollover(
14531 int selected_index, const std::vector<int> &selected_dbIndex_array,
14532 int n_charts, int scale) {
14533 if (g_options && g_options->IsShown())
14534 return; // Piano might be invalid due to chartset updates.
14535 if (!GetpCurrentStack()) return;
14536 if (!ChartData) return;
14537
14538 if (ChartData->IsBusy()) return;
14539
14540 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14541
14542 if (!GetQuiltMode()) {
14543 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14544 } else {
14545 // Select the correct vector
14546 std::vector<int> piano_chart_index_array;
14547 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14548 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14549 if ((GetpCurrentStack()->nEntry > 1) ||
14550 (piano_chart_index_array.size() >= 1)) {
14551 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14552
14553 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14554 ReloadVP(false); // no VP adjustment allowed
14555 } else if (GetpCurrentStack()->nEntry == 1) {
14556 const ChartTableEntry &cte =
14557 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14558 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14559 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14560 ReloadVP(false);
14561 } else if ((-1 == selected_index) &&
14562 (0 == selected_dbIndex_array.size())) {
14563 ShowChartInfoWindow(key_location.x, -1);
14564 }
14565 }
14566 } else {
14567 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14568
14569 if ((GetpCurrentStack()->nEntry > 1) ||
14570 (piano_chart_index_array.size() >= 1)) {
14571 if (n_charts > 1)
14572 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14573 selected_dbIndex_array);
14574 else if (n_charts == 1)
14575 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14576
14577 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14578 ReloadVP(false); // no VP adjustment allowed
14579 }
14580 }
14581 }
14582}
14583
14584void ChartCanvas::ClearPianoRollover() {
14585 ClearQuiltChartHiLiteIndexArray();
14586 ShowChartInfoWindow(0, -1);
14587 std::vector<int> vec;
14588 ShowCompositeInfoWindow(0, 0, 0, vec);
14589 ReloadVP(false);
14590}
14591
14592void ChartCanvas::UpdateCanvasControlBar() {
14593 if (m_pianoFrozen) return;
14594
14595 if (!GetpCurrentStack()) return;
14596 if (!ChartData) return;
14597 if (!g_bShowChartBar) return;
14598
14599 int sel_type = -1;
14600 int sel_family = -1;
14601
14602 std::vector<int> piano_chart_index_array;
14603 std::vector<int> empty_piano_chart_index_array;
14604
14605 wxString old_hash = m_Piano->GetStoredHash();
14606
14607 if (GetQuiltMode()) {
14608 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14609 GetQuiltFullScreendbIndexArray());
14610
14611 std::vector<int> piano_active_chart_index_array =
14612 GetQuiltCandidatedbIndexArray();
14613 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14614
14615 std::vector<int> piano_eclipsed_chart_index_array =
14616 GetQuiltEclipsedStackdbIndexArray();
14617 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14618
14619 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14620 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14621
14622 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14623 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14624 } else {
14625 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14626 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14627 // TODO refresh_Piano();
14628
14629 if (m_singleChart) {
14630 sel_type = m_singleChart->GetChartType();
14631 sel_family = m_singleChart->GetChartFamily();
14632 }
14633 }
14634
14635 // Set up the TMerc and Skew arrays
14636 std::vector<int> piano_skew_chart_index_array;
14637 std::vector<int> piano_tmerc_chart_index_array;
14638 std::vector<int> piano_poly_chart_index_array;
14639
14640 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14641 const ChartTableEntry &ctei =
14642 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14643 double skew_norm = ctei.GetChartSkew();
14644 if (skew_norm > 180.) skew_norm -= 360.;
14645
14646 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14647 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14648
14649 // Polyconic skewed charts should show as skewed
14650 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14651 if (fabs(skew_norm) > 1.)
14652 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14653 else
14654 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14655 } else if (fabs(skew_norm) > 1.)
14656 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14657 }
14658 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14659 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14660 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14661
14662 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14663 if (new_hash != old_hash) {
14664 m_Piano->FormatKeys();
14665 HideChartInfoWindow();
14666 m_Piano->ResetRollover();
14667 SetQuiltChartHiLiteIndex(-1);
14668 m_brepaint_piano = true;
14669 }
14670
14671 // Create a bitmask int that describes what Family/Type of charts are shown in
14672 // the bar, and notify the platform.
14673 int mask = 0;
14674 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14675 const ChartTableEntry &ctei =
14676 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14677 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14678 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14679 if (e == CHART_FAMILY_RASTER) mask |= 1;
14680 if (e == CHART_FAMILY_VECTOR) {
14681 if (t == CHART_TYPE_CM93COMP)
14682 mask |= 4;
14683 else
14684 mask |= 2;
14685 }
14686 }
14687
14688 wxString s_indicated;
14689 if (sel_type == CHART_TYPE_CM93COMP)
14690 s_indicated = "cm93";
14691 else {
14692 if (sel_family == CHART_FAMILY_RASTER)
14693 s_indicated = "raster";
14694 else if (sel_family == CHART_FAMILY_VECTOR)
14695 s_indicated = "vector";
14696 }
14697
14698 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14699}
14700
14701void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14702
14703void ChartCanvas::PianoPopupMenu(
14704 int x, int y, int selected_index,
14705 const std::vector<int> &selected_dbIndex_array) {
14706 if (!GetpCurrentStack()) return;
14707
14708 // No context menu if quilting is disabled
14709 if (!GetQuiltMode()) return;
14710
14711 m_piano_ctx_menu = new wxMenu();
14712
14713 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14714 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14715 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14716 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14717 } else {
14718 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14719 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14720 // wxEVT_COMMAND_MENU_SELECTED,
14721 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14722
14723 menu_selected_dbIndex = selected_dbIndex_array[0];
14724 menu_selected_index = selected_index;
14725
14726 // Search the no-show array
14727 bool b_is_in_noshow = false;
14728 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14729 if (m_quilt_noshow_index_array[i] ==
14730 menu_selected_dbIndex) // chart is in the noshow list
14731 {
14732 b_is_in_noshow = true;
14733 break;
14734 }
14735 }
14736
14737 if (b_is_in_noshow) {
14738 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14739 _("Show This Chart"));
14740 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14741 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14742 } else if (GetpCurrentStack()->nEntry > 1) {
14743 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14744 _("Hide This Chart"));
14745 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14746 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14747 }
14748 }
14749
14750 wxPoint pos = wxPoint(x, y - 30);
14751
14752 // Invoke the drop-down menu
14753 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14754 PopupMenu(m_piano_ctx_menu, pos);
14755
14756 delete m_piano_ctx_menu;
14757 m_piano_ctx_menu = NULL;
14758
14759 HideChartInfoWindow();
14760 m_Piano->ResetRollover();
14761
14762 SetQuiltChartHiLiteIndex(-1);
14763 ClearQuiltChartHiLiteIndexArray();
14764
14765 ReloadVP();
14766}
14767
14768void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14769 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14770 if (m_quilt_noshow_index_array[i] ==
14771 menu_selected_dbIndex) // chart is in the noshow list
14772 {
14773 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14774 break;
14775 }
14776 }
14777}
14778
14779void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14780 if (!GetpCurrentStack()) return;
14781 if (!ChartData) return;
14782
14783 RemoveChartFromQuilt(menu_selected_dbIndex);
14784
14785 // It could happen that the chart being disabled is the reference
14786 // chart....
14787 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14788 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14789
14790 int i = menu_selected_index + 1; // select next smaller scale chart
14791 bool b_success = false;
14792 while (i < GetpCurrentStack()->nEntry - 1) {
14793 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14794 if (type == ChartData->GetDBChartType(dbIndex)) {
14795 SelectQuiltRefChart(i);
14796 b_success = true;
14797 break;
14798 }
14799 i++;
14800 }
14801
14802 // If that did not work, try to select the next larger scale compatible
14803 // chart
14804 if (!b_success) {
14805 i = menu_selected_index - 1;
14806 while (i > 0) {
14807 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14808 if (type == ChartData->GetDBChartType(dbIndex)) {
14809 SelectQuiltRefChart(i);
14810 b_success = true;
14811 break;
14812 }
14813 i--;
14814 }
14815 }
14816 }
14817}
14818
14819void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14820 // Remove the item from the list (if it appears) to avoid multiple addition
14821 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14822 if (m_quilt_noshow_index_array[i] ==
14823 dbIndex) // chart is already in the noshow list
14824 {
14825 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14826 break;
14827 }
14828 }
14829
14830 m_quilt_noshow_index_array.push_back(dbIndex);
14831}
14832
14833bool ChartCanvas::UpdateS52State() {
14834 bool retval = false;
14835
14836 if (ps52plib) {
14837 ps52plib->SetShowS57Text(m_encShowText);
14838 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14839 ps52plib->m_bShowSoundg = m_encShowDepth;
14840 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14841 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14842
14843 // Lights
14844 if (!m_encShowLights) // On, going off
14845 ps52plib->AddObjNoshow("LIGHTS");
14846 else // Off, going on
14847 ps52plib->RemoveObjNoshow("LIGHTS");
14848 ps52plib->SetLightsOff(!m_encShowLights);
14849 ps52plib->m_bExtendLightSectors = true;
14850
14851 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14852 ps52plib->SetAnchorOn(m_encShowAnchor);
14853 ps52plib->SetQualityOfData(m_encShowDataQual);
14854 }
14855
14856 return retval;
14857}
14858
14859void ChartCanvas::SetShowENCDataQual(bool show) {
14860 m_encShowDataQual = show;
14861 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14862 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14863
14864 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14865}
14866
14867void ChartCanvas::SetShowENCText(bool show) {
14868 m_encShowText = show;
14869 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14870 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14871
14872 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14873}
14874
14875void ChartCanvas::SetENCDisplayCategory(int category) {
14876 m_encDisplayCategory = category;
14877 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14878}
14879
14880void ChartCanvas::SetShowENCDepth(bool show) {
14881 m_encShowDepth = show;
14882 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14883 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14884
14885 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14886}
14887
14888void ChartCanvas::SetShowENCLightDesc(bool show) {
14889 m_encShowLightDesc = show;
14890 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14891 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14892
14893 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14894}
14895
14896void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14897 m_encShowBuoyLabels = show;
14898 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14899}
14900
14901void ChartCanvas::SetShowENCLights(bool show) {
14902 m_encShowLights = show;
14903 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14904 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14905
14906 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14907}
14908
14909void ChartCanvas::SetShowENCAnchor(bool show) {
14910 m_encShowAnchor = show;
14911 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14912 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14913
14914 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14915}
14916
14917wxRect ChartCanvas::GetMUIBarRect() {
14918 wxRect rv;
14919 if (m_muiBar) {
14920 rv = m_muiBar->GetRect();
14921 }
14922
14923 return rv;
14924}
14925
14926void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14927 if (!GetAlertString().IsEmpty()) {
14928 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14929 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14930
14931 dc.SetFont(*pfont);
14932 dc.SetPen(*wxTRANSPARENT_PEN);
14933
14934 dc.SetBrush(wxColour(243, 229, 47));
14935 int w, h;
14936 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14937 h += 2;
14938 // int yp = vp.pix_height - 20 - h;
14939
14940 wxRect sbr = GetScaleBarRect();
14941 int xp = sbr.x + sbr.width + 10;
14942 int yp = (sbr.y + sbr.height) - h;
14943
14944 int wdraw = w + 10;
14945 dc.DrawRectangle(xp, yp, wdraw, h);
14946 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14947 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14948 }
14949}
14950
14951//--------------------------------------------------------------------------------------------------------
14952// Screen Brightness Control Support Routines
14953//
14954//--------------------------------------------------------------------------------------------------------
14955
14956#ifdef __UNIX__
14957#define BRIGHT_XCALIB
14958#define __OPCPN_USEICC__
14959#endif
14960
14961#ifdef __OPCPN_USEICC__
14962int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14963 double co_green, double co_blue);
14964
14965wxString temp_file_name;
14966#endif
14967
14968#if 0
14969class ocpnCurtain: public wxDialog
14970{
14971 DECLARE_CLASS( ocpnCurtain )
14972 DECLARE_EVENT_TABLE()
14973
14974public:
14975 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14976 ~ocpnCurtain( );
14977 bool ProcessEvent(wxEvent& event);
14978
14979};
14980
14981IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14982
14983BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14984END_EVENT_TABLE()
14985
14986ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14987{
14988 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14989}
14990
14991ocpnCurtain::~ocpnCurtain()
14992{
14993}
14994
14995bool ocpnCurtain::ProcessEvent(wxEvent& event)
14996{
14997 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14998 return GetParent()->GetEventHandler()->ProcessEvent(event);
14999}
15000#endif
15001
15002#ifdef _WIN32
15003#include <windows.h>
15004
15005HMODULE hGDI32DLL;
15006typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15007typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15008SetDeviceGammaRamp_ptr_type
15009 g_pSetDeviceGammaRamp; // the API entry points in the dll
15010GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15011
15012WORD *g_pSavedGammaMap;
15013
15014#endif
15015
15016int InitScreenBrightness() {
15017#ifdef _WIN32
15018#ifdef ocpnUSE_GL
15019 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15020 HDC hDC;
15021 BOOL bbr;
15022
15023 if (NULL == hGDI32DLL) {
15024 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15025
15026 if (NULL != hGDI32DLL) {
15027 // Get the entry points of the required functions
15028 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15029 hGDI32DLL, "SetDeviceGammaRamp");
15030 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15031 hGDI32DLL, "GetDeviceGammaRamp");
15032
15033 // If the functions are not found, unload the DLL and return false
15034 if ((NULL == g_pSetDeviceGammaRamp) ||
15035 (NULL == g_pGetDeviceGammaRamp)) {
15036 FreeLibrary(hGDI32DLL);
15037 hGDI32DLL = NULL;
15038 return 0;
15039 }
15040 }
15041 }
15042
15043 // Interface is ready, so....
15044 // Get some storage
15045 if (!g_pSavedGammaMap) {
15046 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15047
15048 hDC = GetDC(NULL); // Get the full screen DC
15049 bbr = g_pGetDeviceGammaRamp(
15050 hDC, g_pSavedGammaMap); // Get the existing ramp table
15051 ReleaseDC(NULL, hDC); // Release the DC
15052 }
15053
15054 // On Windows hosts, try to adjust the registry to allow full range
15055 // setting of Gamma table This is an undocumented Windows hack.....
15056 wxRegKey *pRegKey = new wxRegKey(
15057 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15058 "NT\\CurrentVersion\\ICM");
15059 if (!pRegKey->Exists()) pRegKey->Create();
15060 pRegKey->SetValue("GdiIcmGammaRange", 256);
15061
15062 g_brightness_init = true;
15063 return 1;
15064 }
15065#endif
15066
15067 {
15068 if (NULL == g_pcurtain) {
15069 if (top_frame::Get()->CanSetTransparent()) {
15070 // Build the curtain window
15071 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15072 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15073 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15074 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15075
15076 // g_pcurtain = new ocpnCurtain(gFrame,
15077 // wxPoint(0,0),::wxGetDisplaySize(),
15078 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15079 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15080
15081 g_pcurtain->Hide();
15082
15083 HWND hWnd = GetHwndOf(g_pcurtain);
15084 SetWindowLong(hWnd, GWL_EXSTYLE,
15085 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15086 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15087 g_pcurtain->SetTransparent(0);
15088
15089 g_pcurtain->Maximize();
15090 g_pcurtain->Show();
15091
15092 // All of this is obtuse, but necessary for Windows...
15093 g_pcurtain->Enable();
15094 g_pcurtain->Disable();
15095
15096 top_frame::Get()->Disable();
15097 top_frame::Get()->Enable();
15098 // SetFocus();
15099 }
15100 }
15101 g_brightness_init = true;
15102
15103 return 1;
15104 }
15105#else
15106 // Look for "xcalib" application
15107 wxString cmd("xcalib -version");
15108
15109 wxArrayString output;
15110 long r = wxExecute(cmd, output);
15111 if (0 != r)
15112 wxLogMessage(
15113 " External application \"xcalib\" not found. Screen brightness "
15114 "not changed.");
15115
15116 g_brightness_init = true;
15117 return 0;
15118#endif
15119}
15120
15121int RestoreScreenBrightness() {
15122#ifdef _WIN32
15123
15124 if (g_pSavedGammaMap) {
15125 HDC hDC = GetDC(NULL); // Get the full screen DC
15126 g_pSetDeviceGammaRamp(hDC,
15127 g_pSavedGammaMap); // Restore the saved ramp table
15128 ReleaseDC(NULL, hDC); // Release the DC
15129
15130 free(g_pSavedGammaMap);
15131 g_pSavedGammaMap = NULL;
15132 }
15133
15134 if (g_pcurtain) {
15135 g_pcurtain->Close();
15136 g_pcurtain->Destroy();
15137 g_pcurtain = NULL;
15138 }
15139
15140 g_brightness_init = false;
15141 return 1;
15142
15143#endif
15144
15145#ifdef BRIGHT_XCALIB
15146 if (g_brightness_init) {
15147 wxString cmd;
15148 cmd = "xcalib -clear";
15149 wxExecute(cmd, wxEXEC_ASYNC);
15150 g_brightness_init = false;
15151 }
15152
15153 return 1;
15154#endif
15155
15156 return 0;
15157}
15158
15159// Set brightness. [0..100]
15160int SetScreenBrightness(int brightness) {
15161#ifdef _WIN32
15162
15163 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15164 // some (most modern?) versions of gdi32.dll Load the required library dll,
15165 // if not already in place
15166#ifdef ocpnUSE_GL
15167 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15168 if (g_pcurtain) {
15169 g_pcurtain->Close();
15170 g_pcurtain->Destroy();
15171 g_pcurtain = NULL;
15172 }
15173
15174 InitScreenBrightness();
15175
15176 if (NULL == hGDI32DLL) {
15177 // Unicode stuff.....
15178 wchar_t wdll_name[80];
15179 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15180 LPCWSTR cstr = wdll_name;
15181
15182 hGDI32DLL = LoadLibrary(cstr);
15183
15184 if (NULL != hGDI32DLL) {
15185 // Get the entry points of the required functions
15186 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15187 hGDI32DLL, "SetDeviceGammaRamp");
15188 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15189 hGDI32DLL, "GetDeviceGammaRamp");
15190
15191 // If the functions are not found, unload the DLL and return false
15192 if ((NULL == g_pSetDeviceGammaRamp) ||
15193 (NULL == g_pGetDeviceGammaRamp)) {
15194 FreeLibrary(hGDI32DLL);
15195 hGDI32DLL = NULL;
15196 return 0;
15197 }
15198 }
15199 }
15200
15201 HDC hDC = GetDC(NULL); // Get the full screen DC
15202
15203 /*
15204 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15205 if (cmcap != CM_GAMMA_RAMP)
15206 {
15207 wxLogMessage(" Video hardware does not support brightness control by
15208 gamma ramp adjustment."); return false;
15209 }
15210 */
15211
15212 int increment = brightness * 256 / 100;
15213
15214 // Build the Gamma Ramp table
15215 WORD GammaTable[3][256];
15216
15217 int table_val = 0;
15218 for (int i = 0; i < 256; i++) {
15219 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15220 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15221 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15222
15223 table_val += increment;
15224
15225 if (table_val > 65535) table_val = 65535;
15226 }
15227
15228 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15229 ReleaseDC(NULL, hDC); // Release the DC
15230
15231 return 1;
15232 }
15233#endif
15234
15235 {
15236 if (g_pSavedGammaMap) {
15237 HDC hDC = GetDC(NULL); // Get the full screen DC
15238 g_pSetDeviceGammaRamp(hDC,
15239 g_pSavedGammaMap); // Restore the saved ramp table
15240 ReleaseDC(NULL, hDC); // Release the DC
15241 }
15242
15243 if (brightness < 100) {
15244 if (NULL == g_pcurtain) InitScreenBrightness();
15245
15246 if (g_pcurtain) {
15247 int sbrite = wxMax(1, brightness);
15248 sbrite = wxMin(100, sbrite);
15249
15250 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15251 }
15252 } else {
15253 if (g_pcurtain) {
15254 g_pcurtain->Close();
15255 g_pcurtain->Destroy();
15256 g_pcurtain = NULL;
15257 }
15258 }
15259
15260 return 1;
15261 }
15262
15263#endif
15264
15265#ifdef BRIGHT_XCALIB
15266
15267 if (!g_brightness_init) {
15268 last_brightness = 100;
15269 g_brightness_init = true;
15270 temp_file_name = wxFileName::CreateTempFileName("");
15271 InitScreenBrightness();
15272 }
15273
15274#ifdef __OPCPN_USEICC__
15275 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15276 // desired, and then activate this temporary profile using xcalib <filename>
15277 if (!CreateSimpleICCProfileFile(
15278 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15279 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15280 wxString cmd("xcalib ");
15281 cmd += temp_file_name;
15282
15283 wxExecute(cmd, wxEXEC_ASYNC);
15284 }
15285
15286#else
15287 // Or, use "xcalib -co" to set overall contrast value
15288 // This is not as nice, since the -co parameter wants to be a fraction of
15289 // the current contrast, and values greater than 100 are not allowed. As a
15290 // result, increases of contrast must do a "-clear" step first, which
15291 // produces objectionable flashing.
15292 if (brightness > last_brightness) {
15293 wxString cmd;
15294 cmd = "xcalib -clear";
15295 wxExecute(cmd, wxEXEC_ASYNC);
15296
15297 ::wxMilliSleep(10);
15298
15299 int brite_adj = wxMax(1, brightness);
15300 cmd.Printf("xcalib -co %2d -a", brite_adj);
15301 wxExecute(cmd, wxEXEC_ASYNC);
15302 } else {
15303 int brite_adj = wxMax(1, brightness);
15304 int factor = (brite_adj * 100) / last_brightness;
15305 factor = wxMax(1, factor);
15306 wxString cmd;
15307 cmd.Printf("xcalib -co %2d -a", factor);
15308 wxExecute(cmd, wxEXEC_ASYNC);
15309 }
15310
15311#endif
15312
15313 last_brightness = brightness;
15314
15315#endif
15316
15317 return 0;
15318}
15319
15320#ifdef __OPCPN_USEICC__
15321
15322#define MLUT_TAG 0x6d4c5554L
15323#define VCGT_TAG 0x76636774L
15324
15325int GetIntEndian(unsigned char *s) {
15326 int ret;
15327 unsigned char *p;
15328 int i;
15329
15330 p = (unsigned char *)&ret;
15331
15332 if (1)
15333 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15334 else
15335 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15336
15337 return ret;
15338}
15339
15340unsigned short GetShortEndian(unsigned char *s) {
15341 unsigned short ret;
15342 unsigned char *p;
15343 int i;
15344
15345 p = (unsigned char *)&ret;
15346
15347 if (1)
15348 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15349 else
15350 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15351
15352 return ret;
15353}
15354
15355// Create a very simple Gamma correction file readable by xcalib
15356int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15357 double co_green, double co_blue) {
15358 FILE *fp;
15359
15360 if (file_name) {
15361 fp = fopen(file_name, "wb");
15362 if (!fp) return -1; /* file can not be created */
15363 } else
15364 return -1; /* filename char pointer not valid */
15365
15366 // Write header
15367 char header[128];
15368 for (int i = 0; i < 128; i++) header[i] = 0;
15369
15370 fwrite(header, 128, 1, fp);
15371
15372 // Num tags
15373 int numTags0 = 1;
15374 int numTags = GetIntEndian((unsigned char *)&numTags0);
15375 fwrite(&numTags, 1, 4, fp);
15376
15377 int tagName0 = VCGT_TAG;
15378 int tagName = GetIntEndian((unsigned char *)&tagName0);
15379 fwrite(&tagName, 1, 4, fp);
15380
15381 int tagOffset0 = 128 + 4 * sizeof(int);
15382 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15383 fwrite(&tagOffset, 1, 4, fp);
15384
15385 int tagSize0 = 1;
15386 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15387 fwrite(&tagSize, 1, 4, fp);
15388
15389 fwrite(&tagName, 1, 4, fp); // another copy of tag
15390
15391 fwrite(&tagName, 1, 4, fp); // dummy
15392
15393 // Table type
15394
15395 /* VideoCardGammaTable (The simplest type) */
15396 int gammatype0 = 0;
15397 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15398 fwrite(&gammatype, 1, 4, fp);
15399
15400 int numChannels0 = 3;
15401 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15402 fwrite(&numChannels, 1, 2, fp);
15403
15404 int numEntries0 = 256;
15405 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15406 fwrite(&numEntries, 1, 2, fp);
15407
15408 int entrySize0 = 1;
15409 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15410 fwrite(&entrySize, 1, 2, fp);
15411
15412 unsigned char ramp[256];
15413
15414 // Red ramp
15415 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15416 fwrite(ramp, 256, 1, fp);
15417
15418 // Green ramp
15419 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15420 fwrite(ramp, 256, 1, fp);
15421
15422 // Blue ramp
15423 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15424 fwrite(ramp, 256, 1, fp);
15425
15426 fclose(fp);
15427
15428 return 0;
15429}
15430#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_121.cpp:833
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:1315
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1314
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1315
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1314
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:173
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13793
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:4551
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11837
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4547
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3643
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13752
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:4497
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:805
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2343
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8001
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7810
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5079
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:896
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4628
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5359
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:183
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:789
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4572
bool IsTideDialogOpen() const
Definition chcanv.cpp:13791
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:4634
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13748
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4492
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5378
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10246
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:485
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
char IDX_station_name[MAXNAMELEN]
Name of the tidal or current station.
Definition idx_entry.h:62
int station_tz_offset
Offset in seconds to convert from harmonic data (epochs) to the station time zone.
Definition idx_entry.h:93
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:60
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:201
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:59
Data for a loaded plugin, including dl-loaded library.
int m_cap_flag
PlugIn Capabilities descriptor.
PluginLoader is a backend module without any direct GUI functionality.
const ArrayOfPlugIns * GetPlugInArray()
Return list of currently loaded plugins.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1783
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h:46
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:77
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:36
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Display utilities.
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:76
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
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:183
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:187
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.