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/own_ship.h"
58#include "model/plugin_comm.h"
59#include "model/route.h"
60#include "model/routeman.h"
61#include "model/select.h"
62#include "model/select_item.h"
63#include "model/track.h"
64
65#include "ais.h"
68#include "canvas_config.h"
69#include "canvas_menu.h"
70#include "canvas_options.h"
71#include "chartdb.h"
72#include "chartimg.h"
73#include "chcanv.h"
74#include "ch_info_win.h"
75#include "cm93.h" // for chart outline draw
76#include "compass.h"
77#include "concanv.h"
78#include "detail_slider.h"
79#include "hotkeys_dlg.h"
80#include "font_mgr.h"
81#include "gl_texture_descr.h"
82#include "go_to_position_dlg.h"
83#include "gshhs.h"
84#include "ienc_toolbar.h"
85#include "kml.h"
86#include "line_clip.h"
87#include "mark_info.h"
88#include "mbtiles.h"
89#include "mui_bar.h"
90#include "navutil.h"
91#include "ocpn_aui_manager.h"
92#include "ocpndc.h"
93#include "ocpn_frame.h"
94#include "ocpn_pixel.h"
95#include "ocpn_region.h"
96#include "options.h"
97#include "piano.h"
98#include "pluginmanager.h"
99#include "quilt.h"
100#include "route_gui.h"
101#include "routemanagerdialog.h"
102#include "route_point_gui.h"
103#include "route_prop_dlg_impl.h"
104#include "s52plib.h"
105#include "s52utils.h"
106#include "s57_query_dlg.h"
107#include "s57chart.h" // for ArrayOfS57Obj
108#include "shapefile_basemap.h"
109#include "styles.h"
110#include "tcmgr.h"
111#include "tc_win.h"
112#include "thumbwin.h"
113#include "tide_time.h"
114#include "timers.h"
115#include "toolbar.h"
116#include "track_gui.h"
117#include "track_prop_dlg.h"
118#include "undo.h"
119
120#include "s57_ocpn_utils.h"
121
122#ifdef __ANDROID__
123#include "androidUTIL.h"
124#endif
125
126#ifdef ocpnUSE_GL
127#include "gl_chart_canvas.h"
130#endif
131
132#ifdef __VISUALC__
133#include <wx/msw/msvcrt.h>
134#endif
135
136#ifndef __WXMSW__
137#include <signal.h>
138#include <setjmp.h>
139#endif
140
141#ifdef __WXMSW__
142#define printf printf2
143
144int __cdecl printf2(const char *format, ...) {
145 char str[1024];
146
147 va_list argptr;
148 va_start(argptr, format);
149 int ret = vsnprintf(str, sizeof(str), format, argptr);
150 va_end(argptr);
151 OutputDebugStringA(str);
152 return ret;
153}
154#endif
155
156#if defined(__MSVC__) && (_MSC_VER < 1700)
157#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
158#endif
159
160// Define to enable the invocation of a temporary menubar by pressing the Alt
161// key. Not implemented for Windows XP, as it interferes with Alt-Tab
162// processing.
163#define OCPN_ALT_MENUBAR 1
164
165// Profiling support
166// #include "/usr/include/valgrind/callgrind.h"
167
168// ----------------------------------------------------------------------------
169// Useful Prototypes
170// ----------------------------------------------------------------------------
171extern ColorScheme global_color_scheme; // library dependence
172extern wxColor GetDimColor(wxColor c); // library dependence
173
174arrayofCanvasPtr g_canvasArray;
176static bool g_bSmoothRecenter = true;
177static bool bDrawCurrentValues;
187static int mouse_x;
197static int mouse_y;
198static bool mouse_leftisdown;
199static bool g_brouteCreating;
200static int r_gamma_mult;
201static int g_gamma_mult;
202static int b_gamma_mult;
203static int gamma_state;
204static bool g_brightness_init;
205static int last_brightness;
206static wxGLContext *g_pGLcontext; // shared common context
207
208// "Curtain" mode parameters
209static wxDialog *g_pcurtain;
210
211static wxString g_lastS52PLIBPluginMessage;
212
213#define MIN_BRIGHT 10
214#define MAX_BRIGHT 100
215
216//------------------------------------------------------------------------------
217// ChartCanvas Implementation
218//------------------------------------------------------------------------------
219BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
220EVT_PAINT(ChartCanvas::OnPaint)
221EVT_ACTIVATE(ChartCanvas::OnActivate)
222EVT_SIZE(ChartCanvas::OnSize)
223#ifndef HAVE_WX_GESTURE_EVENTS
224EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
225#endif
226EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
227EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
228EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
229EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
230EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
231EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
232EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
233EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
234EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
235EVT_KEY_UP(ChartCanvas::OnKeyUp)
236EVT_CHAR(ChartCanvas::OnKeyChar)
237EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
238EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
239EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
240EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
241EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
242EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
243EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
244EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
245EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
246EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
247
248END_EVENT_TABLE()
249
250// Define a constructor for my canvas
251ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
252 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
253 m_nmea_log(nmea_log) {
254 parent_frame = (MyFrame *)frame; // save a pointer to parent
255 m_canvasIndex = canvasIndex;
256
257 pscratch_bm = NULL;
258
259 SetBackgroundColour(wxColour(0, 0, 0));
260 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
261 // color scheme change
262
263 m_groupIndex = 0;
264 m_bDrawingRoute = false;
265 m_bRouteEditing = false;
266 m_bMarkEditing = false;
267 m_bRoutePoinDragging = false;
268 m_bIsInRadius = false;
269 m_bMayToggleMenuBar = true;
270
271 m_bFollow = false;
272 m_bShowNavobjects = true;
273 m_bTCupdate = false;
274 m_bAppendingRoute = false; // was true in MSW, why??
275 pThumbDIBShow = NULL;
276 m_bShowCurrent = false;
277 m_bShowTide = false;
278 bShowingCurrent = false;
279 pCwin = NULL;
280 warp_flag = false;
281 m_bzooming = false;
282 m_b_paint_enable = true;
283 m_routeState = 0;
284
285 pss_overlay_bmp = NULL;
286 pss_overlay_mask = NULL;
287 m_bChartDragging = false;
288 m_bMeasure_Active = false;
289 m_bMeasure_DistCircle = false;
290 m_pMeasureRoute = NULL;
291 m_pTrackRolloverWin = NULL;
292 m_pRouteRolloverWin = NULL;
293 m_pAISRolloverWin = NULL;
294 m_bedge_pan = false;
295 m_disable_edge_pan = false;
296 m_dragoffsetSet = false;
297 m_bautofind = false;
298 m_bFirstAuto = true;
299 m_groupIndex = 0;
300 m_singleChart = NULL;
301 m_upMode = NORTH_UP_MODE;
302 m_bShowAIS = true;
303 m_bShowAISScaled = false;
304 m_timed_move_vp_active = false;
305 m_inPinch = false;
306 m_disable_adjust_on_zoom = false;
307
308 m_vLat = 0.;
309 m_vLon = 0.;
310
311 m_pCIWin = NULL;
312
313 m_pSelectedRoute = NULL;
314 m_pSelectedTrack = NULL;
315 m_pRoutePointEditTarget = NULL;
316 m_pFoundPoint = NULL;
317 m_pMouseRoute = NULL;
318 m_prev_pMousePoint = NULL;
319 m_pEditRouteArray = NULL;
320 m_pFoundRoutePoint = NULL;
321 m_FinishRouteOnKillFocus = true;
322
323 m_pRolloverRouteSeg = NULL;
324 m_pRolloverTrackSeg = NULL;
325 m_bsectors_shown = false;
326
327 m_bbrightdir = false;
328 r_gamma_mult = 1;
329 g_gamma_mult = 1;
330 b_gamma_mult = 1;
331
332 m_pos_image_user_day = NULL;
333 m_pos_image_user_dusk = NULL;
334 m_pos_image_user_night = NULL;
335 m_pos_image_user_grey_day = NULL;
336 m_pos_image_user_grey_dusk = NULL;
337 m_pos_image_user_grey_night = NULL;
338
339 m_zoom_factor = 1;
340 m_rotation_speed = 0;
341 m_mustmove = 0;
342
343 m_OSoffsetx = 0.;
344 m_OSoffsety = 0.;
345
346 m_pos_image_user_yellow_day = NULL;
347 m_pos_image_user_yellow_dusk = NULL;
348 m_pos_image_user_yellow_night = NULL;
349
350 SetOwnShipState(SHIP_INVALID);
351
352 undo = new Undo(this);
353
354 VPoint.Invalidate();
355
356 m_glcc = NULL;
357
358 m_focus_indicator_pix = 1;
359
360 m_pCurrentStack = NULL;
361 m_bpersistent_quilt = false;
362 m_piano_ctx_menu = NULL;
363 m_Compass = NULL;
364 m_NotificationsList = NULL;
365 m_notification_button = NULL;
366
367 g_ChartNotRenderScaleFactor = 2.0;
368 m_bShowScaleInStatusBar = true;
369
370 m_muiBar = NULL;
371 m_bShowScaleInStatusBar = false;
372 m_show_focus_bar = true;
373
374 m_bShowOutlines = false;
375 m_bDisplayGrid = false;
376 m_bShowDepthUnits = true;
377 m_encDisplayCategory = (int)STANDARD;
378
379 m_encShowLights = true;
380 m_encShowAnchor = true;
381 m_encShowDataQual = false;
382 m_bShowGPS = true;
383 m_pQuilt = new Quilt(this);
384 SetQuiltMode(true);
385 SetAlertString("");
386 m_sector_glat = 0;
387 m_sector_glon = 0;
388 g_PrintingInProgress = false;
389
390#ifdef HAVE_WX_GESTURE_EVENTS
391 m_oldVPSScale = -1.0;
392 m_popupWanted = false;
393 m_leftdown = false;
394#endif /* HAVE_WX_GESTURE_EVENTS */
395 m_inLongPress = false;
396 m_sw_down_time = 0;
397 m_sw_up_time = 0;
398 m_sw_left_down.Start();
399 m_sw_left_up.Start();
400
401 SetupGlCanvas();
402
403 singleClickEventIsValid = false;
404
405 // Build the cursors
406
407 pCursorLeft = NULL;
408 pCursorRight = NULL;
409 pCursorUp = NULL;
410 pCursorDown = NULL;
411 pCursorArrow = NULL;
412 pCursorPencil = NULL;
413 pCursorCross = NULL;
414
415 RebuildCursors();
416
417 SetCursor(*pCursorArrow);
418
419 pPanTimer = new wxTimer(this, m_MouseDragging);
420 pPanTimer->Stop();
421
422 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
423 pMovementTimer->Stop();
424
425 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
426 pMovementStopTimer->Stop();
427
428 pRotDefTimer = new wxTimer(this, ROT_TIMER);
429 pRotDefTimer->Stop();
430
431 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
432 m_DoubleClickTimer->Stop();
433
434 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
435 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
436 m_chart_drag_inertia_active = false;
437
438 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
439 m_animationActive = false;
440 m_menuTimer.SetOwner(this, MENU_TIMER);
441 m_tap_timer.SetOwner(this, TAP_TIMER);
442
443 m_panx = m_pany = 0;
444 m_panspeed = 0;
445 m_panx_target_final = m_pany_target_final = 0;
446 m_panx_target_now = m_pany_target_now = 0;
447 m_DragTrigger = -1;
448
449 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
450 pCurTrackTimer->Stop();
451 m_curtrack_timer_msec = 10;
452
453 m_wheelzoom_stop_oneshot = 0;
454 m_last_wheel_dir = 0;
455
456 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
457
458 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
459
460 m_rollover_popup_timer_msec = 20;
461
462 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
463
464 m_b_rot_hidef = true;
465
466 proute_bm = NULL;
467 m_prot_bm = NULL;
468
469 m_upMode = NORTH_UP_MODE;
470 m_bLookAhead = false;
471
472 // Set some benign initial values
473
474 m_cs = GLOBAL_COLOR_SCHEME_DAY;
475 VPoint.clat = 0;
476 VPoint.clon = 0;
477 VPoint.view_scale_ppm = 1;
478 VPoint.Invalidate();
479 m_nMeasureState = 0;
480 m_ignore_next_leftup = false;
481
482 m_canvas_scale_factor = 1.;
483
484 m_canvas_width = 1000;
485
486 m_overzoomTextWidth = 0;
487 m_overzoomTextHeight = 0;
488
489 // Create the default world chart
490 pWorldBackgroundChart = new GSHHSChart;
491 gShapeBasemap.Reset();
492
493 // Create the default depth unit emboss maps
494 m_pEM_Feet = NULL;
495 m_pEM_Meters = NULL;
496 m_pEM_Fathoms = NULL;
497
498 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
499
500 m_pEM_OverZoom = NULL;
501 SetOverzoomFont();
502 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
503
504 // Build icons for tide/current points
505 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
506 m_bmTideDay =
507 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
508
509 // Dusk
510 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
511
512 // Night
513 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
514
515 // Build Dusk/Night ownship icons
516 double factor_dusk = 0.5;
517 double factor_night = 0.25;
518
519 // Red
520 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
521
522 int rimg_width = m_os_image_red_day.GetWidth();
523 int rimg_height = m_os_image_red_day.GetHeight();
524
525 m_os_image_red_dusk = m_os_image_red_day.Copy();
526 m_os_image_red_night = m_os_image_red_day.Copy();
527
528 for (int iy = 0; iy < rimg_height; iy++) {
529 for (int ix = 0; ix < rimg_width; ix++) {
530 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
531 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
532 m_os_image_red_day.GetGreen(ix, iy),
533 m_os_image_red_day.GetBlue(ix, iy));
534 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
535 hsv.value = hsv.value * factor_dusk;
536 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
537 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
538
539 hsv = wxImage::RGBtoHSV(rgb);
540 hsv.value = hsv.value * factor_night;
541 nrgb = wxImage::HSVtoRGB(hsv);
542 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
543 }
544 }
545 }
546
547 // Grey
548 m_os_image_grey_day =
549 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
550
551 int gimg_width = m_os_image_grey_day.GetWidth();
552 int gimg_height = m_os_image_grey_day.GetHeight();
553
554 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
555 m_os_image_grey_night = m_os_image_grey_day.Copy();
556
557 for (int iy = 0; iy < gimg_height; iy++) {
558 for (int ix = 0; ix < gimg_width; ix++) {
559 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
560 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
561 m_os_image_grey_day.GetGreen(ix, iy),
562 m_os_image_grey_day.GetBlue(ix, iy));
563 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
564 hsv.value = hsv.value * factor_dusk;
565 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
566 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
567
568 hsv = wxImage::RGBtoHSV(rgb);
569 hsv.value = hsv.value * factor_night;
570 nrgb = wxImage::HSVtoRGB(hsv);
571 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
572 }
573 }
574 }
575
576 // Yellow
577 m_os_image_yellow_day = m_os_image_red_day.Copy();
578
579 gimg_width = m_os_image_yellow_day.GetWidth();
580 gimg_height = m_os_image_yellow_day.GetHeight();
581
582 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
583 m_os_image_yellow_night = m_os_image_red_day.Copy();
584
585 for (int iy = 0; iy < gimg_height; iy++) {
586 for (int ix = 0; ix < gimg_width; ix++) {
587 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
588 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
589 m_os_image_yellow_day.GetGreen(ix, iy),
590 m_os_image_yellow_day.GetBlue(ix, iy));
591 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
592 hsv.hue += 60. / 360.; // shift to yellow
593 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
594 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
595
596 hsv = wxImage::RGBtoHSV(rgb);
597 hsv.value = hsv.value * factor_dusk;
598 hsv.hue += 60. / 360.; // shift to yellow
599 nrgb = wxImage::HSVtoRGB(hsv);
600 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
601
602 hsv = wxImage::RGBtoHSV(rgb);
603 hsv.hue += 60. / 360.; // shift to yellow
604 hsv.value = hsv.value * factor_night;
605 nrgb = wxImage::HSVtoRGB(hsv);
606 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
607 }
608 }
609 }
610
611 // Set initial pointers to ownship images
612 m_pos_image_red = &m_os_image_red_day;
613 m_pos_image_yellow = &m_os_image_yellow_day;
614 m_pos_image_grey = &m_os_image_grey_day;
615
616 SetUserOwnship();
617
618 m_pBrightPopup = NULL;
619
620#ifdef ocpnUSE_GL
621 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
622#endif
623
624 SetupGridFont();
625
626 m_Piano = new Piano(this);
627
628 m_bShowCompassWin = true;
629 m_Compass = new ocpnCompass(this);
630 m_Compass->SetScaleFactor(g_compass_scalefactor);
631 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
632
633 if (IsPrimaryCanvas()) {
634 m_notification_button = new NotificationButton(this);
635 m_notification_button->SetScaleFactor(g_compass_scalefactor);
636 m_notification_button->Show(true);
637 }
638
639 m_pianoFrozen = false;
640
641 SetMinSize(wxSize(200, 200));
642
643 m_displayScale = 1.0;
644#if defined(__WXOSX__) || defined(__WXGTK3__)
645 // Support scaled HDPI displays.
646 m_displayScale = GetContentScaleFactor();
647#endif
648 VPoint.SetPixelScale(m_displayScale);
649
650#ifdef HAVE_WX_GESTURE_EVENTS
651 // if (!m_glcc)
652 {
653 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
654 wxLogError("Failed to enable touch events");
655 }
656
657 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
658
659 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
660 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
661
662 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
663 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
664
665 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
666 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
667
668 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
669 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
670 }
671#endif
672
673 // Listen for notification events
674 auto &noteman = NotificationManager::GetInstance();
675
676 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
677 evt_notificationlist_change_listener.Listen(
678 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
679 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
680 if (m_NotificationsList && m_NotificationsList->IsShown()) {
681 m_NotificationsList->ReloadNotificationList();
682 }
683 Refresh();
684 });
685}
686
687ChartCanvas::~ChartCanvas() {
688 delete pThumbDIBShow;
689
690 // Delete Cursors
691 delete pCursorLeft;
692 delete pCursorRight;
693 delete pCursorUp;
694 delete pCursorDown;
695 delete pCursorArrow;
696 delete pCursorPencil;
697 delete pCursorCross;
698
699 delete pPanTimer;
700 delete pMovementTimer;
701 delete pMovementStopTimer;
702 delete pCurTrackTimer;
703 delete pRotDefTimer;
704 delete m_DoubleClickTimer;
705
706 delete m_pTrackRolloverWin;
707 delete m_pRouteRolloverWin;
708 delete m_pAISRolloverWin;
709 delete m_pBrightPopup;
710
711 delete m_pCIWin;
712
713 delete pscratch_bm;
714
715 m_dc_route.SelectObject(wxNullBitmap);
716 delete proute_bm;
717
718 delete pWorldBackgroundChart;
719 delete pss_overlay_bmp;
720
721 delete m_pEM_Feet;
722 delete m_pEM_Meters;
723 delete m_pEM_Fathoms;
724
725 delete m_pEM_OverZoom;
726 // delete m_pEM_CM93Offset;
727
728 delete m_prot_bm;
729
730 delete m_pos_image_user_day;
731 delete m_pos_image_user_dusk;
732 delete m_pos_image_user_night;
733 delete m_pos_image_user_grey_day;
734 delete m_pos_image_user_grey_dusk;
735 delete m_pos_image_user_grey_night;
736 delete m_pos_image_user_yellow_day;
737 delete m_pos_image_user_yellow_dusk;
738 delete m_pos_image_user_yellow_night;
739
740 delete undo;
741#ifdef ocpnUSE_GL
742 if (!g_bdisable_opengl) {
743 delete m_glcc;
744
745#if wxCHECK_VERSION(2, 9, 0)
746 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
747#endif
748 }
749#endif
750
751 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
752 // wx tries to deliver events to this canvas during destroy.
753 MUIBar *muiBar = m_muiBar;
754 m_muiBar = 0;
755 delete muiBar;
756 delete m_pQuilt;
757 delete m_pCurrentStack;
758 delete m_Compass;
759 delete m_Piano;
760 delete m_notification_button;
761}
762
763void ChartCanvas::SetupGridFont() {
764 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
765 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
766 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
767 m_pgridFont = FontMgr::Get().FindOrCreateFont(
768 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
769 FALSE, wxString("Arial"));
770}
771
772void ChartCanvas::RebuildCursors() {
773 delete pCursorLeft;
774 delete pCursorRight;
775 delete pCursorUp;
776 delete pCursorDown;
777 delete pCursorArrow;
778 delete pCursorPencil;
779 delete pCursorCross;
780
781 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
782 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
783
784 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
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 = new glChartCanvas(
916 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
917 // m_glcc = new glChartCanvas(this, pctx); //Shared
918 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
919 wxGLContext *pwxctx = new wxGLContext(m_glcc);
920 m_glcc->SetContext(pwxctx);
921 m_glcc->m_pParentCanvas = this;
922 // m_glcc->Reparent(this);
923 }
924 }
925 }
926#endif
927}
928
929void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
930 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
931
932 // On Android, we get a KillFocus on just about every keystroke.
933 // Why?
934#ifdef __ANDROID__
935 return;
936#endif
937
938 // Special logic:
939 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
940 // canvas focus. Why??? Who knows... So, we provide for this case by
941 // starting a timer if required to actually Finish() a route on a legitimate
942 // focus change, but not if the focus is quickly regained ( <20 msec.) on
943 // this canvas.
944#ifdef __WXOSX__
945 if (m_routeState && m_FinishRouteOnKillFocus)
946 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
947#else
948 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
949#endif
950}
951
952void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
953 m_routeFinishTimer.Stop();
954
955 // Try to keep the global top-line menubar selections up to date with the
956 // current "focus" canvas
957 gFrame->UpdateGlobalMenuItems(this);
958
959 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
960}
961
962void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
963 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
964}
965
966#ifdef HAVE_WX_GESTURE_EVENTS
967void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
968#ifdef __ANDROID__
969 /* we defer the popup menu call upon the leftup event
970 else the menu disappears immediately,
971 (see
972 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
973 */
974 m_popupWanted = true;
975#else
976 m_inLongPress = !g_bhide_context_menus;
977
978 // Send a synthetic mouse left-up event to sync the mouse pan logic.
979 m_menuPos = event.GetPosition();
980 wxMouseEvent ev(wxEVT_LEFT_UP);
981 ev.m_x = m_menuPos.x;
982 ev.m_y = m_menuPos.y;
983 wxPostEvent(this, ev);
984
985 // In touch mode, send a "RIGHT CLICK" event, for plugins
986 if (g_btouch) {
987 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
988 ev_right_click.m_x = m_menuPos.x;
989 ev_right_click.m_y = m_menuPos.y;
990 MouseEvent(ev_right_click);
991 }
992#endif
993}
994
995void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
996 // not implemented yet
997}
998
999void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1000
1001void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1002
1003void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1004#ifdef __WXGTK__
1005 long dt = m_sw_left_up.Time() - m_sw_up_time;
1006 m_sw_up_time = m_sw_left_up.Time();
1007
1008 // printf(" dt %ld\n",dt);
1009 if (dt < 5) {
1010 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1011 // ignore it.
1012 return;
1013 }
1014#endif
1015 // printf("Left_UP\n");
1016
1017 wxPoint pos = event.GetPosition();
1018
1019 m_leftdown = false;
1020
1021 if (!m_popupWanted) {
1022 wxMouseEvent ev(wxEVT_LEFT_UP);
1023 ev.m_x = pos.x;
1024 ev.m_y = pos.y;
1025 MouseEvent(ev);
1026 return;
1027 }
1028
1029 m_popupWanted = false;
1030
1031 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1032 ev.m_x = pos.x;
1033 ev.m_y = pos.y;
1034
1035 MouseEvent(ev);
1036}
1037
1038void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1039 m_leftdown = true;
1040
1041 // Detect and manage multiple left-downs coming from GTK mouse emulation
1042#ifdef __WXGTK__
1043 long dt = m_sw_left_down.Time() - m_sw_down_time;
1044 m_sw_down_time = m_sw_left_down.Time();
1045
1046 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1047
1048 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1049 // The timing between the two events is dependent upon the wxWidgets
1050 // message queue status, and the processing time required for intervening
1051 // events.
1052 // We detect and remove the duplicate events by measuring the elapsed time
1053 // between arrival of events.
1054 // Choose a duplicate detection time long enough to catch worst case time lag
1055 // between duplicating events, but considerably shorter than the nominal
1056 // "intentional double-click" time interval defined generally as 350 msec.
1057 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1058 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1059 // ignore it.
1060 return;
1061 }
1062#endif
1063
1064 // printf("Left_DOWN\n");
1065
1066 // detect and manage double-tap
1067#ifdef __WXGTK__
1068 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1069 2; // Use system setting for distance
1070 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1071 m_lastTapPos.y - max_double_click_distance,
1072 max_double_click_distance * 2, max_double_click_distance * 2);
1073
1074 // A new tap has started, check if it's close enough and in time
1075 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1076 // printf(" TapBump 1\n");
1077 m_tap_count += 1;
1078 } else {
1079 // printf(" TapSet 1\n");
1080 m_tap_count = 1;
1081 m_lastTapPos = event.GetPosition();
1082 m_tap_timer.StartOnce(
1083 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1084 }
1085
1086 if (m_tap_count == 2) {
1087 // printf(" Doubletap detected\n");
1088 m_tap_count = 0; // Reset after a double-tap
1089
1090 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1091 ev.m_x = event.m_x;
1092 ev.m_y = event.m_y;
1093 // wxPostEvent(this, ev);
1094 MouseEvent(ev);
1095 return;
1096 }
1097
1098#endif
1099
1100 MouseEvent(event);
1101}
1102
1103void ChartCanvas::OnMotion(wxMouseEvent &event) {
1104 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1105 dragging, upon simple click, and without the OnLeftDown event before Thus,
1106 this consists in skiping it, and setting the leftdown bit according to a
1107 status that we trust */
1108 event.m_leftDown = m_leftdown;
1109 MouseEvent(event);
1110}
1111
1112void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1113 /* there are spurious end zoom events upon right-click */
1114 if (event.IsGestureEnd()) return;
1115
1116 double factor = event.GetZoomFactor();
1117
1118 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1119 m_oldVPSScale = GetVPScale();
1120 }
1121
1122 double current_vps = GetVPScale();
1123 double wanted_factor = m_oldVPSScale / current_vps * factor;
1124
1125 ZoomCanvas(wanted_factor, true, false);
1126
1127 // Allow combined zoom/pan operation
1128 if (event.IsGestureStart()) {
1129 m_zoomStartPoint = event.GetPosition();
1130 } else {
1131 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1132 PanCanvas(-delta.x, -delta.y);
1133 m_zoomStartPoint = event.GetPosition();
1134 }
1135}
1136
1137void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1138
1139void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1140 DoRotateCanvas(0.0);
1141}
1142#endif /* HAVE_WX_GESTURE_EVENTS */
1143
1144void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1145 // printf("tap timer %d\n", m_tap_count);
1146 m_tap_count = 0;
1147}
1148
1149void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1150 m_FinishRouteOnKillFocus = false;
1151 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1152 m_FinishRouteOnKillFocus = true;
1153}
1154
1155void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1156 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1157 m_vLat = pcc->iLat;
1158 m_vLon = pcc->iLon;
1159
1160 m_restore_dbindex = pcc->DBindex;
1161 m_bFollow = pcc->bFollow;
1162 if (pcc->GroupID < 0) pcc->GroupID = 0;
1163
1164 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1165 m_groupIndex = 0;
1166 else
1167 m_groupIndex = pcc->GroupID;
1168
1169 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1170
1171 ShowTides(pcc->bShowTides);
1172 ShowCurrents(pcc->bShowCurrents);
1173
1174 SetShowDepthUnits(pcc->bShowDepthUnits);
1175 SetShowGrid(pcc->bShowGrid);
1176 SetShowOutlines(pcc->bShowOutlines);
1177
1178 SetShowAIS(pcc->bShowAIS);
1179 SetAttenAIS(pcc->bAttenAIS);
1180
1181 // ENC options
1182 SetShowENCText(pcc->bShowENCText);
1183 m_encDisplayCategory = pcc->nENCDisplayCategory;
1184 m_encShowDepth = pcc->bShowENCDepths;
1185 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1186 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1187 m_encShowLights = pcc->bShowENCLights;
1188 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1189 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1190 m_encShowDataQual = pcc->bShowENCDataQuality;
1191
1192 bool courseUp = pcc->bCourseUp;
1193 bool headUp = pcc->bHeadUp;
1194 m_upMode = NORTH_UP_MODE;
1195 if (courseUp)
1196 m_upMode = COURSE_UP_MODE;
1197 else if (headUp)
1198 m_upMode = HEAD_UP_MODE;
1199
1200 m_bLookAhead = pcc->bLookahead;
1201
1202 m_singleChart = NULL;
1203}
1204
1205void ChartCanvas::ApplyGlobalSettings() {
1206 // GPS compas window
1207 if (m_Compass) {
1208 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1209 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1210 }
1211 if (m_notification_button) m_notification_button->UpdateStatus();
1212}
1213
1214void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1215 bool groupOK = CheckGroup(m_groupIndex);
1216
1217 if (!groupOK) {
1218 SetGroupIndex(m_groupIndex, true);
1219 }
1220}
1221
1222void ChartCanvas::SetShowGPS(bool bshow) {
1223 if (m_bShowGPS != bshow) {
1224 delete m_Compass;
1225 m_Compass = new ocpnCompass(this, bshow);
1226 m_Compass->SetScaleFactor(g_compass_scalefactor);
1227 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1228 }
1229 m_bShowGPS = bshow;
1230}
1231
1232void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1233 m_bShowCompassWin = bshow;
1234 if (m_Compass) {
1235 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1236 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1237 }
1238}
1239
1240int ChartCanvas::GetPianoHeight() {
1241 int height = 0;
1242 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1243
1244 return height;
1245}
1246
1247void ChartCanvas::ConfigureChartBar() {
1248 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1249
1250 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1251 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1252
1253 if (GetQuiltMode()) {
1254 m_Piano->SetRoundedRectangles(true);
1255 }
1256 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1257 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1258 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1259}
1260
1261void ChartCanvas::ShowTides(bool bShow) {
1262 gFrame->LoadHarmonics();
1263
1264 if (ptcmgr->IsReady()) {
1265 SetbShowTide(bShow);
1266
1267 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1268 } else {
1269 wxLogMessage("Chart1::Event...TCMgr Not Available");
1270 SetbShowTide(false);
1271 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1272 }
1273
1274 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1275 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1276
1277 // TODO
1278 // if( GetbShowTide() ) {
1279 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1280 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1281 // update
1282 // } else
1283 // FrameTCTimer.Stop();
1284}
1285
1286void ChartCanvas::ShowCurrents(bool bShow) {
1287 gFrame->LoadHarmonics();
1288
1289 if (ptcmgr->IsReady()) {
1290 SetbShowCurrent(bShow);
1291 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1292 } else {
1293 wxLogMessage("Chart1::Event...TCMgr Not Available");
1294 SetbShowCurrent(false);
1295 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1296 }
1297
1298 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1299 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1300
1301 // TODO
1302 // if( GetbShowCurrent() ) {
1303 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1304 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1305 // update
1306 // } else
1307 // FrameTCTimer.Stop();
1308}
1309
1310// TODO
1311static ChartDummy *pDummyChart;
1312
1315
1316void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1317
1318void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1319 SetAlertString("");
1320
1321 int new_index = index;
1322 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1323
1324 bool bgroup_override = false;
1325 int old_group_index = new_index;
1326
1327 if (!CheckGroup(new_index)) {
1328 new_index = 0;
1329 bgroup_override = true;
1330 }
1331
1332 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1333 new_index = index;
1334
1335 // Get the currently displayed chart native scale, and the current ViewPort
1336 int current_chart_native_scale = GetCanvasChartNativeScale();
1337 ViewPort vp = GetVP();
1338
1339 m_groupIndex = new_index;
1340
1341 // Are there ENCs in this group
1342 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1343
1344 // Update the MUIBar for ENC availability
1345 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1346
1347 // Allow the chart database to pre-calculate the MBTile inclusion test
1348 // boolean...
1349 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1350
1351 // Invalidate the "sticky" chart on group change, since it might not be in
1352 // the new group
1353 g_sticky_chart = -1;
1354
1355 // We need a chartstack and quilt to figure out which chart to open in the
1356 // new group
1357 UpdateCanvasOnGroupChange();
1358
1359 int dbi_now = -1;
1360 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1361
1362 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1363
1364 // If a new reference chart is indicated, set a good scale for it.
1365 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1366 double best_scale = GetBestStartScale(dbi_hint, vp);
1367 SetVPScale(best_scale);
1368 }
1369
1370 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1371
1372 // Refresh the canvas, selecting the "best" chart,
1373 // applying the prior ViewPort exactly
1374 canvasChartsRefresh(dbi_hint);
1375
1376 UpdateCanvasControlBar();
1377
1378 if (!autoSwitch && bgroup_override) {
1379 // show a short timed message box
1380 wxString msg(_("Group \""));
1381
1382 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1383 msg += pGroup->m_group_name;
1384
1385 msg += _("\" is empty.");
1386
1387 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1388
1389 return;
1390 }
1391
1392 // Message box is deferred so that canvas refresh occurs properly before
1393 // dialog
1394 if (bgroup_override) {
1395 wxString msg(_("Group \""));
1396
1397 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1398 msg += pGroup->m_group_name;
1399
1400 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1401
1402 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1403 }
1404}
1405
1406bool ChartCanvas::CheckGroup(int igroup) {
1407 if (!ChartData) return true; // Not known yet...
1408
1409 if (igroup == 0) return true; // "all charts" is always OK
1410
1411 if (igroup < 0) // negative group is an error
1412 return false;
1413
1414 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1415
1416 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1417 // and auto-shift to group 0
1418 return false;
1419
1420 for (const auto &elem : pGroup->m_element_array) {
1421 for (unsigned int ic = 0;
1422 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1423 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1424 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1425
1426 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1427 }
1428 }
1429
1430 // If necessary, check for GSHHS
1431 for (const auto &elem : pGroup->m_element_array) {
1432 const wxString &element_root = elem.m_element_name;
1433 wxString test_string = "GSHH";
1434 if (element_root.Upper().Contains(test_string)) return true;
1435 }
1436
1437 return false;
1438}
1439
1440void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1441 if (!ChartData) return;
1442
1443 AbstractPlatform::ShowBusySpinner();
1444
1445 double old_scale = GetVPScale();
1446 InvalidateQuilt();
1447 SetQuiltRefChart(-1);
1448
1449 m_singleChart = NULL;
1450
1451 // delete m_pCurrentStack;
1452 // m_pCurrentStack = NULL;
1453
1454 // Build a new ChartStack
1455 if (!m_pCurrentStack) {
1456 m_pCurrentStack = new ChartStack;
1457 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1458 }
1459
1460 if (-1 != dbi_hint) {
1461 if (GetQuiltMode()) {
1462 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1463 SetQuiltRefChart(dbi_hint);
1464 } else {
1465 // Open the saved chart
1466 ChartBase *pTentative_Chart;
1467 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1468
1469 if (pTentative_Chart) {
1470 /* m_singleChart is always NULL here, (set above) should this go before
1471 * that? */
1472 if (m_singleChart) m_singleChart->Deactivate();
1473
1474 m_singleChart = pTentative_Chart;
1475 m_singleChart->Activate();
1476
1477 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1478 GetpCurrentStack(), m_singleChart->GetFullPath());
1479 }
1480 }
1481
1482 // refresh_Piano();
1483 } else {
1484 // Select reference chart from the stack, as though clicked by user
1485 // Make it the smallest scale chart on the stack
1486 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1487 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1488 SetQuiltRefChart(selected_index);
1489 }
1490
1491 // Validate the correct single chart, or set the quilt mode as appropriate
1492 SetupCanvasQuiltMode();
1493 if (!GetQuiltMode() && m_singleChart == 0) {
1494 // use a dummy like in DoChartUpdate
1495 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1496 m_singleChart = pDummyChart;
1497 SetVPScale(old_scale);
1498 }
1499
1500 ReloadVP();
1501
1502 UpdateCanvasControlBar();
1503 UpdateGPSCompassStatusBox(true);
1504
1505 SetCursor(wxCURSOR_ARROW);
1506
1507 AbstractPlatform::HideBusySpinner();
1508}
1509
1510bool ChartCanvas::DoCanvasUpdate() {
1511 double tLat, tLon; // Chart Stack location
1512 double vpLat, vpLon; // ViewPort location
1513 bool blong_jump = false;
1514 meters_to_shift = 0;
1515 dir_to_shift = 0;
1516
1517 bool bNewChart = false;
1518 bool bNewView = false;
1519 bool bCanvasChartAutoOpen = true; // debugging
1520
1521 bool bNewPiano = false;
1522 bool bOpenSpecified;
1523 ChartStack LastStack;
1524 ChartBase *pLast_Ch;
1525
1526 ChartStack WorkStack;
1527
1528 if (bDBUpdateInProgress) return false;
1529 if (!ChartData) return false;
1530
1531 if (ChartData->IsBusy()) return false;
1532 if (m_chart_drag_inertia_active) return false;
1533
1534 // Startup case:
1535 // Quilting is enabled, but the last chart seen was not quiltable
1536 // In this case, drop to single chart mode, set persistence flag,
1537 // And open the specified chart
1538 // TODO implement this
1539 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1540 // if( GetQuiltMode() ) {
1541 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1542 // gFrame->ToggleQuiltMode();
1543 // m_bpersistent_quilt = true;
1544 // m_singleChart = NULL;
1545 // }
1546 // }
1547 // }
1548
1549 // If in auto-follow mode, use the current glat,glon to build chart
1550 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1551 // other means
1552
1553 if (m_bFollow) {
1554 tLat = gLat;
1555 tLon = gLon;
1556
1557 // Set the ViewPort center based on the OWNSHIP offset
1558 double dx = m_OSoffsetx;
1559 double dy = m_OSoffsety;
1560 double d_east = dx / GetVP().view_scale_ppm;
1561 double d_north = dy / GetVP().view_scale_ppm;
1562
1563 if (GetUpMode() == NORTH_UP_MODE) {
1564 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1565 } else {
1566 double offset_angle = atan2(d_north, d_east);
1567 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1568 double chart_angle = GetVPRotation();
1569 double target_angle = chart_angle + offset_angle;
1570 double d_east_mod = offset_distance * cos(target_angle);
1571 double d_north_mod = offset_distance * sin(target_angle);
1572 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1573 }
1574
1575 // on lookahead mode, adjust the vp center point
1576 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1577 double cog_to_use = gCog;
1578 if (g_btenhertz &&
1579 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1580 cog_to_use = gCog_gt;
1581 blong_jump = true;
1582 }
1583 if (!g_btenhertz) cog_to_use = g_COGAvg;
1584
1585 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1586
1587 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1588 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1589
1590 double pixel_delta_tent =
1591 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1592
1593 double pixel_delta = 0;
1594
1595 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1596 // avoid jumping of the vp center point during slow maneuvering, or at
1597 // anchor....
1598 if (!std::isnan(gSog)) {
1599 if (gSog < 2.0)
1600 pixel_delta = 0.;
1601 else
1602 pixel_delta = pixel_delta_tent;
1603 }
1604
1605 meters_to_shift = 0;
1606 dir_to_shift = 0;
1607 if (!std::isnan(gCog)) {
1608 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1609 dir_to_shift = cog_to_use;
1610 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1611 &vpLon);
1612 } else {
1613 vpLat = gLat;
1614 vpLon = gLon;
1615 }
1616 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1617 m_OSoffsetx = 0; // center ownship on loss of GPS
1618 m_OSoffsety = 0;
1619 vpLat = gLat;
1620 vpLon = gLon;
1621 }
1622
1623 } else {
1624 tLat = m_vLat;
1625 tLon = m_vLon;
1626 vpLat = m_vLat;
1627 vpLon = m_vLon;
1628 }
1629
1630 if (GetQuiltMode()) {
1631 int current_db_index = -1;
1632 if (m_pCurrentStack)
1633 current_db_index =
1634 m_pCurrentStack
1635 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1636 // chart dbIndex
1637 else
1638 m_pCurrentStack = new ChartStack;
1639
1640 // This logic added to enable opening a chart when there is no
1641 // previous chart indication, either from inital startup, or from adding
1642 // new chart directory
1643 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1644 m_pCurrentStack) {
1645 if (m_pCurrentStack->nEntry) {
1646 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1647 1); // smallest scale
1648 SelectQuiltRefdbChart(new_dbIndex, true);
1649 m_bautofind = false;
1650 }
1651 }
1652
1653 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1654 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1655
1656 if (m_bFirstAuto) {
1657 // Allow the chart database to pre-calculate the MBTile inclusion test
1658 // boolean...
1659 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1660
1661 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1662 // physical pixels. On standard DPI displays where logical = physical
1663 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1664 // logical pixels, this ratio would be 0.5.
1665 double proposed_scale_onscreen =
1666 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1667
1668 int initial_db_index = m_restore_dbindex;
1669 if (initial_db_index < 0) {
1670 if (m_pCurrentStack->nEntry) {
1671 initial_db_index =
1672 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1673 } else
1674 m_bautofind = true; // initial_db_index = 0;
1675 }
1676
1677 if (m_pCurrentStack->nEntry) {
1678 int initial_type = ChartData->GetDBChartType(initial_db_index);
1679
1680 // Check to see if the target new chart is quiltable as a reference
1681 // chart
1682
1683 if (!IsChartQuiltableRef(initial_db_index)) {
1684 // If it is not quiltable, then walk the stack up looking for a
1685 // satisfactory chart i.e. one that is quiltable and of the same type
1686 // XXX if there's none?
1687 int stack_index = 0;
1688
1689 if (stack_index >= 0) {
1690 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1691 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1692 if (IsChartQuiltableRef(test_db_index) &&
1693 (initial_type ==
1694 ChartData->GetDBChartType(initial_db_index))) {
1695 initial_db_index = test_db_index;
1696 break;
1697 }
1698 stack_index++;
1699 }
1700 }
1701 }
1702
1703 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1704 if (pc) {
1705 SetQuiltRefChart(initial_db_index);
1706 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1707 }
1708 }
1709 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1710 // just GetVPScale(), so I'm not sure why it's necessary to define the
1711 // proposed_scale_onscreen variable.
1712 bNewView |= SetViewPoint(vpLat, vpLon,
1713 GetCanvasScaleFactor() / proposed_scale_onscreen,
1714 0, GetVPRotation());
1715 }
1716 // Measure rough jump distance if in bfollow mode
1717 // No good reason to do smooth pan for
1718 // jump distance more than one screen width at scale.
1719 bool super_jump = false;
1720 if (m_bFollow) {
1721 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1722 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1723 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1724 }
1725#if 0
1726 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1727 int nstep = 5;
1728 if (blong_jump) nstep = 20;
1729 StartTimedMovementVP(vpLat, vpLon, nstep);
1730 } else
1731#endif
1732 {
1733 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1734 }
1735
1736 goto update_finish;
1737 }
1738
1739 // Single Chart Mode from here....
1740 pLast_Ch = m_singleChart;
1741 ChartTypeEnum new_open_type;
1742 ChartFamilyEnum new_open_family;
1743 if (pLast_Ch) {
1744 new_open_type = pLast_Ch->GetChartType();
1745 new_open_family = pLast_Ch->GetChartFamily();
1746 } else {
1747 new_open_type = CHART_TYPE_KAP;
1748 new_open_family = CHART_FAMILY_RASTER;
1749 }
1750
1751 bOpenSpecified = m_bFirstAuto;
1752
1753 // Make sure the target stack is valid
1754 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1755
1756 // Build a chart stack based on tLat, tLon
1757 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1758 m_groupIndex)) { // Bogus Lat, Lon?
1759 if (NULL == pDummyChart) {
1760 pDummyChart = new ChartDummy;
1761 bNewChart = true;
1762 }
1763
1764 if (m_singleChart)
1765 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1766
1767 m_singleChart = pDummyChart;
1768
1769 // If the current viewpoint is invalid, set the default scale to
1770 // something reasonable.
1771 double set_scale = GetVPScale();
1772 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1773
1774 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1775
1776 // If the chart stack has just changed, there is new status
1777 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1778 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1779 bNewPiano = true;
1780 bNewChart = true;
1781 }
1782 }
1783
1784 // Copy the new (by definition empty) stack into the target stack
1785 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1786
1787 goto update_finish;
1788 }
1789
1790 // Check to see if Chart Stack has changed
1791 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1792 // New chart stack, so...
1793 bNewPiano = true;
1794
1795 // Save a copy of the current stack
1796 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1797
1798 // Copy the new stack into the target stack
1799 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1800
1801 // Is Current Chart in new stack?
1802
1803 int tEntry = -1;
1804 if (NULL != m_singleChart) // this handles startup case
1805 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1806 m_singleChart->GetFullPath());
1807
1808 if (tEntry != -1) { // m_singleChart is in the new stack
1809 m_pCurrentStack->CurrentStackEntry = tEntry;
1810 bNewChart = false;
1811 }
1812
1813 else // m_singleChart is NOT in new stack
1814 { // So, need to open a new chart
1815 // Find the largest scale raster chart that opens OK
1816
1817 ChartBase *pProposed = NULL;
1818
1819 if (bCanvasChartAutoOpen) {
1820 bool search_direction =
1821 false; // default is to search from lowest to highest
1822 int start_index = 0;
1823
1824 // A special case: If panning at high scale, open largest scale
1825 // chart first
1826 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1827 (LastStack.nEntry == 0)) {
1828 search_direction = true;
1829 start_index = m_pCurrentStack->nEntry - 1;
1830 }
1831
1832 // Another special case, open specified index on program start
1833 if (bOpenSpecified) {
1834 search_direction = false;
1835 start_index = 0;
1836 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1837 start_index = 0;
1838
1839 new_open_type = CHART_TYPE_DONTCARE;
1840 }
1841
1842 pProposed = ChartData->OpenStackChartConditional(
1843 m_pCurrentStack, start_index, search_direction, new_open_type,
1844 new_open_family);
1845
1846 // Try to open other types/families of chart in some priority
1847 if (NULL == pProposed)
1848 pProposed = ChartData->OpenStackChartConditional(
1849 m_pCurrentStack, start_index, search_direction,
1850 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1851
1852 if (NULL == pProposed)
1853 pProposed = ChartData->OpenStackChartConditional(
1854 m_pCurrentStack, start_index, search_direction,
1855 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1856
1857 bNewChart = true;
1858
1859 } // bCanvasChartAutoOpen
1860
1861 else
1862 pProposed = NULL;
1863
1864 // If no go, then
1865 // Open a Dummy Chart
1866 if (NULL == pProposed) {
1867 if (NULL == pDummyChart) {
1868 pDummyChart = new ChartDummy;
1869 bNewChart = true;
1870 }
1871
1872 if (pLast_Ch)
1873 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1874
1875 pProposed = pDummyChart;
1876 }
1877
1878 // Arriving here, pProposed points to an opened chart, or NULL.
1879 if (m_singleChart) m_singleChart->Deactivate();
1880 m_singleChart = pProposed;
1881
1882 if (m_singleChart) {
1883 m_singleChart->Activate();
1884 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1885 m_pCurrentStack, m_singleChart->GetFullPath());
1886 }
1887 } // need new chart
1888
1889 // Arriving here, m_singleChart is opened and OK, or NULL
1890 if (NULL != m_singleChart) {
1891 // Setup the view using the current scale
1892 double set_scale = GetVPScale();
1893
1894 // If the current viewpoint is invalid, set the default scale to
1895 // something reasonable.
1896 if (!GetVP().IsValid())
1897 set_scale = 1. / 20000.;
1898 else { // otherwise, match scale if elected.
1899 double proposed_scale_onscreen;
1900
1901 if (m_bFollow) { // autoset the scale only if in autofollow
1902 double new_scale_ppm =
1903 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1904 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1905 } else
1906 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1907
1908 // This logic will bring a new chart onscreen at roughly twice the true
1909 // paper scale equivalent. Note that first chart opened on application
1910 // startup (bOpenSpecified = true) will open at the config saved scale
1911 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1912 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1913 double equivalent_vp_scale =
1914 GetCanvasScaleFactor() / proposed_scale_onscreen;
1915 double new_scale_ppm =
1916 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1917 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1918 }
1919
1920 if (m_bFollow) { // bounds-check the scale only if in autofollow
1921 proposed_scale_onscreen =
1922 wxMin(proposed_scale_onscreen,
1923 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1924 GetCanvasWidth()));
1925 proposed_scale_onscreen =
1926 wxMax(proposed_scale_onscreen,
1927 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1929 }
1930
1931 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1932 }
1933
1934 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1935 m_singleChart->GetChartSkew() * PI / 180.,
1936 GetVPRotation());
1937 }
1938 } // new stack
1939
1940 else // No change in Chart Stack
1941 {
1942 if ((m_bFollow) && m_singleChart)
1943 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1944 m_singleChart->GetChartSkew() * PI / 180.,
1945 GetVPRotation());
1946 }
1947
1948update_finish:
1949
1950 // TODO
1951 // if( bNewPiano ) UpdateControlBar();
1952
1953 m_bFirstAuto = false; // Auto open on program start
1954
1955 // If we need a Refresh(), do it here...
1956 // But don't duplicate a Refresh() done by SetViewPoint()
1957 if (bNewChart && !bNewView) Refresh(false);
1958
1959#ifdef ocpnUSE_GL
1960 // If a new chart, need to invalidate gl viewport for refresh
1961 // so the fbo gets flushed
1962 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1963#endif
1964
1965 return bNewChart | bNewView;
1966}
1967
1968void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1969 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1970
1971 SetQuiltRefChart(db_index);
1972 if (ChartData) {
1973 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1974 if (pc) {
1975 if (b_autoscale) {
1976 double best_scale_ppm = GetBestVPScale(pc);
1977 SetVPScale(best_scale_ppm);
1978 }
1979 } else
1980 SetQuiltRefChart(-1);
1981 } else
1982 SetQuiltRefChart(-1);
1983}
1984
1985void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1986 std::vector<int> piano_chart_index_array =
1987 GetQuiltExtendedStackdbIndexArray();
1988 int current_db_index = piano_chart_index_array[selected_index];
1989
1990 SelectQuiltRefdbChart(current_db_index);
1991}
1992
1993double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1994 if (pchart) {
1995 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1996
1997 if ((g_bPreserveScaleOnX) ||
1998 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1999 double new_scale_ppm = GetVPScale();
2000 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2001 } else {
2002 // This logic will bring the new chart onscreen at roughly twice the true
2003 // paper scale equivalent.
2004 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2005 double equivalent_vp_scale =
2006 GetCanvasScaleFactor() / proposed_scale_onscreen;
2007 double new_scale_ppm =
2008 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2009 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2010 }
2011
2012 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2013 // set. Otherwise, we get severe performance problems on all platforms
2014
2015 double max_underzoom_multiplier = 2.0;
2016 if (GetVP().b_quilt) {
2017 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2018 pchart->GetChartType(),
2019 pchart->GetChartFamily());
2020 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2021 }
2022
2023 proposed_scale_onscreen = wxMin(
2024 proposed_scale_onscreen,
2025 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2026 max_underzoom_multiplier);
2027
2028 // And, do not allow excessive overzoom either
2029 proposed_scale_onscreen =
2030 wxMax(proposed_scale_onscreen,
2031 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2032
2033 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2034 } else
2035 return 1.0;
2036}
2037
2038void ChartCanvas::SetupCanvasQuiltMode() {
2039 if (GetQuiltMode()) // going to quilt mode
2040 {
2041 ChartData->LockCache();
2042
2043 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2044
2045 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2046
2047 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2048 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2049 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2050 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2051
2052 m_Piano->SetRoundedRectangles(true);
2053
2054 // Select the proper Ref chart
2055 int target_new_dbindex = -1;
2056 if (m_pCurrentStack) {
2057 target_new_dbindex =
2058 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2059
2060 if (-1 != target_new_dbindex) {
2061 if (!IsChartQuiltableRef(target_new_dbindex)) {
2062 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2063 int type = ChartData->GetDBChartType(target_new_dbindex);
2064
2065 // walk the stack up looking for a satisfactory chart
2066 int stack_index = m_pCurrentStack->CurrentStackEntry;
2067
2068 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2069 (stack_index >= 0)) {
2070 int proj_tent = ChartData->GetDBChartProj(
2071 m_pCurrentStack->GetDBIndex(stack_index));
2072 int type_tent = ChartData->GetDBChartType(
2073 m_pCurrentStack->GetDBIndex(stack_index));
2074
2075 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2076 if ((proj == proj_tent) && (type_tent == type)) {
2077 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2078 break;
2079 }
2080 }
2081 stack_index++;
2082 }
2083 }
2084 }
2085 }
2086
2087 if (IsChartQuiltableRef(target_new_dbindex))
2088 SelectQuiltRefdbChart(target_new_dbindex,
2089 false); // Try not to allow a scale change
2090 else
2091 SelectQuiltRefdbChart(-1, false);
2092
2093 m_singleChart = NULL; // Bye....
2094
2095 // Re-qualify the quilt reference chart selection
2096 AdjustQuiltRefChart();
2097
2098 // Restore projection type saved on last quilt mode toggle
2099 // TODO
2100 // if(g_sticky_projection != -1)
2101 // GetVP().SetProjectionType(g_sticky_projection);
2102 // else
2103 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2104 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2105
2106 } else // going to SC Mode
2107 {
2108 std::vector<int> empty_array;
2109 m_Piano->SetActiveKeyArray(empty_array);
2110 m_Piano->SetNoshowIndexArray(empty_array);
2111 m_Piano->SetEclipsedIndexArray(empty_array);
2112
2113 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2114 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2115 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2116 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2117 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2118
2119 m_Piano->SetRoundedRectangles(false);
2120 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2121 }
2122
2123 // When shifting from quilt to single chart mode, select the "best" single
2124 // chart to show
2125 if (!GetQuiltMode()) {
2126 if (ChartData && ChartData->IsValid()) {
2127 UnlockQuilt();
2128
2129 double tLat, tLon;
2130 if (m_bFollow == true) {
2131 tLat = gLat;
2132 tLon = gLon;
2133 } else {
2134 tLat = m_vLat;
2135 tLon = m_vLon;
2136 }
2137
2138 if (!m_singleChart) {
2139 // Build a temporary chart stack based on tLat, tLon
2140 ChartStack TempStack;
2141 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2142 m_groupIndex);
2143
2144 // Iterate over the quilt charts actually shown, looking for the
2145 // largest scale chart that will be in the new chartstack.... This
2146 // will (almost?) always be the reference chart....
2147
2148 ChartBase *Candidate_Chart = NULL;
2149 int cur_max_scale = (int)1e8;
2150
2151 ChartBase *pChart = GetFirstQuiltChart();
2152 while (pChart) {
2153 // Is this pChart in new stack?
2154 int tEntry =
2155 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2156 if (tEntry != -1) {
2157 if (pChart->GetNativeScale() < cur_max_scale) {
2158 Candidate_Chart = pChart;
2159 cur_max_scale = pChart->GetNativeScale();
2160 }
2161 }
2162 pChart = GetNextQuiltChart();
2163 }
2164
2165 m_singleChart = Candidate_Chart;
2166
2167 // If the quilt is empty, there is no "best" chart.
2168 // So, open the smallest scale chart in the current stack
2169 if (NULL == m_singleChart) {
2170 m_singleChart = ChartData->OpenStackChartConditional(
2171 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2172 CHART_FAMILY_DONTCARE);
2173 }
2174 }
2175
2176 // Invalidate all the charts in the quilt,
2177 // as any cached data may be region based and not have fullscreen coverage
2178 InvalidateAllQuiltPatchs();
2179
2180 if (m_singleChart) {
2181 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2182 std::vector<int> one_array;
2183 one_array.push_back(dbi);
2184 m_Piano->SetActiveKeyArray(one_array);
2185 }
2186
2187 if (m_singleChart) {
2188 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2189 }
2190 }
2191 // Invalidate the current stack so that it will be rebuilt on next tick
2192 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2193 }
2194}
2195
2196bool ChartCanvas::IsTempMenuBarEnabled() {
2197#ifdef __WXMSW__
2198 int major;
2199 wxGetOsVersion(&major);
2200 return (major >
2201 5); // For Windows, function is only available on Vista and above
2202#else
2203 return true;
2204#endif
2205}
2206
2207double ChartCanvas::GetCanvasRangeMeters() {
2208 int width, height;
2209 GetSize(&width, &height);
2210 int minDimension = wxMin(width, height);
2211
2212 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2213 range *= cos(GetVP().clat * PI / 180.);
2214 return range;
2215}
2216
2217void ChartCanvas::SetCanvasRangeMeters(double range) {
2218 int width, height;
2219 GetSize(&width, &height);
2220 int minDimension = wxMin(width, height);
2221
2222 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2223 SetVPScale(scale_ppm / 2);
2224}
2225
2226bool ChartCanvas::SetUserOwnship() {
2227 // Look for user defined ownship image
2228 // This may be found in the shared data location along with other user
2229 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2230 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2231 double factor_dusk = 0.5;
2232 double factor_night = 0.25;
2233
2234 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2235 m_pos_image_user_day = new wxImage;
2236 *m_pos_image_user_day = pbmp->ConvertToImage();
2237 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2238
2239 int gimg_width = m_pos_image_user_day->GetWidth();
2240 int gimg_height = m_pos_image_user_day->GetHeight();
2241
2242 // Make dusk and night images
2243 m_pos_image_user_dusk = new wxImage;
2244 m_pos_image_user_night = new wxImage;
2245
2246 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2247 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2248
2249 for (int iy = 0; iy < gimg_height; iy++) {
2250 for (int ix = 0; ix < gimg_width; ix++) {
2251 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2252 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2253 m_pos_image_user_day->GetGreen(ix, iy),
2254 m_pos_image_user_day->GetBlue(ix, iy));
2255 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2256 hsv.value = hsv.value * factor_dusk;
2257 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2258 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2259 nrgb.blue);
2260
2261 hsv = wxImage::RGBtoHSV(rgb);
2262 hsv.value = hsv.value * factor_night;
2263 nrgb = wxImage::HSVtoRGB(hsv);
2264 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2265 nrgb.blue);
2266 }
2267 }
2268 }
2269
2270 // Make some alternate greyed out day/dusk/night images
2271 m_pos_image_user_grey_day = new wxImage;
2272 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2273
2274 m_pos_image_user_grey_dusk = new wxImage;
2275 m_pos_image_user_grey_night = new wxImage;
2276
2277 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2278 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2279
2280 for (int iy = 0; iy < gimg_height; iy++) {
2281 for (int ix = 0; ix < gimg_width; ix++) {
2282 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2283 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2284 m_pos_image_user_grey_day->GetGreen(ix, iy),
2285 m_pos_image_user_grey_day->GetBlue(ix, iy));
2286 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2287 hsv.value = hsv.value * factor_dusk;
2288 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2289 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2290 nrgb.blue);
2291
2292 hsv = wxImage::RGBtoHSV(rgb);
2293 hsv.value = hsv.value * factor_night;
2294 nrgb = wxImage::HSVtoRGB(hsv);
2295 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2296 nrgb.blue);
2297 }
2298 }
2299 }
2300
2301 // Make a yellow image for rendering under low accuracy chart conditions
2302 m_pos_image_user_yellow_day = new wxImage;
2303 m_pos_image_user_yellow_dusk = new wxImage;
2304 m_pos_image_user_yellow_night = new wxImage;
2305
2306 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2307 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2308 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2309
2310 for (int iy = 0; iy < gimg_height; iy++) {
2311 for (int ix = 0; ix < gimg_width; ix++) {
2312 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2313 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2314 m_pos_image_user_grey_day->GetGreen(ix, iy),
2315 m_pos_image_user_grey_day->GetBlue(ix, iy));
2316
2317 // Simply remove all "blue" from the greyscaled image...
2318 // so, what is not black becomes yellow.
2319 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2320 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2321 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2322
2323 hsv = wxImage::RGBtoHSV(rgb);
2324 hsv.value = hsv.value * factor_dusk;
2325 nrgb = wxImage::HSVtoRGB(hsv);
2326 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2327
2328 hsv = wxImage::RGBtoHSV(rgb);
2329 hsv.value = hsv.value * factor_night;
2330 nrgb = wxImage::HSVtoRGB(hsv);
2331 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2332 0);
2333 }
2334 }
2335 }
2336
2337 return true;
2338 } else
2339 return false;
2340}
2341
2343 m_display_size_mm = size;
2344
2345 // int sx, sy;
2346 // wxDisplaySize( &sx, &sy );
2347
2348 // Calculate logical pixels per mm for later reference.
2349 wxSize sd = g_Platform->getDisplaySize();
2350 double horizontal = sd.x;
2351 // Set DPI (Win) scale factor
2352 g_scaler = g_Platform->GetDisplayDIPMult(this);
2353
2354 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2355 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2356
2357 if (ps52plib) {
2358 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2359 ps52plib->SetPPMM(m_pix_per_mm);
2360 }
2361
2362 wxString msg;
2363 msg.Printf(
2364 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2365 "%d:%d ",
2366 m_display_size_mm, sd.x, sd.y);
2367 wxLogDebug(msg);
2368
2369 int ssx, ssy;
2370 ssx = g_monitor_info[g_current_monitor].width;
2371 ssy = g_monitor_info[g_current_monitor].height;
2372 msg.Printf("monitor size: %d %d", ssx, ssy);
2373 wxLogDebug(msg);
2374
2375 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2376}
2377#if 0
2378void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2379{
2380 wxString msg(event.m_string.c_str(), wxConvUTF8);
2381 // if cpus are removed between runs
2382 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2383 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2384 }
2385
2386 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2387 {
2388 compress_msg_array.RemoveAt(event.thread);
2389 compress_msg_array.Insert( msg, event.thread);
2390 }
2391 else
2392 compress_msg_array.Add(msg);
2393
2394
2395 wxString combined_msg;
2396 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2397 combined_msg += compress_msg_array[i];
2398 combined_msg += "\n";
2399 }
2400
2401 bool skip = false;
2402 pprog->Update(pprog_count, combined_msg, &skip );
2403 pprog->SetSize(pprog_size);
2404 if(skip)
2405 b_skipout = skip;
2406}
2407#endif
2408void ChartCanvas::InvalidateGL() {
2409 if (!m_glcc) return;
2410#ifdef ocpnUSE_GL
2411 if (g_bopengl) m_glcc->Invalidate();
2412#endif
2413 if (m_Compass) m_Compass->UpdateStatus(true);
2414}
2415
2416int ChartCanvas::GetCanvasChartNativeScale() {
2417 int ret = 1;
2418 if (!VPoint.b_quilt) {
2419 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2420 } else
2421 ret = (int)m_pQuilt->GetRefNativeScale();
2422
2423 return ret;
2424}
2425
2426ChartBase *ChartCanvas::GetChartAtCursor() {
2427 ChartBase *target_chart;
2428 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2429 target_chart = m_singleChart;
2430 else if (VPoint.b_quilt)
2431 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2432 else
2433 target_chart = NULL;
2434 return target_chart;
2435}
2436
2437ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2438 ChartBase *target_chart;
2439 if (VPoint.b_quilt)
2440 target_chart =
2441 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2442 else
2443 target_chart = NULL;
2444 return target_chart;
2445}
2446
2447int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2448 int new_dbIndex = -1;
2449 if (!VPoint.b_quilt) {
2450 if (m_pCurrentStack) {
2451 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2452 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2453 if (sc >= scale) {
2454 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2455 break;
2456 }
2457 }
2458 }
2459 } else {
2460 // Using the current quilt, select a useable reference chart
2461 // Said chart will be in the extended (possibly full-screen) stack,
2462 // And will have a scale equal to or just greater than the stipulated
2463 // value
2464 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2465 if (im > 0) {
2466 for (unsigned int is = 0; is < im; is++) {
2467 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2468 m_pQuilt->GetExtendedStackIndexArray()[is]);
2469 if ((m.Scale_ge(
2470 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2471 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2472 break;
2473 }
2474 }
2475 }
2476 }
2477
2478 return new_dbIndex;
2479}
2480
2481void ChartCanvas::EnablePaint(bool b_enable) {
2482 m_b_paint_enable = b_enable;
2483#ifdef ocpnUSE_GL
2484 if (m_glcc) m_glcc->EnablePaint(b_enable);
2485#endif
2486}
2487
2488bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2489
2490void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2491
2492std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2493 return m_pQuilt->GetQuiltIndexArray();
2494 ;
2495}
2496
2497void ChartCanvas::SetQuiltMode(bool b_quilt) {
2498 VPoint.b_quilt = b_quilt;
2499 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2500}
2501
2502bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2503
2504int ChartCanvas::GetQuiltReferenceChartIndex() {
2505 return m_pQuilt->GetRefChartdbIndex();
2506}
2507
2508void ChartCanvas::InvalidateAllQuiltPatchs() {
2509 m_pQuilt->InvalidateAllQuiltPatchs();
2510}
2511
2512ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2513 return m_pQuilt->GetLargestScaleChart();
2514}
2515
2516ChartBase *ChartCanvas::GetFirstQuiltChart() {
2517 return m_pQuilt->GetFirstChart();
2518}
2519
2520ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2521
2522int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2523
2524void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2525 m_pQuilt->SetHiliteIndex(dbIndex);
2526}
2527
2528void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2529 m_pQuilt->SetHiliteIndexArray(hilite_array);
2530}
2531
2532void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2533 m_pQuilt->ClearHiliteIndexArray();
2534}
2535
2536std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2537 bool flag2) {
2538 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2539}
2540
2541int ChartCanvas::GetQuiltRefChartdbIndex() {
2542 return m_pQuilt->GetRefChartdbIndex();
2543}
2544
2545std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2546 return m_pQuilt->GetExtendedStackIndexArray();
2547}
2548
2549std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2550 return m_pQuilt->GetFullscreenIndexArray();
2551}
2552
2553std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2554 return m_pQuilt->GetEclipsedStackIndexArray();
2555}
2556
2557void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2558
2559double ChartCanvas::GetQuiltMaxErrorFactor() {
2560 return m_pQuilt->GetMaxErrorFactor();
2561}
2562
2563bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2564 return m_pQuilt->IsChartQuiltableRef(db_index);
2565}
2566
2567bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2568 double chartMaxScale =
2569 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2570 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2571}
2572
2573void ChartCanvas::StartMeasureRoute() {
2574 if (!m_routeState) { // no measure tool if currently creating route
2575 if (m_bMeasure_Active) {
2576 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2577 m_pMeasureRoute = NULL;
2578 }
2579
2580 m_bMeasure_Active = true;
2581 m_nMeasureState = 1;
2582 m_bDrawingRoute = false;
2583
2584 SetCursor(*pCursorPencil);
2585 Refresh();
2586 }
2587}
2588
2589void ChartCanvas::CancelMeasureRoute() {
2590 m_bMeasure_Active = false;
2591 m_nMeasureState = 0;
2592 m_bDrawingRoute = false;
2593
2594 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2595 m_pMeasureRoute = NULL;
2596
2597 SetCursor(*pCursorArrow);
2598}
2599
2600ViewPort &ChartCanvas::GetVP() { return VPoint; }
2601
2602void ChartCanvas::SetVP(ViewPort &vp) {
2603 VPoint = vp;
2604 VPoint.SetPixelScale(m_displayScale);
2605}
2606
2607// void ChartCanvas::SetFocus()
2608// {
2609// printf("set %d\n", m_canvasIndex);
2610// //wxWindow:SetFocus();
2611// }
2612
2613void ChartCanvas::TriggerDeferredFocus() {
2614 // #if defined(__WXGTK__) || defined(__WXOSX__)
2615
2616 m_deferredFocusTimer.Start(20, true);
2617
2618#if defined(__WXGTK__) || defined(__WXOSX__)
2619 gFrame->Raise();
2620#endif
2621
2622 // gFrame->Raise();
2623 // #else
2624 // SetFocus();
2625 // Refresh(true);
2626 // #endif
2627}
2628
2629void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2630 SetFocus();
2631 Refresh(true);
2632}
2633
2634void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2635 if (SendKeyEventToPlugins(event))
2636 return; // PlugIn did something, and does not want the canvas to do
2637 // anything else
2638
2639 int key_char = event.GetKeyCode();
2640 switch (key_char) {
2641 case '?':
2642 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2643 break;
2644 case '+':
2645 ZoomCanvas(g_plus_minus_zoom_factor, false);
2646 break;
2647 case '-':
2648 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2649 break;
2650 default:
2651 break;
2652 }
2653 if (g_benable_rotate) {
2654 switch (key_char) {
2655 case ']':
2656 RotateCanvas(1);
2657 Refresh();
2658 break;
2659
2660 case '[':
2661 RotateCanvas(-1);
2662 Refresh();
2663 break;
2664
2665 case '\\':
2666 DoRotateCanvas(0);
2667 break;
2668 }
2669 }
2670
2671 event.Skip();
2672}
2673
2674void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2675 if (SendKeyEventToPlugins(event))
2676 return; // PlugIn did something, and does not want the canvas to do
2677 // anything else
2678
2679 bool b_handled = false;
2680
2681 m_modkeys = event.GetModifiers();
2682
2683 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2684
2685#ifdef OCPN_ALT_MENUBAR
2686#ifndef __WXOSX__
2687 // If the permanent menubar is disabled, we show it temporarily when Alt is
2688 // pressed or when Alt + a letter is presssed (for the top-menu-level
2689 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2690 // some special cases.
2691 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2692 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2693 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2694 if (!g_bTempShowMenuBar) {
2695 g_bTempShowMenuBar = true;
2696 parent_frame->ApplyGlobalSettings(false);
2697 }
2698 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2699 event.Skip();
2700 return;
2701 }
2702 // If another key is pressed while Alt is down, do NOT toggle the menus when
2703 // Alt is released
2704 if (event.GetKeyCode() != WXK_ALT) {
2705 m_bMayToggleMenuBar = false;
2706 }
2707 }
2708#endif
2709#endif
2710
2711 // HOTKEYS
2712 switch (event.GetKeyCode()) {
2713 case WXK_TAB:
2714 // parent_frame->SwitchKBFocus( this );
2715 break;
2716
2717 case WXK_MENU:
2718 int x, y;
2719 event.GetPosition(&x, &y);
2720 m_FinishRouteOnKillFocus = false;
2721 CallPopupMenu(x, y);
2722 m_FinishRouteOnKillFocus = true;
2723 break;
2724
2725 case WXK_ALT:
2726 m_modkeys |= wxMOD_ALT;
2727 break;
2728
2729 case WXK_CONTROL:
2730 m_modkeys |= wxMOD_CONTROL;
2731 break;
2732
2733#ifdef __WXOSX__
2734 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2735 case WXK_RAW_CONTROL:
2736 m_modkeys |= wxMOD_RAW_CONTROL;
2737 break;
2738#endif
2739
2740 case WXK_LEFT:
2741 if (m_modkeys == wxMOD_CONTROL)
2742 parent_frame->DoStackDown(this);
2743 else if (g_bsmoothpanzoom) {
2744 StartTimedMovement();
2745 m_panx = -1;
2746 } else {
2747 PanCanvas(-panspeed, 0);
2748 }
2749 b_handled = true;
2750 break;
2751
2752 case WXK_UP:
2753 if (g_bsmoothpanzoom) {
2754 StartTimedMovement();
2755 m_pany = -1;
2756 } else
2757 PanCanvas(0, -panspeed);
2758 b_handled = true;
2759 break;
2760
2761 case WXK_RIGHT:
2762 if (m_modkeys == wxMOD_CONTROL)
2763 parent_frame->DoStackUp(this);
2764 else if (g_bsmoothpanzoom) {
2765 StartTimedMovement();
2766 m_panx = 1;
2767 } else
2768 PanCanvas(panspeed, 0);
2769 b_handled = true;
2770
2771 break;
2772
2773 case WXK_DOWN:
2774 if (g_bsmoothpanzoom) {
2775 StartTimedMovement();
2776 m_pany = 1;
2777 } else
2778 PanCanvas(0, panspeed);
2779 b_handled = true;
2780 break;
2781
2782 case WXK_F2: {
2783 if (event.ShiftDown()) {
2784 double scale = GetVP().view_scale_ppm;
2785 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2786
2787 std::shared_ptr<HostApi> host_api;
2788 host_api = GetHostApi();
2789 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2790
2791 if (api_121)
2792 api_121->SelectChartFamily(m_canvasIndex,
2793 (ChartFamilyEnumPI)target_family);
2794 } else
2795 TogglebFollow();
2796 break;
2797 }
2798 case WXK_F3: {
2799 if (event.ShiftDown()) {
2800 double scale = GetVP().view_scale_ppm;
2801 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2802
2803 std::shared_ptr<HostApi> host_api;
2804 host_api = GetHostApi();
2805 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2806
2807 if (api_121)
2808 api_121->SelectChartFamily(m_canvasIndex,
2809 (ChartFamilyEnumPI)target_family);
2810 } else {
2811 SetShowENCText(!GetShowENCText());
2812 Refresh(true);
2813 InvalidateGL();
2814 }
2815 break;
2816 }
2817 case WXK_F4:
2818 if (!m_bMeasure_Active) {
2819 if (event.ShiftDown())
2820 m_bMeasure_DistCircle = true;
2821 else
2822 m_bMeasure_DistCircle = false;
2823
2824 StartMeasureRoute();
2825 } else {
2826 CancelMeasureRoute();
2827
2828 SetCursor(*pCursorArrow);
2829
2830 // SurfaceToolbar();
2831 InvalidateGL();
2832 Refresh(false);
2833 }
2834
2835 break;
2836
2837 case WXK_F5:
2838 parent_frame->ToggleColorScheme();
2839 gFrame->Raise();
2840 TriggerDeferredFocus();
2841 break;
2842
2843 case WXK_F6: {
2844 int mod = m_modkeys & wxMOD_SHIFT;
2845 if (mod != m_brightmod) {
2846 m_brightmod = mod;
2847 m_bbrightdir = !m_bbrightdir;
2848 }
2849
2850 if (!m_bbrightdir) {
2851 g_nbrightness -= 10;
2852 if (g_nbrightness <= MIN_BRIGHT) {
2853 g_nbrightness = MIN_BRIGHT;
2854 m_bbrightdir = true;
2855 }
2856 } else {
2857 g_nbrightness += 10;
2858 if (g_nbrightness >= MAX_BRIGHT) {
2859 g_nbrightness = MAX_BRIGHT;
2860 m_bbrightdir = false;
2861 }
2862 }
2863
2864 SetScreenBrightness(g_nbrightness);
2865 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2866
2867 SetFocus(); // just in case the external program steals it....
2868 gFrame->Raise(); // And reactivate the application main
2869
2870 break;
2871 }
2872
2873 case WXK_F7:
2874 parent_frame->DoStackDown(this);
2875 break;
2876
2877 case WXK_F8:
2878 parent_frame->DoStackUp(this);
2879 break;
2880
2881#ifndef __WXOSX__
2882 case WXK_F9: {
2883 ToggleCanvasQuiltMode();
2884 break;
2885 }
2886#endif
2887
2888 case WXK_F11:
2889 parent_frame->ToggleFullScreen();
2890 b_handled = true;
2891 break;
2892
2893 case WXK_F12: {
2894 if (m_modkeys == wxMOD_ALT) {
2895 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2896 } else {
2897 ToggleChartOutlines();
2898 }
2899 break;
2900 }
2901
2902 case WXK_PAUSE: // Drop MOB
2903 parent_frame->ActivateMOB();
2904 break;
2905
2906 // NUMERIC PAD
2907 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2908 case WXK_PAGEUP: {
2909 ZoomCanvas(g_plus_minus_zoom_factor, false);
2910 break;
2911 }
2912 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2913 case WXK_PAGEDOWN: {
2914 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2915 break;
2916 }
2917 case WXK_DELETE:
2918 case WXK_BACK:
2919 if (m_bMeasure_Active) {
2920 if (m_nMeasureState > 2) {
2921 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2922 m_pMeasureRoute->m_lastMousePointIndex =
2923 m_pMeasureRoute->GetnPoints();
2924 m_nMeasureState--;
2925 gFrame->RefreshAllCanvas();
2926 } else {
2927 CancelMeasureRoute();
2928 StartMeasureRoute();
2929 }
2930 }
2931 break;
2932 default:
2933 break;
2934 }
2935
2936 if (event.GetKeyCode() < 128) // ascii
2937 {
2938 int key_char = event.GetKeyCode();
2939
2940 // Handle both QWERTY and AZERTY keyboard separately for a few control
2941 // codes
2942 if (!g_b_assume_azerty) {
2943#ifdef __WXMAC__
2944 if (g_benable_rotate) {
2945 switch (key_char) {
2946 // On other platforms these are handled in OnKeyChar, which
2947 // (apparently) works better in some locales. On OS X it is better
2948 // to handle them here, since pressing Alt (which should change the
2949 // rotation speed) changes the key char and so prevents the keys
2950 // from working.
2951 case ']':
2952 RotateCanvas(1);
2953 b_handled = true;
2954 break;
2955
2956 case '[':
2957 RotateCanvas(-1);
2958 b_handled = true;
2959 break;
2960
2961 case '\\':
2962 DoRotateCanvas(0);
2963 b_handled = true;
2964 break;
2965 }
2966 }
2967#endif
2968 } else { // AZERTY
2969 switch (key_char) {
2970 case 43:
2971 ZoomCanvas(g_plus_minus_zoom_factor, false);
2972 break;
2973
2974 case 54: // '-' alpha/num pad
2975 // case 56: // '_' alpha/num pad
2976 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2977 break;
2978 }
2979 }
2980
2981#ifdef __WXOSX__
2982 // Ctrl+Cmd+F toggles fullscreen on macOS
2983 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2984 m_modkeys & wxMOD_RAW_CONTROL) {
2985 parent_frame->ToggleFullScreen();
2986 return;
2987 }
2988#endif
2989
2990 if (event.ControlDown()) key_char -= 64;
2991
2992 if (key_char >= '0' && key_char <= '9')
2993 SetGroupIndex(key_char - '0');
2994 else
2995
2996 switch (key_char) {
2997 case 'A':
2998 SetShowENCAnchor(!GetShowENCAnchor());
2999 ReloadVP();
3000
3001 break;
3002
3003 case 'C':
3004 parent_frame->ToggleColorScheme();
3005 break;
3006
3007 case 'D': {
3008 int x, y;
3009 event.GetPosition(&x, &y);
3010 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3011 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3012 // First find out what kind of chart is being used
3013 if (!pPopupDetailSlider) {
3014 if (VPoint.b_quilt) {
3015 if (m_pQuilt) {
3016 if (m_pQuilt->GetChartAtPix(
3017 VPoint,
3018 wxPoint(
3019 x, y))) // = null if no chart loaded for this point
3020 {
3021 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3022 ->GetChartType();
3023 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3024 ->GetChartFamily();
3025 }
3026 }
3027 } else {
3028 if (m_singleChart) {
3029 ChartType = m_singleChart->GetChartType();
3030 ChartFam = m_singleChart->GetChartFamily();
3031 }
3032 }
3033 // If a charttype is found show the popupslider
3034 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3035 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3037 this, -1, ChartType, ChartFam,
3038 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3039 wxDefaultSize, wxSIMPLE_BORDER, "");
3041 }
3042 } else //( !pPopupDetailSlider ) close popupslider
3043 {
3045 pPopupDetailSlider = NULL;
3046 }
3047 break;
3048 }
3049
3050 case 'E':
3051 m_nmea_log->Show();
3052 m_nmea_log->Raise();
3053 break;
3054
3055 case 'L':
3056 SetShowENCLights(!GetShowENCLights());
3057 ReloadVP();
3058
3059 break;
3060
3061 case 'M':
3062 if (event.ShiftDown())
3063 m_bMeasure_DistCircle = true;
3064 else
3065 m_bMeasure_DistCircle = false;
3066
3067 StartMeasureRoute();
3068 break;
3069
3070 case 'N':
3071 if (g_bInlandEcdis && ps52plib) {
3072 SetENCDisplayCategory((_DisCat)STANDARD);
3073 }
3074 break;
3075
3076 case 'O':
3077 ToggleChartOutlines();
3078 break;
3079
3080 case 'Q':
3081 ToggleCanvasQuiltMode();
3082 break;
3083
3084 case 'P':
3085 parent_frame->ToggleTestPause();
3086 break;
3087 case 'R':
3088 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3089 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3090 g_iNavAidRadarRingsNumberVisible = 1;
3091 else if (!g_bNavAidRadarRingsShown &&
3092 g_iNavAidRadarRingsNumberVisible == 1)
3093 g_iNavAidRadarRingsNumberVisible = 0;
3094 break;
3095 case 'S':
3096 SetShowENCDepth(!m_encShowDepth);
3097 ReloadVP();
3098 break;
3099
3100 case 'T':
3101 SetShowENCText(!GetShowENCText());
3102 ReloadVP();
3103 break;
3104
3105 case 'U':
3106 SetShowENCDataQual(!GetShowENCDataQual());
3107 ReloadVP();
3108 break;
3109
3110 case 'V':
3111 m_bShowNavobjects = !m_bShowNavobjects;
3112 Refresh(true);
3113 break;
3114
3115 case 'W': // W Toggle CPA alarm
3116 ToggleCPAWarn();
3117
3118 break;
3119
3120 case 1: // Ctrl A
3121 TogglebFollow();
3122
3123 break;
3124
3125 case 2: // Ctrl B
3126 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3127 break;
3128
3129 case 13: // Ctrl M // Drop Marker at cursor
3130 {
3131 if (event.ControlDown()) gFrame->DropMarker(false);
3132 break;
3133 }
3134
3135 case 14: // Ctrl N - Activate next waypoint in a route
3136 {
3137 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3138 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3139 if ((indexActive + 1) <= r->GetnPoints()) {
3141 InvalidateGL();
3142 Refresh(false);
3143 }
3144 }
3145 break;
3146 }
3147
3148 case 15: // Ctrl O - Drop Marker at boat's position
3149 {
3150 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3151 break;
3152 }
3153
3154 case 32: // Special needs use space bar
3155 {
3156 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3157 break;
3158 }
3159
3160 case -32: // Ctrl Space // Drop MOB
3161 {
3162 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3163
3164 break;
3165 }
3166
3167 case -20: // Ctrl ,
3168 {
3169 parent_frame->DoSettings();
3170 break;
3171 }
3172 case 17: // Ctrl Q
3173 parent_frame->Close();
3174 return;
3175
3176 case 18: // Ctrl R
3177 StartRoute();
3178 return;
3179
3180 case 20: // Ctrl T
3181 if (NULL == pGoToPositionDialog) // There is one global instance of
3182 // the Go To Position Dialog
3184 pGoToPositionDialog->SetCanvas(this);
3185 pGoToPositionDialog->Show();
3186 break;
3187
3188 case 25: // Ctrl Y
3189 if (undo->AnythingToRedo()) {
3190 undo->RedoNextAction();
3191 InvalidateGL();
3192 Refresh(false);
3193 }
3194 break;
3195
3196 case 26:
3197 if (event.ShiftDown()) { // Shift-Ctrl-Z
3198 if (undo->AnythingToRedo()) {
3199 undo->RedoNextAction();
3200 InvalidateGL();
3201 Refresh(false);
3202 }
3203 } else { // Ctrl Z
3204 if (undo->AnythingToUndo()) {
3205 undo->UndoLastAction();
3206 InvalidateGL();
3207 Refresh(false);
3208 }
3209 }
3210 break;
3211
3212 case 27:
3213 // Generic break
3214 if (m_bMeasure_Active) {
3215 CancelMeasureRoute();
3216
3217 SetCursor(*pCursorArrow);
3218
3219 // SurfaceToolbar();
3220 gFrame->RefreshAllCanvas();
3221 }
3222
3223 if (m_routeState) // creating route?
3224 {
3225 FinishRoute();
3226 // SurfaceToolbar();
3227 InvalidateGL();
3228 Refresh(false);
3229 }
3230
3231 break;
3232
3233 case 7: // Ctrl G
3234 switch (gamma_state) {
3235 case (0):
3236 r_gamma_mult = 0;
3237 g_gamma_mult = 1;
3238 b_gamma_mult = 0;
3239 gamma_state = 1;
3240 break;
3241 case (1):
3242 r_gamma_mult = 1;
3243 g_gamma_mult = 0;
3244 b_gamma_mult = 0;
3245 gamma_state = 2;
3246 break;
3247 case (2):
3248 r_gamma_mult = 1;
3249 g_gamma_mult = 1;
3250 b_gamma_mult = 1;
3251 gamma_state = 0;
3252 break;
3253 }
3254 SetScreenBrightness(g_nbrightness);
3255
3256 break;
3257
3258 case 9: // Ctrl I
3259 if (event.ControlDown()) {
3260 m_bShowCompassWin = !m_bShowCompassWin;
3261 SetShowGPSCompassWindow(m_bShowCompassWin);
3262 Refresh(false);
3263 }
3264 break;
3265
3266 default:
3267 break;
3268
3269 } // switch
3270 }
3271
3272 // Allow OnKeyChar to catch the key events too.
3273 if (!b_handled) {
3274 event.Skip();
3275 }
3276}
3277
3278void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3279 if (SendKeyEventToPlugins(event))
3280 return; // PlugIn did something, and does not want the canvas to do
3281 // anything else
3282
3283 switch (event.GetKeyCode()) {
3284 case WXK_TAB:
3285 parent_frame->SwitchKBFocus(this);
3286 break;
3287
3288 case WXK_LEFT:
3289 case WXK_RIGHT:
3290 m_panx = 0;
3291 if (!m_pany) m_panspeed = 0;
3292 break;
3293
3294 case WXK_UP:
3295 case WXK_DOWN:
3296 m_pany = 0;
3297 if (!m_panx) m_panspeed = 0;
3298 break;
3299
3300 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3301 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3302 case WXK_PAGEUP:
3303 case WXK_PAGEDOWN:
3304 if (m_mustmove) DoMovement(m_mustmove);
3305
3306 m_zoom_factor = 1;
3307 break;
3308
3309 case WXK_ALT:
3310 m_modkeys &= ~wxMOD_ALT;
3311#ifdef OCPN_ALT_MENUBAR
3312#ifndef __WXOSX__
3313 // If the permanent menu bar is disabled, and we are not in the middle of
3314 // another key combo, then show the menu bar temporarily when Alt is
3315 // released (or hide it if already visible).
3316 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3317 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3318 parent_frame->ApplyGlobalSettings(false);
3319 }
3320 m_bMayToggleMenuBar = true;
3321#endif
3322#endif
3323 break;
3324
3325 case WXK_CONTROL:
3326 m_modkeys &= ~wxMOD_CONTROL;
3327 break;
3328 }
3329
3330 if (event.GetKeyCode() < 128) // ascii
3331 {
3332 int key_char = event.GetKeyCode();
3333
3334 // Handle both QWERTY and AZERTY keyboard separately for a few control
3335 // codes
3336 if (!g_b_assume_azerty) {
3337 switch (key_char) {
3338 case '+':
3339 case '=':
3340 case '-':
3341 case '_':
3342 case 54:
3343 case 56: // '_' alpha/num pad
3344 DoMovement(m_mustmove);
3345
3346 // m_zoom_factor = 1;
3347 break;
3348 case '[':
3349 case ']':
3350 DoMovement(m_mustmove);
3351 m_rotation_speed = 0;
3352 break;
3353 }
3354 } else {
3355 switch (key_char) {
3356 case 43:
3357 case 54: // '-' alpha/num pad
3358 case 56: // '_' alpha/num pad
3359 DoMovement(m_mustmove);
3360
3361 m_zoom_factor = 1;
3362 break;
3363 }
3364 }
3365 }
3366 event.Skip();
3367}
3368
3369void ChartCanvas::ToggleChartOutlines() {
3370 m_bShowOutlines = !m_bShowOutlines;
3371
3372 Refresh(false);
3373
3374#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3375 // needs a full refresh
3376 if (g_bopengl) InvalidateGL();
3377#endif
3378}
3379
3380void ChartCanvas::ToggleLookahead() {
3381 m_bLookAhead = !m_bLookAhead;
3382 m_OSoffsetx = 0; // center ownship
3383 m_OSoffsety = 0;
3384}
3385
3386void ChartCanvas::SetUpMode(int mode) {
3387 m_upMode = mode;
3388
3389 if (mode != NORTH_UP_MODE) {
3390 // Stuff the COGAvg table in case COGUp is selected
3391 double stuff = 0;
3392 if (!std::isnan(gCog)) stuff = gCog;
3393
3394 if (g_COGAvgSec > 0) {
3395 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3396 }
3397 g_COGAvg = stuff;
3398 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3399 } else {
3400 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3401 SetVPRotation(GetVPSkew());
3402 else
3403 SetVPRotation(0); /* reset to north up */
3404 }
3405
3406 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3407 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3408
3409 UpdateGPSCompassStatusBox(true);
3410 gFrame->DoChartUpdate();
3411}
3412
3413bool ChartCanvas::DoCanvasCOGSet() {
3414 if (GetUpMode() == NORTH_UP_MODE) return false;
3415 double cog_use = g_COGAvg;
3416 if (g_btenhertz) cog_use = gCog;
3417
3418 double rotation = 0;
3419 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3420 rotation = -gHdt * PI / 180.;
3421 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3422 rotation = -cog_use * PI / 180.;
3423
3424 SetVPRotation(rotation);
3425 return true;
3426}
3427
3428double easeOutCubic(double t) {
3429 // Starts quickly and slows down toward the end
3430 return 1.0 - pow(1.0 - t, 3.0);
3431}
3432
3433void ChartCanvas::StartChartDragInertia() {
3434 m_bChartDragging = false;
3435
3436 // Set some parameters
3437 m_chart_drag_inertia_time = 750; // msec
3438 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3439 m_last_elapsed = 0;
3440
3441 // Calculate ending drag velocity
3442 size_t n_vel = 10;
3443 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3444 int xacc = 0;
3445 int yacc = 0;
3446 double tacc = 0;
3447 size_t length = m_drag_vec_t.size();
3448 for (size_t i = 0; i < n_vel; i++) {
3449 xacc += m_drag_vec_x.at(length - 1 - i);
3450 yacc += m_drag_vec_y.at(length - 1 - i);
3451 tacc += m_drag_vec_t.at(length - 1 - i);
3452 }
3453
3454 if (tacc == 0) return;
3455
3456 double drag_velocity_x = xacc / tacc;
3457 double drag_velocity_y = yacc / tacc;
3458 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3459 // drag_velocity_y);
3460
3461 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3462 // touch tap.
3463 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3464
3465 m_chart_drag_velocity_x = drag_velocity_x;
3466 m_chart_drag_velocity_y = drag_velocity_y;
3467
3468 m_chart_drag_inertia_active = true;
3469 // First callback as fast as possible.
3470 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3471}
3472
3473void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3474 if (!m_chart_drag_inertia_active) return;
3475 // Calculate time fraction from 0..1
3476 wxLongLong now = wxGetLocalTimeMillis();
3477 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3478 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3479 if (t > 1.0) t = 1.0;
3480 double e = 1.0 - easeOutCubic(t); // 0..1
3481
3482 double dx =
3483 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3484 double dy =
3485 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3486
3487 m_last_elapsed = elapsed;
3488
3489 // Ensure that target destination lies on whole-pixel boundary
3490 // This allows the render engine to use a faster FBO copy method for drawing
3491 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3492 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3493 double inertia_lat, inertia_lon;
3494 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3495 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3496 // Check if ownship has moved off-screen
3497 if (!IsOwnshipOnScreen()) {
3498 m_bFollow = false; // update the follow flag
3499 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3500 UpdateFollowButtonState();
3501 m_OSoffsetx = 0;
3502 m_OSoffsety = 0;
3503 } else {
3504 m_OSoffsetx += dx;
3505 m_OSoffsety -= dy;
3506 }
3507
3508 Refresh(false);
3509
3510 // Stop condition
3511 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3512 m_chart_drag_inertia_timer.Stop();
3513
3514 // Disable chart pan movement logic
3515 m_target_lat = GetVP().clat;
3516 m_target_lon = GetVP().clon;
3517 m_pan_drag.x = m_pan_drag.y = 0;
3518 m_panx = m_pany = 0;
3519 m_chart_drag_inertia_active = false;
3520 DoCanvasUpdate();
3521
3522 } else {
3523 int target_redraw_interval = 40; // msec
3524 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3525 }
3526}
3527
3528void ChartCanvas::StopMovement() {
3529 m_panx = m_pany = 0;
3530 m_panspeed = 0;
3531 m_zoom_factor = 1;
3532 m_rotation_speed = 0;
3533 m_mustmove = 0;
3534#if 0
3535#if !defined(__WXGTK__) && !defined(__WXQT__)
3536 SetFocus();
3537 gFrame->Raise();
3538#endif
3539#endif
3540}
3541
3542/* instead of integrating in timer callbacks
3543 (which do not always get called fast enough)
3544 we can perform the integration of movement
3545 at each render frame based on the time change */
3546bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3547 // Start/restart the stop movement timer
3548 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3549
3550 if (!pMovementTimer->IsRunning()) {
3551 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3552 }
3553
3554 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3555 // already moving, gets called again because of key-repeat event
3556 return false;
3557 }
3558
3559 m_last_movement_time = wxDateTime::UNow();
3560
3561 return true;
3562}
3563void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3564 int nstep) {
3565 // Save the target
3566 m_target_lat = target_lat;
3567 m_target_lon = target_lon;
3568
3569 // Save the start point
3570 m_start_lat = GetVP().clat;
3571 m_start_lon = GetVP().clon;
3572
3573 m_VPMovementTimer.Start(1, true); // oneshot
3574 m_timed_move_vp_active = true;
3575 m_stvpc = 0;
3576 m_timedVP_step = nstep;
3577}
3578
3579void ChartCanvas::DoTimedMovementVP() {
3580 if (!m_timed_move_vp_active) return; // not active
3581 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3582 StopMovement();
3583 return;
3584 }
3585 // Stop condition
3586 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3587 double d2 =
3588 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3589 d2 = pow(d2, 0.5);
3590
3591 if (d2 < one_pix) {
3592 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3593 StopMovementVP();
3594 return;
3595 }
3596
3597 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3598 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3599 // StopMovementVP();
3600 // return;
3601 // }
3602
3603 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3604 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3605
3606 m_run_lat = new_lat;
3607 m_run_lon = new_lon;
3608
3609 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3610}
3611
3612void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3613
3614void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3615
3616void ChartCanvas::StartTimedMovementTarget() {}
3617
3618void ChartCanvas::DoTimedMovementTarget() {}
3619
3620void ChartCanvas::StopMovementTarget() {}
3621int ntm;
3622
3623void ChartCanvas::DoTimedMovement() {
3624 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3625 !m_rotation_speed)
3626 return; /* not moving */
3627
3628 wxDateTime now = wxDateTime::UNow();
3629 long dt = 0;
3630 if (m_last_movement_time.IsValid())
3631 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3632
3633 m_last_movement_time = now;
3634
3635 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3636 dt = 500;
3637
3638 DoMovement(dt);
3639}
3640
3642 /* if we get here quickly assume 1ms so that some movement occurs */
3643 if (dt == 0) dt = 1;
3644
3645 m_mustmove -= dt;
3646 if (m_mustmove < 0) m_mustmove = 0;
3647
3648 if (!m_inPinch) { // this stops compound zoom/pan
3649 if (m_pan_drag.x || m_pan_drag.y) {
3650 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3651 m_pan_drag.x = m_pan_drag.y = 0;
3652 }
3653
3654 if (m_panx || m_pany) {
3655 const double slowpan = .1, maxpan = 2;
3656 if (m_modkeys == wxMOD_ALT)
3657 m_panspeed = slowpan;
3658 else {
3659 m_panspeed += (double)dt / 500; /* apply acceleration */
3660 m_panspeed = wxMin(maxpan, m_panspeed);
3661 }
3662 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3663 }
3664 }
3665 if (m_zoom_factor != 1) {
3666 double alpha = 400, beta = 1.5;
3667 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3668
3669 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3670
3671 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3672
3673 // Try to hit the zoom target exactly.
3674 // if(m_wheelzoom_stop_oneshot > 0)
3675 {
3676 if (zoom_factor > 1) {
3677 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3678 zoom_factor = VPoint.chart_scale / m_zoom_target;
3679 }
3680
3681 else if (zoom_factor < 1) {
3682 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3683 zoom_factor = VPoint.chart_scale / m_zoom_target;
3684 }
3685 }
3686
3687 if (fabs(zoom_factor - 1) > 1e-4) {
3688 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3689 } else {
3690 StopMovement();
3691 }
3692
3693 if (m_wheelzoom_stop_oneshot > 0) {
3694 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3695 m_wheelzoom_stop_oneshot = 0;
3696 StopMovement();
3697 }
3698
3699 // Don't overshoot the zoom target.
3700 if (zoom_factor > 1) {
3701 if (VPoint.chart_scale <= m_zoom_target) {
3702 m_wheelzoom_stop_oneshot = 0;
3703 StopMovement();
3704 }
3705 } else if (zoom_factor < 1) {
3706 if (VPoint.chart_scale >= m_zoom_target) {
3707 m_wheelzoom_stop_oneshot = 0;
3708 StopMovement();
3709 }
3710 }
3711 }
3712 }
3713
3714 if (m_rotation_speed) { /* in degrees per second */
3715 double speed = m_rotation_speed;
3716 if (m_modkeys == wxMOD_ALT) speed /= 10;
3717 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3718 }
3719}
3720
3721void ChartCanvas::SetColorScheme(ColorScheme cs) {
3722 SetAlertString("");
3723
3724 // Setup ownship image pointers
3725 switch (cs) {
3726 case GLOBAL_COLOR_SCHEME_DAY:
3727 m_pos_image_red = &m_os_image_red_day;
3728 m_pos_image_grey = &m_os_image_grey_day;
3729 m_pos_image_yellow = &m_os_image_yellow_day;
3730 m_pos_image_user = m_pos_image_user_day;
3731 m_pos_image_user_grey = m_pos_image_user_grey_day;
3732 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3733 m_cTideBitmap = m_bmTideDay;
3734 m_cCurrentBitmap = m_bmCurrentDay;
3735
3736 break;
3737 case GLOBAL_COLOR_SCHEME_DUSK:
3738 m_pos_image_red = &m_os_image_red_dusk;
3739 m_pos_image_grey = &m_os_image_grey_dusk;
3740 m_pos_image_yellow = &m_os_image_yellow_dusk;
3741 m_pos_image_user = m_pos_image_user_dusk;
3742 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3743 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3744 m_cTideBitmap = m_bmTideDusk;
3745 m_cCurrentBitmap = m_bmCurrentDusk;
3746 break;
3747 case GLOBAL_COLOR_SCHEME_NIGHT:
3748 m_pos_image_red = &m_os_image_red_night;
3749 m_pos_image_grey = &m_os_image_grey_night;
3750 m_pos_image_yellow = &m_os_image_yellow_night;
3751 m_pos_image_user = m_pos_image_user_night;
3752 m_pos_image_user_grey = m_pos_image_user_grey_night;
3753 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3754 m_cTideBitmap = m_bmTideNight;
3755 m_cCurrentBitmap = m_bmCurrentNight;
3756 break;
3757 default:
3758 m_pos_image_red = &m_os_image_red_day;
3759 m_pos_image_grey = &m_os_image_grey_day;
3760 m_pos_image_yellow = &m_os_image_yellow_day;
3761 m_pos_image_user = m_pos_image_user_day;
3762 m_pos_image_user_grey = m_pos_image_user_grey_day;
3763 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3764 m_cTideBitmap = m_bmTideDay;
3765 m_cCurrentBitmap = m_bmCurrentDay;
3766 break;
3767 }
3768
3769 CreateDepthUnitEmbossMaps(cs);
3770 CreateOZEmbossMapData(cs);
3771
3772 // Set up fog effect base color
3773 m_fog_color = wxColor(
3774 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3775 float dim = 1.0;
3776 switch (cs) {
3777 case GLOBAL_COLOR_SCHEME_DUSK:
3778 dim = 0.5;
3779 break;
3780 case GLOBAL_COLOR_SCHEME_NIGHT:
3781 dim = 0.25;
3782 break;
3783 default:
3784 break;
3785 }
3786 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3787 m_fog_color.Blue() * dim);
3788
3789 // Really dark
3790#if 0
3791 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3792 SetBackgroundColour( wxColour(0,0,0) );
3793
3794 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3795 }
3796 else{
3797 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3798#ifndef __WXMAC__
3799 SetBackgroundColour( wxNullColour );
3800#endif
3801 }
3802#endif
3803
3804 // UpdateToolbarColorScheme(cs);
3805
3806 m_Piano->SetColorScheme(cs);
3807
3808 m_Compass->SetColorScheme(cs);
3809
3810 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3811
3812 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3813
3814 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3815 if (m_notification_button) {
3816 m_notification_button->SetColorScheme(cs);
3817 }
3818
3819#ifdef ocpnUSE_GL
3820 if (g_bopengl && m_glcc) {
3821 m_glcc->SetColorScheme(cs);
3822 g_glTextureManager->ClearAllRasterTextures();
3823 // m_glcc->FlushFBO();
3824 }
3825#endif
3826 SetbTCUpdate(true); // force re-render of tide/current locators
3827 m_brepaint_piano = true;
3828
3829 ReloadVP();
3830
3831 m_cs = cs;
3832}
3833
3834wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3835 wxImage img = Bitmap.ConvertToImage();
3836 int sx = img.GetWidth();
3837 int sy = img.GetHeight();
3838
3839 wxImage new_img(img);
3840
3841 for (int i = 0; i < sx; i++) {
3842 for (int j = 0; j < sy; j++) {
3843 if (!img.IsTransparent(i, j)) {
3844 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3845 (unsigned char)(img.GetGreen(i, j) * factor),
3846 (unsigned char)(img.GetBlue(i, j) * factor));
3847 }
3848 }
3849 }
3850
3851 wxBitmap ret = wxBitmap(new_img);
3852
3853 return ret;
3854}
3855
3856void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3857 int max) {
3858 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3859 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3860
3861 if (!m_pBrightPopup) {
3862 // Calculate size
3863 int x, y;
3864 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3865
3866 m_pBrightPopup = new TimedPopupWin(this, 3);
3867
3868 m_pBrightPopup->SetSize(x, y);
3869 m_pBrightPopup->Move(120, 120);
3870 }
3871
3872 int bmpsx = m_pBrightPopup->GetSize().x;
3873 int bmpsy = m_pBrightPopup->GetSize().y;
3874
3875 wxBitmap bmp(bmpsx, bmpsx);
3876 wxMemoryDC mdc(bmp);
3877
3878 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3879 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3880 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3881 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3882 mdc.Clear();
3883
3884 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3885
3886 mdc.SetFont(*pfont);
3887 wxString val;
3888
3889 if (brightness == max)
3890 val = "MAX";
3891 else if (brightness == min)
3892 val = "MIN";
3893 else
3894 val.Printf("%3d", brightness);
3895
3896 mdc.DrawText(val, 0, 0);
3897
3898 mdc.SelectObject(wxNullBitmap);
3899
3900 m_pBrightPopup->SetBitmap(bmp);
3901 m_pBrightPopup->Show();
3902 m_pBrightPopup->Refresh();
3903}
3904
3905void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3906 m_b_rot_hidef = true;
3907 ReloadVP();
3908}
3909
3910void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3911 if (!g_bRollover) return;
3912
3913 bool b_need_refresh = false;
3914
3915 wxSize win_size = GetSize() * m_displayScale;
3916 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3917
3918 // Handle the AIS Rollover Window first
3919 bool showAISRollover = false;
3920 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3921 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3922 SelectItem *pFind = pSelectAIS->FindSelection(
3923 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3924 if (pFind) {
3925 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3926 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3927
3928 if (ptarget) {
3929 showAISRollover = true;
3930
3931 if (NULL == m_pAISRolloverWin) {
3932 m_pAISRolloverWin = new RolloverWin(this);
3933 m_pAISRolloverWin->IsActive(false);
3934 b_need_refresh = true;
3935 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3936 m_AISRollover_MMSI != FoundAIS_MMSI) {
3937 // Sometimes the mouse moves fast enough to get over a new AIS
3938 // target before the one-shot has fired to remove the old target.
3939 // Result: wrong target data is shown.
3940 // Detect this case,close the existing rollover ASAP, and restart
3941 // the timer.
3942 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3943 m_pAISRolloverWin->IsActive(false);
3944 m_AISRollover_MMSI = 0;
3945 Refresh();
3946 return;
3947 }
3948
3949 m_AISRollover_MMSI = FoundAIS_MMSI;
3950
3951 if (!m_pAISRolloverWin->IsActive()) {
3952 wxString s = ptarget->GetRolloverString();
3953 m_pAISRolloverWin->SetString(s);
3954
3955 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3956 AIS_ROLLOVER, win_size);
3957 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3958 m_pAISRolloverWin->IsActive(true);
3959 b_need_refresh = true;
3960 }
3961 }
3962 } else {
3963 m_AISRollover_MMSI = 0;
3964 showAISRollover = false;
3965 }
3966 }
3967
3968 // Maybe turn the rollover off
3969 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3970 m_pAISRolloverWin->IsActive(false);
3971 m_AISRollover_MMSI = 0;
3972 b_need_refresh = true;
3973 }
3974
3975 // Now the Route info rollover
3976 // Show the route segment info
3977 bool showRouteRollover = false;
3978
3979 if (NULL == m_pRolloverRouteSeg) {
3980 // Get a list of all selectable sgements, and search for the first
3981 // visible segment as the rollover target.
3982
3983 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3984 SelectableItemList SelList = pSelect->FindSelectionList(
3985 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3986 auto node = SelList.begin();
3987 while (node != SelList.end()) {
3988 SelectItem *pFindSel = *node;
3989
3990 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3991
3992 if (pr && pr->IsVisible()) {
3993 m_pRolloverRouteSeg = pFindSel;
3994 showRouteRollover = true;
3995
3996 if (NULL == m_pRouteRolloverWin) {
3997 m_pRouteRolloverWin = new RolloverWin(this, 10);
3998 m_pRouteRolloverWin->IsActive(false);
3999 }
4000
4001 if (!m_pRouteRolloverWin->IsActive()) {
4002 wxString s;
4003 RoutePoint *segShow_point_a =
4004 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4005 RoutePoint *segShow_point_b =
4006 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4007
4008 double brg, dist;
4009 DistanceBearingMercator(
4010 segShow_point_b->m_lat, segShow_point_b->m_lon,
4011 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4012
4013 if (!pr->m_bIsInLayer)
4014 s.Append(_("Route") + ": ");
4015 else
4016 s.Append(_("Layer Route: "));
4017
4018 if (pr->m_RouteNameString.IsEmpty())
4019 s.Append(_("(unnamed)"));
4020 else
4021 s.Append(pr->m_RouteNameString);
4022
4023 s << "\n"
4024 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4025 << "\n"
4026 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4027 << segShow_point_b->GetName() << "\n";
4028
4029 if (g_bShowTrue)
4030 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4031 (int)floor(brg + 0.5), 0x00B0);
4032 if (g_bShowMag) {
4033 double latAverage =
4034 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4035 double lonAverage =
4036 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4037 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4038
4039 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4040 (int)floor(varBrg + 0.5), 0x00B0);
4041 }
4042
4043 s << FormatDistanceAdaptive(dist);
4044
4045 // Compute and display cumulative distance from route start point to
4046 // current leg end point and RNG,TTG,ETA from ship to current leg end
4047 // point for active route
4048 double shiptoEndLeg = 0.;
4049 bool validActive = false;
4050 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4051 validActive = true;
4052
4053 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4054 auto node = pr->pRoutePointList->begin();
4055 RoutePoint *prp;
4056 float dist_to_endleg = 0;
4057 wxString t;
4058
4059 for (++node; node != pr->pRoutePointList->end(); ++node) {
4060 prp = *node;
4061 if (validActive)
4062 shiptoEndLeg += prp->m_seg_len;
4063 else if (prp->m_bIsActive)
4064 validActive = true;
4065 dist_to_endleg += prp->m_seg_len;
4066 if (prp->IsSame(segShow_point_a)) break;
4067 }
4068 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4069 }
4070 // write from ship to end selected leg point data if the route is
4071 // active
4072 if (validActive) {
4073 s << "\n"
4074 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4075 shiptoEndLeg +=
4077 ->GetCurrentRngToActivePoint(); // add distance from ship
4078 // to active point
4079 shiptoEndLeg +=
4080 segShow_point_b
4081 ->m_seg_len; // add the lenght of the selected leg
4082 s << FormatDistanceAdaptive(shiptoEndLeg);
4083 // ensure sog/cog are valid and vmg is positive to keep data
4084 // coherent
4085 double vmg = 0.;
4086 if (!std::isnan(gCog) && !std::isnan(gSog))
4087 vmg = gSog *
4088 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4089 PI / 180.);
4090 if (vmg > 0.) {
4091 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4092 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4093 s << " - "
4094 << wxString(ttg_sec > SECONDS_PER_DAY
4095 ? ttg_span.Format(_("%Dd %H:%M"))
4096 : ttg_span.Format(_("%H:%M")));
4097 wxDateTime dtnow, eta;
4098 eta = dtnow.SetToCurrent().Add(ttg_span);
4099 s << " - " << eta.Format("%b").Mid(0, 4)
4100 << eta.Format(" %d %H:%M");
4101 } else
4102 s << " ---- ----";
4103 }
4104 m_pRouteRolloverWin->SetString(s);
4105
4106 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4107 LEG_ROLLOVER, win_size);
4108 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4109 m_pRouteRolloverWin->IsActive(true);
4110 b_need_refresh = true;
4111 showRouteRollover = true;
4112 break;
4113 }
4114 } else {
4115 ++node;
4116 }
4117 }
4118 } else {
4119 // Is the cursor still in select radius, and not timed out?
4120 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4121 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4122 m_pRolloverRouteSeg))
4123 showRouteRollover = false;
4124 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4125 showRouteRollover = false;
4126 else
4127 showRouteRollover = true;
4128 }
4129
4130 // If currently creating a route, do not show this rollover window
4131 if (m_routeState) showRouteRollover = false;
4132
4133 // Similar for AIS target rollover window
4134 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4135 showRouteRollover = false;
4136
4137 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4138 !showRouteRollover) {
4139 m_pRouteRolloverWin->IsActive(false);
4140 m_pRolloverRouteSeg = NULL;
4141 m_pRouteRolloverWin->Destroy();
4142 m_pRouteRolloverWin = NULL;
4143 b_need_refresh = true;
4144 } else if (m_pRouteRolloverWin && showRouteRollover) {
4145 m_pRouteRolloverWin->IsActive(true);
4146 b_need_refresh = true;
4147 }
4148
4149 // Now the Track info rollover
4150 // Show the track segment info
4151 bool showTrackRollover = false;
4152
4153 if (NULL == m_pRolloverTrackSeg) {
4154 // Get a list of all selectable sgements, and search for the first
4155 // visible segment as the rollover target.
4156
4157 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4158 SelectableItemList SelList = pSelect->FindSelectionList(
4159 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4160
4161 auto node = SelList.begin();
4162 while (node != SelList.end()) {
4163 SelectItem *pFindSel = *node;
4164
4165 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4166
4167 if (pt && pt->IsVisible()) {
4168 m_pRolloverTrackSeg = pFindSel;
4169 showTrackRollover = true;
4170
4171 if (NULL == m_pTrackRolloverWin) {
4172 m_pTrackRolloverWin = new RolloverWin(this, 10);
4173 m_pTrackRolloverWin->IsActive(false);
4174 }
4175
4176 if (!m_pTrackRolloverWin->IsActive()) {
4177 wxString s;
4178 TrackPoint *segShow_point_a =
4179 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4180 TrackPoint *segShow_point_b =
4181 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4182
4183 double brg, dist;
4184 DistanceBearingMercator(
4185 segShow_point_b->m_lat, segShow_point_b->m_lon,
4186 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4187
4188 if (!pt->m_bIsInLayer)
4189 s.Append(_("Track") + ": ");
4190 else
4191 s.Append(_("Layer Track: "));
4192
4193 if (pt->GetName().IsEmpty())
4194 s.Append(_("(unnamed)"));
4195 else
4196 s.Append(pt->GetName());
4197 double tlenght = pt->Length();
4198 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4199 if (pt->GetLastPoint()->GetTimeString() &&
4200 pt->GetPoint(0)->GetTimeString()) {
4201 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4202 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4203 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4204 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4205 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4206 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4207 << getUsrSpeedUnit();
4208 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4209 : ttime.Format(" %H:%M"));
4210 }
4211 }
4212
4213 if (g_bShowTrackPointTime &&
4214 strlen(segShow_point_b->GetTimeString())) {
4215 wxString stamp = segShow_point_b->GetTimeString();
4216 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4217 if (timestamp.IsValid()) {
4218 // Format track rollover timestamp to OCPN global TZ setting
4221 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4222 }
4223 s << "\n" << _("Segment Created: ") << stamp;
4224 }
4225
4226 s << "\n";
4227 if (g_bShowTrue)
4228 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4229 0x00B0);
4230
4231 if (g_bShowMag) {
4232 double latAverage =
4233 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4234 double lonAverage =
4235 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4236 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4237
4238 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4239 0x00B0);
4240 }
4241
4242 s << FormatDistanceAdaptive(dist);
4243
4244 if (segShow_point_a->GetTimeString() &&
4245 segShow_point_b->GetTimeString()) {
4246 wxDateTime apoint = segShow_point_a->GetCreateTime();
4247 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4248 if (apoint.IsValid() && bpoint.IsValid()) {
4249 double segmentSpeed = toUsrSpeed(
4250 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4251 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4252 << getUsrSpeedUnit();
4253 }
4254 }
4255
4256 m_pTrackRolloverWin->SetString(s);
4257
4258 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4259 LEG_ROLLOVER, win_size);
4260 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4261 m_pTrackRolloverWin->IsActive(true);
4262 b_need_refresh = true;
4263 showTrackRollover = true;
4264 break;
4265 }
4266 } else {
4267 ++node;
4268 }
4269 }
4270 } else {
4271 // Is the cursor still in select radius, and not timed out?
4272 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4273 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4274 m_pRolloverTrackSeg))
4275 showTrackRollover = false;
4276 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4277 showTrackRollover = false;
4278 else
4279 showTrackRollover = true;
4280 }
4281
4282 // Similar for AIS target rollover window
4283 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4284 showTrackRollover = false;
4285
4286 // Similar for route rollover window
4287 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4288 showTrackRollover = false;
4289
4290 // TODO We onlt show tracks on primary canvas....
4291 // if(!IsPrimaryCanvas())
4292 // showTrackRollover = false;
4293
4294 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4295 !showTrackRollover) {
4296 m_pTrackRolloverWin->IsActive(false);
4297 m_pRolloverTrackSeg = NULL;
4298 m_pTrackRolloverWin->Destroy();
4299 m_pTrackRolloverWin = NULL;
4300 b_need_refresh = true;
4301 } else if (m_pTrackRolloverWin && showTrackRollover) {
4302 m_pTrackRolloverWin->IsActive(true);
4303 b_need_refresh = true;
4304 }
4305
4306 if (b_need_refresh) Refresh();
4307}
4308
4309void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4310 if ((GetShowENCLights() || m_bsectors_shown) &&
4311 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4312 extendedSectorLegs)) {
4313 if (!m_bsectors_shown) {
4314 ReloadVP(false);
4315 m_bsectors_shown = true;
4316 }
4317 } else {
4318 if (m_bsectors_shown) {
4319 ReloadVP(false);
4320 m_bsectors_shown = false;
4321 }
4322 }
4323
4324// This is here because GTK status window update is expensive..
4325// cairo using pango rebuilds the font every time so is very
4326// inefficient
4327// Anyway, only update the status bar when this timer expires
4328#if defined(__WXGTK__) || defined(__WXQT__)
4329 {
4330 // Check the absolute range of the cursor position
4331 // There could be a window wherein the chart geoereferencing is not
4332 // valid....
4333 double cursor_lat, cursor_lon;
4334 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4335
4336 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4337 while (cursor_lon < -180.) cursor_lon += 360.;
4338
4339 while (cursor_lon > 180.) cursor_lon -= 360.;
4340
4341 SetCursorStatus(cursor_lat, cursor_lon);
4342 }
4343 }
4344#endif
4345}
4346
4347void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4348 if (!parent_frame->m_pStatusBar) return;
4349
4350 wxString s1;
4351 s1 += " ";
4352 s1 += toSDMM(1, cursor_lat);
4353 s1 += " ";
4354 s1 += toSDMM(2, cursor_lon);
4355
4356 if (STAT_FIELD_CURSOR_LL >= 0)
4357 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4358
4359 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4360
4361 double brg, dist;
4362 wxString sm;
4363 wxString st;
4364 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4365 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4366 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4367
4368 wxString s = st + sm;
4369 s << FormatDistanceAdaptive(dist);
4370
4371 // CUSTOMIZATION - LIVE ETA OPTION
4372 // -------------------------------------------------------
4373 // Calculate an "live" ETA based on route starting from the current
4374 // position of the boat and goes to the cursor of the mouse.
4375 // In any case, an standard ETA will be calculated with a default speed
4376 // of the boat to give an estimation of the route (in particular if GPS
4377 // is off).
4378
4379 // Display only if option "live ETA" is selected in Settings > Display >
4380 // General.
4381 if (g_bShowLiveETA) {
4382 float realTimeETA;
4383 float boatSpeed;
4384 float boatSpeedDefault = g_defaultBoatSpeed;
4385
4386 // Calculate Estimate Time to Arrival (ETA) in minutes
4387 // Check before is value not closed to zero (it will make an very big
4388 // number...)
4389 if (!std::isnan(gSog)) {
4390 boatSpeed = gSog;
4391 if (boatSpeed < 0.5) {
4392 realTimeETA = 0;
4393 } else {
4394 realTimeETA = dist / boatSpeed * 60;
4395 }
4396 } else {
4397 realTimeETA = 0;
4398 }
4399
4400 // Add space after distance display
4401 s << " ";
4402 // Display ETA
4403 s << minutesToHoursDays(realTimeETA);
4404
4405 // In any case, display also an ETA with default speed at 6knts
4406
4407 s << " [@";
4408 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4409 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4410 s << " ";
4411 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4412 s << "]";
4413 }
4414 // END OF - LIVE ETA OPTION
4415
4416 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4417}
4418
4419// CUSTOMIZATION - FORMAT MINUTES
4420// -------------------------------------------------------
4421// New function to format minutes into a more readable format:
4422// * Hours + minutes, or
4423// * Days + hours.
4424wxString minutesToHoursDays(float timeInMinutes) {
4425 wxString s;
4426
4427 if (timeInMinutes == 0) {
4428 s << "--min";
4429 }
4430
4431 // Less than 60min, keep time in minutes
4432 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4433 s << wxString::Format("%d", (int)timeInMinutes);
4434 s << "min";
4435 }
4436
4437 // Between 1h and less than 24h, display time in hours, minutes
4438 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4439 int hours;
4440 int min;
4441 hours = (int)timeInMinutes / 60;
4442 min = (int)timeInMinutes % 60;
4443
4444 if (min == 0) {
4445 s << wxString::Format("%d", hours);
4446 s << "h";
4447 } else {
4448 s << wxString::Format("%d", hours);
4449 s << "h";
4450 s << wxString::Format("%d", min);
4451 s << "min";
4452 }
4453
4454 }
4455
4456 // More than 24h, display time in days, hours
4457 else if (timeInMinutes > 24 * 60) {
4458 int days;
4459 int hours;
4460 days = (int)(timeInMinutes / 60) / 24;
4461 hours = (int)(timeInMinutes / 60) % 24;
4462
4463 if (hours == 0) {
4464 s << wxString::Format("%d", days);
4465 s << "d";
4466 } else {
4467 s << wxString::Format("%d", days);
4468 s << "d";
4469 s << wxString::Format("%d", hours);
4470 s << "h";
4471 }
4472 }
4473
4474 return s;
4475}
4476
4477// END OF CUSTOMIZATION - FORMAT MINUTES
4478// Thanks open source code ;-)
4479// -------------------------------------------------------
4480
4481void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4482 double clat, clon;
4483 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4484 *lat = clat;
4485 *lon = clon;
4486}
4487
4488void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4489 wxPoint2DDouble *r) {
4490 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4491}
4492
4494 double rlon, wxPoint2DDouble *r) {
4495 // If the Current Chart is a raster chart, and the
4496 // requested lat/long is within the boundaries of the chart,
4497 // and the VP is not rotated,
4498 // then use the embedded BSB chart georeferencing algorithm
4499 // for greater accuracy
4500 // Additionally, use chart embedded georef if the projection is TMERC
4501 // i.e. NOT MERCATOR and NOT POLYCONIC
4502
4503 // If for some reason the chart rejects the request by returning an error,
4504 // then fall back to Viewport Projection estimate from canvas parameters
4505 if (!g_bopengl && m_singleChart &&
4506 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4507 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4508 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4509 (m_singleChart->GetChartProjectionType() !=
4510 PROJECTION_TRANSVERSE_MERCATOR) &&
4511 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4512 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4513 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4514 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4515 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4516 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4517 // Cur_BSB_Ch->GetCOVRTablenPoints
4518 // ( 0 ), rlon,
4519 // rlat );
4520 // bInside = true;
4521 // if ( bInside )
4522 if (Cur_BSB_Ch) {
4523 // This is a Raster chart....
4524 // If the VP is changing, the raster chart parameters may not yet be
4525 // setup So do that before accessing the chart's embedded
4526 // georeferencing
4527 Cur_BSB_Ch->SetVPRasterParms(vp);
4528 double rpixxd, rpixyd;
4529 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4530 r->m_x = rpixxd;
4531 r->m_y = rpixyd;
4532 return;
4533 }
4534 }
4535 }
4536
4537 // if needed, use the VPoint scaling estimator,
4538 *r = vp.GetDoublePixFromLL(rlat, rlon);
4539}
4540
4541// This routine might be deleted and all of the rendering improved
4542// to have floating point accuracy
4543bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4544 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4545}
4546
4547bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4548 wxPoint *r) {
4549 wxPoint2DDouble p;
4550 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4551
4552 // some projections give nan values when invisible values (other side of
4553 // world) are requested we should stop using integer coordinates or return
4554 // false here (and test it everywhere)
4555 if (std::isnan(p.m_x)) {
4556 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4557 return false;
4558 }
4559
4560 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4561 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4562 else
4563 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4564
4565 return true;
4566}
4567
4568void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4569 double &lon) {
4570 // If the Current Chart is a raster chart, and the
4571 // requested x,y is within the boundaries of the chart,
4572 // and the VP is not rotated,
4573 // then use the embedded BSB chart georeferencing algorithm
4574 // for greater accuracy
4575 // Additionally, use chart embedded georef if the projection is TMERC
4576 // i.e. NOT MERCATOR and NOT POLYCONIC
4577
4578 // If for some reason the chart rejects the request by returning an error,
4579 // then fall back to Viewport Projection estimate from canvas parameters
4580 bool bUseVP = true;
4581
4582 if (!g_bopengl && m_singleChart &&
4583 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4584 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4585 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4586 (m_singleChart->GetChartProjectionType() !=
4587 PROJECTION_TRANSVERSE_MERCATOR) &&
4588 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4589 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4590 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4591 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4592
4593 // TODO maybe need iterative process to validate bInside
4594 // first pass is mercator, then check chart boundaries
4595
4596 if (Cur_BSB_Ch) {
4597 // This is a Raster chart....
4598 // If the VP is changing, the raster chart parameters may not yet be
4599 // setup So do that before accessing the chart's embedded
4600 // georeferencing
4601 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4602
4603 double slat, slon;
4604 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4605 lat = slat;
4606
4607 if (slon < -180.)
4608 slon += 360.;
4609 else if (slon > 180.)
4610 slon -= 360.;
4611
4612 lon = slon;
4613 bUseVP = false;
4614 }
4615 }
4616 }
4617
4618 // if needed, use the VPoint scaling estimator
4619 if (bUseVP) {
4620 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4621 }
4622}
4623
4625 StopMovement();
4626 DoZoomCanvas(factor, false);
4627 extendedSectorLegs.clear();
4628}
4629
4630void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4631 bool stoptimer) {
4632 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4633
4634 if (g_bsmoothpanzoom) {
4635 if (StartTimedMovement(stoptimer)) {
4636 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4637 m_zoom_factor = factor;
4638 }
4639
4640 m_zoom_target = VPoint.chart_scale / factor;
4641 } else {
4642 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4643
4644 DoZoomCanvas(factor, can_zoom_to_cursor);
4645 }
4646
4647 extendedSectorLegs.clear();
4648}
4649
4650void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4651 // possible on startup
4652 if (!ChartData) return;
4653 if (!m_pCurrentStack) return;
4654
4655 /* TODO: queue the quilted loading code to a background thread
4656 so yield is never called from here, and also rendering is not delayed */
4657
4658 // Cannot allow Yield() re-entrancy here
4659 if (m_bzooming) return;
4660 m_bzooming = true;
4661
4662 double old_ppm = GetVP().view_scale_ppm;
4663
4664 // Capture current cursor position for zoom to cursor
4665 double zlat = m_cursor_lat;
4666 double zlon = m_cursor_lon;
4667
4668 double proposed_scale_onscreen =
4669 GetVP().chart_scale /
4670 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4671 bool b_do_zoom = false;
4672
4673 if (factor > 1) {
4674 b_do_zoom = true;
4675
4676 // double zoom_factor = factor;
4677
4678 ChartBase *pc = NULL;
4679
4680 if (!VPoint.b_quilt) {
4681 pc = m_singleChart;
4682 } else {
4683 if (!m_disable_adjust_on_zoom) {
4684 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4685 if (new_db_index >= 0)
4686 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4687 else { // for whatever reason, no reference chart is known
4688 // Choose the smallest scale chart on the current stack
4689 // and then adjust for scale range
4690 int current_ref_stack_index = -1;
4691 if (m_pCurrentStack->nEntry) {
4692 int trial_index =
4693 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4694 m_pQuilt->SetReferenceChart(trial_index);
4695 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4696 if (new_db_index >= 0)
4697 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4698 }
4699 }
4700
4701 if (m_pCurrentStack)
4702 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4703 new_db_index); // highlite the correct bar entry
4704 }
4705 }
4706
4707 if (pc) {
4708 // double target_scale_ppm = GetVPScale() * zoom_factor;
4709 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4710 // target_scale_ppm;
4711
4712 // Query the chart to determine the appropriate zoom range
4713 double min_allowed_scale =
4714 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4715
4716 if (proposed_scale_onscreen < min_allowed_scale) {
4717 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4718 m_zoom_factor = 1; /* stop zooming */
4719 b_do_zoom = false;
4720 } else
4721 proposed_scale_onscreen = min_allowed_scale;
4722 }
4723
4724 } else {
4725 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4726 }
4727
4728 } else if (factor < 1) {
4729 b_do_zoom = true;
4730
4731 ChartBase *pc = NULL;
4732
4733 bool b_smallest = false;
4734
4735 if (!VPoint.b_quilt) { // not quilted
4736 pc = m_singleChart;
4737
4738 if (pc) {
4739 // If m_singleChart is not on the screen, unbound the zoomout
4740 LLBBox viewbox = VPoint.GetBBox();
4741 // BoundingBox chart_box;
4742 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4743 double max_allowed_scale;
4744
4745 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4746
4747 // We can allow essentially unbounded zoomout in single chart mode
4748 // if( ChartData->GetDBBoundingBox( current_index,
4749 // &chart_box ) &&
4750 // !viewbox.IntersectOut( chart_box ) )
4751 // // Clamp the minimum scale zoom-out to the value
4752 // specified by the chart max_allowed_scale =
4753 // wxMin(max_allowed_scale, 4.0 *
4754 // pc->GetNormalScaleMax(
4755 // GetCanvasScaleFactor(),
4756 // GetCanvasWidth() ) );
4757 if (proposed_scale_onscreen > max_allowed_scale) {
4758 m_zoom_factor = 1; /* stop zooming */
4759 proposed_scale_onscreen = max_allowed_scale;
4760 }
4761 }
4762
4763 } else {
4764 if (!m_disable_adjust_on_zoom) {
4765 int new_db_index =
4766 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4767 if (new_db_index >= 0)
4768 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4769
4770 if (m_pCurrentStack)
4771 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4772 new_db_index); // highlite the correct bar entry
4773
4774 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4775
4776 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4777 proposed_scale_onscreen =
4778 wxMin(proposed_scale_onscreen,
4779 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4780 }
4781
4782 // set a minimum scale
4783 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4784 m_absolute_min_scale_ppm)
4785 proposed_scale_onscreen =
4786 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4787 }
4788 }
4789 double new_scale =
4790 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4791
4792 if (b_do_zoom) {
4793 // Disable ZTC if lookahead is ON, and currently b_follow is active
4794 bool b_allow_ztc = true;
4795 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4796 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4797 if (m_bLookAhead) {
4798 double brg, distance;
4799 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4800 &distance);
4801 dir_to_shift = brg;
4802 meters_to_shift = distance * 1852;
4803 }
4804 // Arrange to combine the zoom and pan into one operation for smoother
4805 // appearance
4806 SetVPScale(new_scale, false); // adjust, but deferred refresh
4807 wxPoint r;
4808 GetCanvasPointPix(zlat, zlon, &r);
4809 // this will emit the Refresh()
4810 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4811 } else {
4812 SetVPScale(new_scale);
4813 if (m_bFollow) DoCanvasUpdate();
4814 }
4815 }
4816
4817 m_bzooming = false;
4818}
4819
4820void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4821 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4822 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4823}
4824
4825int rot;
4826void ChartCanvas::RotateCanvas(double dir) {
4827 // SetUpMode(NORTH_UP_MODE);
4828
4829 if (g_bsmoothpanzoom) {
4830 if (StartTimedMovement()) {
4831 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4832 m_rotation_speed = dir * 60;
4833 }
4834 } else {
4835 double speed = dir * 10;
4836 if (m_modkeys == wxMOD_ALT) speed /= 20;
4837 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4838 }
4839}
4840
4841void ChartCanvas::DoRotateCanvas(double rotation) {
4842 while (rotation < 0) rotation += 2 * PI;
4843 while (rotation > 2 * PI) rotation -= 2 * PI;
4844
4845 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4846
4847 SetVPRotation(rotation);
4848 parent_frame->UpdateRotationState(VPoint.rotation);
4849}
4850
4851void ChartCanvas::DoTiltCanvas(double tilt) {
4852 while (tilt < 0) tilt = 0;
4853 while (tilt > .95) tilt = .95;
4854
4855 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4856
4857 VPoint.tilt = tilt;
4858 Refresh(false);
4859}
4860
4861void ChartCanvas::TogglebFollow() {
4862 if (!m_bFollow)
4863 SetbFollow();
4864 else
4865 ClearbFollow();
4866}
4867
4868void ChartCanvas::ClearbFollow() {
4869 m_bFollow = false; // update the follow flag
4870
4871 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4872
4873 UpdateFollowButtonState();
4874
4875 DoCanvasUpdate();
4876 ReloadVP();
4877 parent_frame->SetChartUpdatePeriod();
4878}
4879
4880void ChartCanvas::SetbFollow() {
4881 // Is the OWNSHIP on-screen?
4882 // If not, then reset the OWNSHIP offset to 0 (center screen)
4883 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4884 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4885 m_OSoffsetx = 0;
4886 m_OSoffsety = 0;
4887 }
4888
4889 // Apply the present b_follow offset values to ship position
4890 wxPoint2DDouble p;
4892 p.m_x += m_OSoffsetx;
4893 p.m_y -= m_OSoffsety;
4894
4895 // compute the target center screen lat/lon
4896 double dlat, dlon;
4897 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4898
4899 JumpToPosition(dlat, dlon, GetVPScale());
4900 m_bFollow = true;
4901
4902 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4903 UpdateFollowButtonState();
4904
4905 if (!g_bSmoothRecenter) {
4906 DoCanvasUpdate();
4907 ReloadVP();
4908 }
4909 parent_frame->SetChartUpdatePeriod();
4910}
4911
4912void ChartCanvas::UpdateFollowButtonState() {
4913 if (m_muiBar) {
4914 if (!m_bFollow)
4915 m_muiBar->SetFollowButtonState(0);
4916 else {
4917 if (m_bLookAhead)
4918 m_muiBar->SetFollowButtonState(2);
4919 else
4920 m_muiBar->SetFollowButtonState(1);
4921 }
4922 }
4923
4924#ifdef __ANDROID__
4925 if (!m_bFollow)
4926 androidSetFollowTool(0);
4927 else {
4928 if (m_bLookAhead)
4929 androidSetFollowTool(2);
4930 else
4931 androidSetFollowTool(1);
4932 }
4933#endif
4934
4935 // Look for plugin using API-121 or later
4936 // If found, make the follow state callback.
4937 if (g_pi_manager) {
4938 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4939 if (pic->m_enabled && pic->m_init_state) {
4940 switch (pic->m_api_version) {
4941 case 121: {
4942 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4943 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4944 break;
4945 }
4946 default:
4947 break;
4948 }
4949 }
4950 }
4951 }
4952}
4953
4954void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4955 if (g_bSmoothRecenter && !m_routeState) {
4956 if (StartSmoothJump(lat, lon, scale_ppm))
4957 return;
4958 else {
4959 // move closer to the target destination, and try again
4960 double gcDist, gcBearingEnd;
4961 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4962 &gcBearingEnd);
4963 gcBearingEnd += 180;
4964 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4965 GetCanvasWidth() / GetVPScale(); // meters
4966 double lon_offset =
4967 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4968 double new_lat = lat + (lat_offset / (1852 * 60));
4969 double new_lon = lon + (lon_offset / (1852 * 60));
4970 SetViewPoint(new_lat, new_lon);
4971 ReloadVP();
4972 StartSmoothJump(lat, lon, scale_ppm);
4973 return;
4974 }
4975 }
4976
4977 if (lon > 180.0) lon -= 360.0;
4978 m_vLat = lat;
4979 m_vLon = lon;
4980 StopMovement();
4981 m_bFollow = false;
4982
4983 if (!GetQuiltMode()) {
4984 double skew = 0;
4985 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4986 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4987 } else {
4988 if (scale_ppm != GetVPScale()) {
4989 // XXX should be done in SetViewPoint
4990 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4991 AdjustQuiltRefChart();
4992 }
4993 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4994 }
4995
4996 ReloadVP();
4997
4998 UpdateFollowButtonState();
4999
5000 // TODO
5001 // if( g_pi_manager ) {
5002 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5003 // }
5004}
5005
5006bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5007 // Check distance to jump, in pixels at current chart scale
5008 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5009 // width.
5010 double gcDist;
5011 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5012 double distance_pixels = gcDist * GetVPScale();
5013 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5014 // Jump is too far, try again
5015 return false;
5016 }
5017
5018 // Save where we're coming from
5019 m_startLat = m_vLat;
5020 m_startLon = m_vLon;
5021 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5022
5023 // Save where we want to end up
5024 m_endLat = lat;
5025 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5026 m_endScale = scale_ppm;
5027
5028 // Setup timing
5029 m_animationDuration = 600; // ms
5030 m_animationStart = wxGetLocalTimeMillis();
5031
5032 // Stop any previous movement, ensure no conflicts
5033 StopMovement();
5034 m_bFollow = false;
5035
5036 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5037 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5038 m_animationActive = true;
5039
5040 return true;
5041}
5042
5043void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5044 // Calculate time fraction from 0..1
5045 wxLongLong now = wxGetLocalTimeMillis();
5046 double elapsed = (now - m_animationStart).ToDouble();
5047 double t = elapsed / m_animationDuration.ToDouble();
5048 if (t > 1.0) t = 1.0;
5049
5050 // Ease function for smoother movement
5051 double e = easeOutCubic(t);
5052
5053 // Interpolate lat/lon/scale
5054 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5055 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5056 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5057
5058 // Update viewpoint
5059 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5060 // portion)
5061 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5062 ReloadVP();
5063
5064 // If we reached the end, stop the timer and finalize
5065 if (t >= 1.0) {
5066 m_easeTimer.Stop();
5067 m_animationActive = false;
5068 UpdateFollowButtonState();
5069 ZoomCanvasSimple(1.0001);
5070 DoCanvasUpdate();
5071 ReloadVP();
5072 }
5073}
5074
5075bool ChartCanvas::PanCanvas(double dx, double dy) {
5076 if (!ChartData) return false;
5077 extendedSectorLegs.clear();
5078
5079 double dlat, dlon;
5080 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5081
5082 int iters = 0;
5083 for (;;) {
5084 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5085
5086 if (iters++ > 5) return false;
5087 if (!std::isnan(dlat)) break;
5088
5089 dx *= .5, dy *= .5;
5090 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5091 }
5092
5093 // avoid overshooting the poles
5094 if (dlat > 90)
5095 dlat = 90;
5096 else if (dlat < -90)
5097 dlat = -90;
5098
5099 if (dlon > 360.) dlon -= 360.;
5100 if (dlon < -360.) dlon += 360.;
5101
5102 // This should not really be necessary, but round-trip georef on some
5103 // charts is not perfect, So we can get creep on repeated unidimensional
5104 // pans, and corrupt chart cacheing.......
5105
5106 // But this only works on north-up projections
5107 // TODO: can we remove this now?
5108 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5109 // .001 ) ) {
5110 //
5111 // if( dx == 0 ) dlon = clon;
5112 // if( dy == 0 ) dlat = clat;
5113 // }
5114
5115 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5116
5117 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5118
5119 if (VPoint.b_quilt) {
5120 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5121 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5122 // Tweak the scale slightly for a new ref chart
5123 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5124 if (pc) {
5125 double tweak_scale_ppm =
5126 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5127 SetVPScale(tweak_scale_ppm);
5128 }
5129 }
5130
5131 if (new_ref_dbIndex == -1) {
5132#pragma GCC diagnostic push
5133#pragma GCC diagnostic ignored "-Warray-bounds"
5134 // The compiler sees a -1 index being used. Does not happen, though.
5135
5136 // for whatever reason, no reference chart is known
5137 // Probably panned out of the coverage region
5138 // If any charts are anywhere on-screen, choose the smallest
5139 // scale chart on the screen to be a new reference chart.
5140 int trial_index = -1;
5141 if (m_pCurrentStack->nEntry) {
5142 int trial_index =
5143 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5144 }
5145
5146 if (trial_index < 0) {
5147 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5148 if (full_screen_array.size())
5149 trial_index = full_screen_array[full_screen_array.size() - 1];
5150 }
5151
5152 if (trial_index >= 0) {
5153 m_pQuilt->SetReferenceChart(trial_index);
5154 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5155 VPoint.rotation);
5156 ReloadVP();
5157 }
5158#pragma GCC diagnostic pop
5159 }
5160 }
5161
5162 // Turn off bFollow only if the ownship has left the screen
5163 if (m_bFollow) {
5164 double offx, offy;
5165 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5166
5167 double offset_angle = atan2(offy, offx);
5168 double offset_distance = sqrt((offy * offy) + (offx * offx));
5169 double chart_angle = GetVPRotation();
5170 double target_angle = chart_angle - offset_angle;
5171 double d_east_mod = offset_distance * cos(target_angle);
5172 double d_north_mod = offset_distance * sin(target_angle);
5173
5174 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5175 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5176
5177 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5178 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5179 m_bFollow = false; // update the follow flag
5180 UpdateFollowButtonState();
5181 }
5182 }
5183
5184 Refresh(false);
5185
5186 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5187
5188 return true;
5189}
5190
5191bool ChartCanvas::IsOwnshipOnScreen() {
5192 wxPoint r;
5194 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5195 ((r.y > 0) && r.y < GetCanvasHeight()))
5196 return true;
5197 else
5198 return false;
5199}
5200
5201void ChartCanvas::ReloadVP(bool b_adjust) {
5202 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5203
5204 LoadVP(VPoint, b_adjust);
5205}
5206
5207void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5208#ifdef ocpnUSE_GL
5209 if (g_bopengl && m_glcc) {
5210 m_glcc->Invalidate();
5211 if (m_glcc->GetSize() != GetSize()) {
5212 m_glcc->SetSize(GetSize());
5213 }
5214 } else
5215#endif
5216 {
5217 m_cache_vp.Invalidate();
5218 m_bm_cache_vp.Invalidate();
5219 }
5220
5221 VPoint.Invalidate();
5222
5223 if (m_pQuilt) m_pQuilt->Invalidate();
5224
5225 // Make sure that the Selected Group is sensible...
5226 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5227 // m_groupIndex = 0;
5228 // if( !CheckGroup( m_groupIndex ) )
5229 // m_groupIndex = 0;
5230
5231 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5232 vp.m_projection_type, b_adjust);
5233}
5234
5235void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5236 m_pQuilt->SetReferenceChart(dbIndex);
5237 VPoint.Invalidate();
5238 m_pQuilt->Invalidate();
5239}
5240
5241double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5242 if (m_pQuilt)
5243 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5244 else
5245 return vp.view_scale_ppm;
5246}
5247
5248// Verify and adjust the current reference chart,
5249// so that it will not lead to excessive overzoom or underzoom onscreen
5250int ChartCanvas::AdjustQuiltRefChart() {
5251 int ret = -1;
5252 if (m_pQuilt) {
5253 wxASSERT(ChartData);
5254 ChartBase *pc =
5255 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5256 if (pc) {
5257 double min_ref_scale =
5258 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5259 double max_ref_scale =
5260 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5261
5262 if (VPoint.chart_scale < min_ref_scale) {
5263 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5264 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5265 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5266 } else {
5267 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5268
5269 if (!brender_ok) {
5270 int target_stack_index = wxNOT_FOUND;
5271 int il = 0;
5272 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5273 if (index == m_pQuilt->GetRefChartdbIndex()) {
5274 target_stack_index = il;
5275 break;
5276 }
5277 il++;
5278 }
5279 if (wxNOT_FOUND == target_stack_index) // should never happen...
5280 target_stack_index = 0;
5281
5282 int ref_family = pc->GetChartFamily();
5283 int extended_array_count =
5284 m_pQuilt->GetExtendedStackIndexArray().size();
5285 while ((!brender_ok) &&
5286 ((int)target_stack_index < (extended_array_count - 1))) {
5287 target_stack_index++;
5288 int test_db_index =
5289 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5290
5291 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5292 IsChartQuiltableRef(test_db_index)) {
5293 // open the target, and check the min_scale
5294 ChartBase *ptest_chart =
5295 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5296 if (ptest_chart) {
5297 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5298 }
5299 }
5300 }
5301
5302 if (brender_ok) { // found a better reference chart
5303 int new_db_index =
5304 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5305 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5306 IsChartQuiltableRef(new_db_index)) {
5307 m_pQuilt->SetReferenceChart(new_db_index);
5308 ret = new_db_index;
5309 } else
5310 ret = m_pQuilt->GetRefChartdbIndex();
5311 } else
5312 ret = m_pQuilt->GetRefChartdbIndex();
5313
5314 } else
5315 ret = m_pQuilt->GetRefChartdbIndex();
5316 }
5317 } else
5318 ret = -1;
5319 }
5320
5321 return ret;
5322}
5323
5324void ChartCanvas::UpdateCanvasOnGroupChange() {
5325 delete m_pCurrentStack;
5326 m_pCurrentStack = new ChartStack;
5327 wxASSERT(ChartData);
5328 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5329 m_groupIndex);
5330
5331 if (m_pQuilt) {
5332 m_pQuilt->Compose(VPoint);
5333 SetFocus();
5334 }
5335}
5336
5337bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5338 double latNE, double lonNE) {
5339 // Center Point
5340 double latc = (latSW + latNE) / 2.0;
5341 double lonc = (lonSW + lonNE) / 2.0;
5342
5343 // Get scale in ppm (latitude)
5344 double ne_easting, ne_northing;
5345 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5346
5347 double sw_easting, sw_northing;
5348 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5349
5350 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5351
5352 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5353}
5354
5355bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5356 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5357 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5358}
5359
5360bool ChartCanvas::SetVPProjection(int projection) {
5361 if (!g_bopengl) // alternative projections require opengl
5362 return false;
5363
5364 // the view scale varies depending on geographic location and projection
5365 // rescale to keep the relative scale on the screen the same
5366 double prev_true_scale_ppm = m_true_scale_ppm;
5367 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5368 VPoint.skew, VPoint.rotation, projection) &&
5369 SetVPScale(wxMax(
5370 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5371 m_absolute_min_scale_ppm));
5372}
5373
5374bool ChartCanvas::SetViewPoint(double lat, double lon) {
5375 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5376 VPoint.rotation);
5377}
5378
5379bool ChartCanvas::SetVPRotation(double angle) {
5380 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5381 VPoint.skew, angle);
5382}
5383bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5384 double skew, double rotation, int projection,
5385 bool b_adjust, bool b_refresh) {
5386 bool b_ret = false;
5387 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5388 skew -= 2 * PI;
5389 // Any sensible change?
5390 if (VPoint.IsValid()) {
5391 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5392 (fabs(VPoint.skew - skew) < 1e-9) &&
5393 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5394 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5395 (VPoint.m_projection_type == projection ||
5396 projection == PROJECTION_UNKNOWN))
5397 return false;
5398 }
5399 if (VPoint.m_projection_type != projection)
5400 VPoint.InvalidateTransformCache(); // invalidate
5401
5402 // Take a local copy of the last viewport
5403 ViewPort last_vp = VPoint;
5404
5405 VPoint.skew = skew;
5406 VPoint.clat = lat;
5407 VPoint.clon = lon;
5408 VPoint.rotation = rotation;
5409 VPoint.view_scale_ppm = scale_ppm;
5410 if (projection != PROJECTION_UNKNOWN)
5411 VPoint.SetProjectionType(projection);
5412 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5413 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5414
5415 // don't allow latitude above 88 for mercator (90 is infinity)
5416 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5417 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5418 if (VPoint.clat > 89.5)
5419 VPoint.clat = 89.5;
5420 else if (VPoint.clat < -89.5)
5421 VPoint.clat = -89.5;
5422 }
5423
5424 // don't zoom out too far for transverse mercator polyconic until we resolve
5425 // issues
5426 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5427 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5428 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5429
5430 // SetVPRotation(rotation);
5431
5432 if (!g_bopengl) // tilt is not possible without opengl
5433 VPoint.tilt = 0;
5434
5435 if ((VPoint.pix_width <= 0) ||
5436 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5437 return false;
5438
5439 bool bwasValid = VPoint.IsValid();
5440 VPoint.Validate(); // Mark this ViewPoint as OK
5441
5442 // Has the Viewport scale changed? If so, invalidate the vp
5443 if (last_vp.view_scale_ppm != scale_ppm) {
5444 m_cache_vp.Invalidate();
5445 InvalidateGL();
5446 }
5447
5448 // A preliminary value, may be tweaked below
5449 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5450
5451 // recompute cursor position
5452 // and send to interested plugins if the mouse is actually in this window
5453 int mouseX = mouse_x;
5454 int mouseY = mouse_y;
5455 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5456 (mouseY < VPoint.pix_height)) {
5457 double lat, lon;
5458 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5459 m_cursor_lat = lat;
5460 m_cursor_lon = lon;
5461 SendCursorLatLonToAllPlugIns(lat, lon);
5462 }
5463
5464 if (!VPoint.b_quilt && m_singleChart) {
5465 VPoint.SetBoxes();
5466
5467 // Allow the chart to adjust the new ViewPort for performance optimization
5468 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5469 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5470
5471 // If there is a sensible change in the chart render, refresh the whole
5472 // screen
5473 if ((!m_cache_vp.IsValid()) ||
5474 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5475 Refresh(false);
5476 b_ret = true;
5477 } else {
5478 wxPoint cp_last, cp_this;
5479 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5480 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5481
5482 if (cp_last != cp_this) {
5483 Refresh(false);
5484 b_ret = true;
5485 }
5486 }
5487 // Create the stack
5488 if (m_pCurrentStack) {
5489 assert(ChartData != 0);
5490 int current_db_index;
5491 current_db_index =
5492 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5493
5494 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5495 m_groupIndex);
5496 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5497 }
5498
5499 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5500 }
5501
5502 // Handle the quilted case
5503 if (VPoint.b_quilt) {
5504 VPoint.SetBoxes();
5505
5506 if (last_vp.view_scale_ppm != scale_ppm)
5507 m_pQuilt->InvalidateAllQuiltPatchs();
5508
5509 // Create the quilt
5510 if (ChartData /*&& ChartData->IsValid()*/) {
5511 if (!m_pCurrentStack) return false;
5512
5513 int current_db_index;
5514 current_db_index =
5515 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5516
5517 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5518 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5519
5520 // Check to see if the current quilt reference chart is in the new stack
5521 int current_ref_stack_index = -1;
5522 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5523 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5524 current_ref_stack_index = i;
5525 }
5526
5527 if (g_bFullScreenQuilt) {
5528 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5529 }
5530
5531 // We might need a new Reference Chart
5532 bool b_needNewRef = false;
5533
5534 // If the new stack does not contain the current ref chart....
5535 if ((-1 == current_ref_stack_index) &&
5536 (m_pQuilt->GetRefChartdbIndex() >= 0))
5537 b_needNewRef = true;
5538
5539 // Would the current Ref Chart be excessively underzoomed?
5540 // We need to check this here to be sure, since we cannot know where the
5541 // reference chart was assigned. For instance, the reference chart may
5542 // have been selected from the config file, or from a long jump with a
5543 // chart family switch implicit. Anyway, we check to be sure....
5544 bool renderable = true;
5545 ChartBase *referenceChart =
5546 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5547 if (referenceChart) {
5548 double chartMaxScale = referenceChart->GetNormalScaleMax(
5549 GetCanvasScaleFactor(), GetCanvasWidth());
5550 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5551 }
5552 if (!renderable) b_needNewRef = true;
5553
5554 // Need new refchart?
5555 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5556 const ChartTableEntry &cte_ref =
5557 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5558 int target_scale = cte_ref.GetScale();
5559 int target_type = cte_ref.GetChartType();
5560 int candidate_stack_index;
5561
5562 // reset the ref chart in a way that does not lead to excessive
5563 // underzoom, for performance reasons Try to find a chart that is the
5564 // same type, and has a scale of just smaller than the current ref
5565 // chart
5566
5567 candidate_stack_index = 0;
5568 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5569 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5570 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5571 int candidate_scale = cte_candidate.GetScale();
5572 int candidate_type = cte_candidate.GetChartType();
5573
5574 if ((candidate_scale >= target_scale) &&
5575 (candidate_type == target_type)) {
5576 bool renderable = true;
5577 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5578 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5579 if (tentative_referenceChart) {
5580 double chartMaxScale =
5581 tentative_referenceChart->GetNormalScaleMax(
5582 GetCanvasScaleFactor(), GetCanvasWidth());
5583 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5584 }
5585
5586 if (renderable) break;
5587 }
5588
5589 candidate_stack_index++;
5590 }
5591
5592 // If that did not work, look for a chart of just larger scale and
5593 // same type
5594 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5595 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5596 while (candidate_stack_index >= 0) {
5597 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5598 if (idx >= 0) {
5599 const ChartTableEntry &cte_candidate =
5600 ChartData->GetChartTableEntry(idx);
5601 int candidate_scale = cte_candidate.GetScale();
5602 int candidate_type = cte_candidate.GetChartType();
5603
5604 if ((candidate_scale <= target_scale) &&
5605 (candidate_type == target_type))
5606 break;
5607 }
5608 candidate_stack_index--;
5609 }
5610 }
5611
5612 // and if that did not work, chose stack entry 0
5613 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5614 (candidate_stack_index < 0))
5615 candidate_stack_index = 0;
5616
5617 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5618
5619 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5620 }
5621
5622 if (!g_bopengl) {
5623 // Preset the VPoint projection type to match what the quilt projection
5624 // type will be
5625 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5626
5627 // Always keep the default Mercator projection if the reference chart is
5628 // not in the PatchList or the scale is too small for it to render.
5629
5630 bool renderable = true;
5631 ChartBase *referenceChart =
5632 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5633 if (referenceChart) {
5634 double chartMaxScale = referenceChart->GetNormalScaleMax(
5635 GetCanvasScaleFactor(), GetCanvasWidth());
5636 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5637 proj = ChartData->GetDBChartProj(ref_db_index);
5638 } else
5639 proj = PROJECTION_MERCATOR;
5640
5641 VPoint.b_MercatorProjectionOverride =
5642 (m_pQuilt->GetnCharts() == 0 || !renderable);
5643
5644 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5645
5646 VPoint.SetProjectionType(proj);
5647 }
5648
5649 // If this quilt will be a perceptible delta from the existing quilt,
5650 // then refresh the entire screen
5651 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5652 // Allow the quilt to adjust the new ViewPort for performance
5653 // optimization This will normally be only a fractional (i.e.
5654 // sub-pixel) adjustment...
5655 if (b_adjust) {
5656 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5657 }
5658
5659 // ChartData->ClearCacheInUseFlags();
5660 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5661
5662 // wxStopWatch sw;
5663
5664#ifdef __ANDROID__
5665 // This is an optimization for panning on touch screen systems.
5666 // The quilt composition is deferred until the OnPaint() message gets
5667 // finally removed and processed from the message queue.
5668 // Takes advantage of the fact that touch-screen pan gestures are
5669 // usually short in distance,
5670 // so not requiring a full quilt rebuild until the pan gesture is
5671 // complete.
5672 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5673 // qDebug() << "Force compose";
5674 m_pQuilt->Compose(VPoint);
5675 } else {
5676 m_pQuilt->Invalidate();
5677 }
5678#else
5679 m_pQuilt->Compose(VPoint);
5680#endif
5681
5682 // printf("comp time %ld\n", sw.Time());
5683
5684 // If the extended chart stack has changed, invalidate any cached
5685 // render bitmap
5686 // if(m_pQuilt->GetXStackHash() != hash1) {
5687 // m_bm_cache_vp.Invalidate();
5688 // InvalidateGL();
5689 // }
5690
5691 ChartData->PurgeCacheUnusedCharts(0.7);
5692
5693 if (b_refresh) Refresh(false);
5694
5695 b_ret = true;
5696 }
5697 }
5698
5699 VPoint.skew = 0.; // Quilting supports 0 Skew
5700 } else if (!g_bopengl) {
5701 OcpnProjType projection = PROJECTION_UNKNOWN;
5702 if (m_singleChart) // viewport projection must match chart projection
5703 // without opengl
5704 projection = m_singleChart->GetChartProjectionType();
5705 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5706 VPoint.SetProjectionType(projection);
5707 }
5708
5709 // Has the Viewport projection changed? If so, invalidate the vp
5710 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5711 m_cache_vp.Invalidate();
5712 InvalidateGL();
5713 }
5714
5715 UpdateCanvasControlBar(); // Refresh the Piano
5716
5717 VPoint.chart_scale = 1.0; // fallback default value
5718
5719 if (VPoint.GetBBox().GetValid()) {
5720 // Update the viewpoint reference scale
5721 if (m_singleChart)
5722 VPoint.ref_scale = m_singleChart->GetNativeScale();
5723 else {
5724#ifdef __ANDROID__
5725 // This is an optimization for panning on touch screen systems.
5726 // See above.
5727 // Quilt might not be fully composed at this point, so for cm93
5728 // the reference scale may not be known.
5729 // In this case, do not update the VP ref_scale.
5730 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5731 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5732 }
5733#else
5734 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5735#endif
5736 }
5737
5738 // Calculate the on-screen displayed actual scale
5739 // by a simple traverse northward from the center point
5740 // of roughly one eighth of the canvas height
5741 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5742
5743 double delta_check =
5744 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5745 delta_check /= 8.;
5746
5747 double check_point = wxMin(89., VPoint.clat);
5748
5749 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5750
5751 double rhumbDist;
5752 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5753 VPoint.clon, 0, &rhumbDist);
5754
5755 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5756 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5757 // Calculate the distance between r1 and r in physical pixels.
5758 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5759 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5760
5761 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5762
5763 // A fall back in case of very high zoom-out, giving delta_y == 0
5764 // which can probably only happen with vector charts
5765 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5766
5767 // Another fallback, for highly zoomed out charts
5768 // This adjustment makes the displayed TrueScale correspond to the
5769 // same algorithm used to calculate the chart zoom-out limit for
5770 // ChartDummy.
5771 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5772
5773 if (m_true_scale_ppm)
5774 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5775 else
5776 VPoint.chart_scale = 1.0;
5777
5778 // Create a nice renderable string
5779 double round_factor = 1000.;
5780 if (VPoint.chart_scale <= 1000.)
5781 round_factor = 10.;
5782 else if (VPoint.chart_scale <= 10000.)
5783 round_factor = 100.;
5784 else if (VPoint.chart_scale <= 100000.)
5785 round_factor = 1000.;
5786
5787 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5788 double retina_coef = 1;
5789#ifdef ocpnUSE_GL
5790#ifdef __WXOSX__
5791 if (g_bopengl) {
5792 retina_coef = GetContentScaleFactor();
5793 }
5794#endif
5795#endif
5796
5797 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5798 // rounded to the nearest 10, 100 or 1000.
5799 //
5800 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5801 // true_scale_display. That does not make sense. The chart scale should be
5802 // the same as the true scale within the limits of the rounding factor.
5803 double true_scale_display =
5804 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5805 wxString text;
5806
5807 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5808
5809 if (m_displayed_scale_factor > 10.0)
5810 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5811 m_displayed_scale_factor);
5812 else if (m_displayed_scale_factor > 1.0)
5813 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5814 m_displayed_scale_factor);
5815 else if (m_displayed_scale_factor > 0.1) {
5816 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5817 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5818 } else if (m_displayed_scale_factor > 0.01) {
5819 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5820 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5821 } else {
5822 text.Printf(
5823 "%s %4.0f (---)", _("Scale"),
5824 true_scale_display); // Generally, no chart, so no chart scale factor
5825 }
5826
5827 m_scaleValue = true_scale_display;
5828 m_scaleText = text;
5829 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5830
5831 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5832 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5833 // Check to see if the text will fit in the StatusBar field...
5834 bool b_noshow = false;
5835 {
5836 int w = 0;
5837 int h;
5838 wxClientDC dc(parent_frame->GetStatusBar());
5839 if (dc.IsOk()) {
5840 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5841 dc.SetFont(*templateFont);
5842 dc.GetTextExtent(text, &w, &h);
5843
5844 // If text is too long for the allocated field, try to reduce the text
5845 // string a bit.
5846 wxRect rect;
5847 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5848 if (w && w > rect.width) {
5849 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5850 }
5851
5852 // Test again...if too big still, then give it up.
5853 dc.GetTextExtent(text, &w, &h);
5854
5855 if (w && w > rect.width) {
5856 b_noshow = true;
5857 }
5858 }
5859 }
5860
5861 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5862 }
5863 }
5864
5865 // Maintain member vLat/vLon
5866 m_vLat = VPoint.clat;
5867 m_vLon = VPoint.clon;
5868
5869 return b_ret;
5870}
5871
5872// Static Icon definitions for some symbols requiring
5873// scaling/rotation/translation Very specific wxDC draw commands are
5874// necessary to properly render these icons...See the code in
5875// ShipDraw()
5876
5877// This icon was adapted and scaled from the S52 Presentation Library
5878// version 3_03.
5879// Symbol VECGND02
5880
5881static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5882
5883// This ownship icon was adapted and scaled from the S52 Presentation
5884// Library version 3_03 Symbol OWNSHP05
5885static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5886 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5887
5888wxColour ChartCanvas::PredColor() {
5889 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5890 // visibility.
5891 if (SHIP_NORMAL == m_ownship_state)
5892 return GetGlobalColor("URED");
5893
5894 else if (SHIP_LOWACCURACY == m_ownship_state)
5895 return GetGlobalColor("YELO1");
5896
5897 return GetGlobalColor("NODTA");
5898}
5899
5900wxColour ChartCanvas::ShipColor() {
5901 // Establish ship color
5902 // It changes color based on GPS and Chart accuracy/availability
5903
5904 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5905
5906 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5907
5908 return GetGlobalColor("URED"); // default is OK
5909}
5910
5911void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5912 wxPoint2DDouble lShipMidPoint) {
5913 dc.SetPen(wxPen(PredColor(), 2));
5914
5915 if (SHIP_NORMAL == m_ownship_state)
5916 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5917 else
5918 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5919
5920 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5921 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5922
5923 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5924 lShipMidPoint.m_y);
5925 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5926 lShipMidPoint.m_y + 12);
5927}
5928
5929void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5930 wxPoint GPSOffsetPixels,
5931 wxPoint2DDouble lGPSPoint) {
5932 // if (m_animationActive) return;
5933 // Develop a uniform length for course predictor line dash length, based on
5934 // physical display size Use this reference length to size all other graphics
5935 // elements
5936 float ref_dim = m_display_size_mm / 24;
5937 ref_dim = wxMin(ref_dim, 12);
5938 ref_dim = wxMax(ref_dim, 6);
5939
5940 wxColour cPred;
5941 cPred.Set(g_cog_predictor_color);
5942 if (cPred == wxNullColour) cPred = PredColor();
5943
5944 // Establish some graphic element line widths dependent on the platform
5945 // display resolution
5946 // double nominal_line_width_pix = wxMax(1.0,
5947 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5948 // not less than 1 pixel
5949 double nominal_line_width_pix = wxMax(
5950 1.0,
5951 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5952
5953 // If the calculated value is greater than the config file spec value, then
5954 // use it.
5955 if (nominal_line_width_pix > g_cog_predictor_width)
5956 g_cog_predictor_width = nominal_line_width_pix;
5957
5958 // Calculate ownship Position Predictor
5959 wxPoint lPredPoint, lHeadPoint;
5960
5961 float pCog = std::isnan(gCog) ? 0 : gCog;
5962 float pSog = std::isnan(gSog) ? 0 : gSog;
5963
5964 double pred_lat, pred_lon;
5965 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5966 &pred_lat, &pred_lon);
5967 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5968
5969 // test to catch the case where COG/HDG line crosses the screen
5970 LLBBox box;
5971
5972 // Should we draw the Head vector?
5973 // Compare the points lHeadPoint and lPredPoint
5974 // If they differ by more than n pixels, and the head vector is valid, then
5975 // render the head vector
5976
5977 float ndelta_pix = 10.;
5978 double hdg_pred_lat, hdg_pred_lon;
5979 bool b_render_hdt = false;
5980 if (!std::isnan(gHdt)) {
5981 // Calculate ownship Heading pointer as a predictor
5982 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5983 &hdg_pred_lon);
5984 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5985 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5986 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5987 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5988 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5989 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5990 }
5991 }
5992
5993 // draw course over ground if they are longer than the ship
5994 wxPoint lShipMidPoint;
5995 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5996 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5997 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5998 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5999
6000 if (lpp >= img_height / 2) {
6001 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6002 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6003 !std::isnan(gSog)) {
6004 // COG Predictor
6005 float dash_length = ref_dim;
6006 wxDash dash_long[2];
6007 dash_long[0] =
6008 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6009 g_cog_predictor_width); // Long dash , in mm <---------+
6010 dash_long[1] = dash_long[0] / 2.0; // Short gap
6011
6012 // On ultra-hi-res displays, do not allow the dashes to be greater than
6013 // 250, since it is defined as (char)
6014 if (dash_length > 250.) {
6015 dash_long[0] = 250. / g_cog_predictor_width;
6016 dash_long[1] = dash_long[0] / 2;
6017 }
6018
6019 wxPen ppPen2(cPred, g_cog_predictor_width,
6020 (wxPenStyle)g_cog_predictor_style);
6021 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6022 ppPen2.SetDashes(2, dash_long);
6023 dc.SetPen(ppPen2);
6024 dc.StrokeLine(
6025 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6026 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6027
6028 if (g_cog_predictor_width > 1) {
6029 float line_width = g_cog_predictor_width / 3.;
6030
6031 wxDash dash_long3[2];
6032 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6033 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6034
6035 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6036 (wxPenStyle)g_cog_predictor_style);
6037 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6038 ppPen3.SetDashes(2, dash_long3);
6039 dc.SetPen(ppPen3);
6040 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6041 lGPSPoint.m_y + GPSOffsetPixels.y,
6042 lPredPoint.x + GPSOffsetPixels.x,
6043 lPredPoint.y + GPSOffsetPixels.y);
6044 }
6045
6046 if (g_cog_predictor_endmarker) {
6047 // Prepare COG predictor endpoint icon
6048 double png_pred_icon_scale_factor = .4;
6049 if (g_ShipScaleFactorExp > 1.0)
6050 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6051 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6052
6053 wxPoint icon[4];
6054
6055 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6056 (float)(lPredPoint.x - lShipMidPoint.x));
6057 cog_rad += (float)PI;
6058
6059 for (int i = 0; i < 4; i++) {
6060 int j = i * 2;
6061 double pxa = (double)(s_png_pred_icon[j]);
6062 double pya = (double)(s_png_pred_icon[j + 1]);
6063
6064 pya *= png_pred_icon_scale_factor;
6065 pxa *= png_pred_icon_scale_factor;
6066
6067 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6068 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6069
6070 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6071 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6072 }
6073
6074 // Render COG endpoint icon
6075 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6076 wxPENSTYLE_SOLID);
6077 dc.SetPen(ppPen1);
6078 dc.SetBrush(wxBrush(cPred));
6079
6080 dc.StrokePolygon(4, icon);
6081 }
6082 }
6083 }
6084
6085 // HDT Predictor
6086 if (b_render_hdt) {
6087 float hdt_dash_length = ref_dim * 0.4;
6088
6089 cPred.Set(g_ownship_HDTpredictor_color);
6090 if (cPred == wxNullColour) cPred = PredColor();
6091 float hdt_width =
6092 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6093 : g_cog_predictor_width * 0.8);
6094 wxDash dash_short[2];
6095 dash_short[0] =
6096 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6097 hdt_width); // Short dash , in mm <---------+
6098 dash_short[1] =
6099 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6100 hdt_width); // Short gap |
6101
6102 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6103 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6104 ppPen2.SetDashes(2, dash_short);
6105
6106 dc.SetPen(ppPen2);
6107 dc.StrokeLine(
6108 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6109 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6110
6111 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6112 dc.SetPen(ppPen1);
6113 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6114
6115 if (g_ownship_HDTpredictor_endmarker) {
6116 double nominal_circle_size_pixels = wxMax(
6117 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6118
6119 // Scale the circle to ChartScaleFactor, slightly softened....
6120 if (g_ShipScaleFactorExp > 1.0)
6121 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6122
6123 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6124 lHeadPoint.y + GPSOffsetPixels.y,
6125 nominal_circle_size_pixels / 2);
6126 }
6127 }
6128
6129 // Draw radar rings if activated
6130 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6131 double factor = 1.00;
6132 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6133 factor = 1 / 1.852;
6134 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6135 if (std::isnan(gSog))
6136 factor = 0.0;
6137 else
6138 factor = gSog / 60;
6139 }
6140 factor *= g_fNavAidRadarRingsStep;
6141
6142 double tlat, tlon;
6143 wxPoint r;
6144 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6145 GetCanvasPointPix(tlat, tlon, &r);
6146
6147 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6148 pow((double)(lGPSPoint.m_y - r.y), 2));
6149 int pix_radius = (int)lpp;
6150
6151 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6152
6153 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6154
6155 dc.SetPen(ppPen1);
6156 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6157
6158 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6159 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6160 }
6161}
6162
6163void ChartCanvas::ComputeShipScaleFactor(
6164 float icon_hdt, int ownShipWidth, int ownShipLength,
6165 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6166 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6167 float screenResolution = m_pix_per_mm;
6168
6169 // Calculate the true ship length in exact pixels
6170 double ship_bow_lat, ship_bow_lon;
6171 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6172 &ship_bow_lat, &ship_bow_lon);
6173 wxPoint lShipBowPoint;
6174 wxPoint2DDouble b_point =
6175 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6176 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6177
6178 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6179 powf((float)(b_point.m_y - a_point.m_y), 2));
6180
6181 // And in mm
6182 float shipLength_mm = shipLength_px / screenResolution;
6183
6184 // Set minimum ownship drawing size
6185 float ownship_min_mm = g_n_ownship_min_mm;
6186 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6187
6188 // Calculate Nautical Miles distance from midships to gps antenna
6189 float hdt_ant = icon_hdt + 180.;
6190 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6191 float dx = g_n_gps_antenna_offset_x / 1852.;
6192 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6193 {
6194 hdt_ant = icon_hdt;
6195 dy = -dy;
6196 }
6197
6198 // If the drawn ship size is going to be clamped, adjust the gps antenna
6199 // offsets
6200 if (shipLength_mm < ownship_min_mm) {
6201 dy /= shipLength_mm / ownship_min_mm;
6202 dx /= shipLength_mm / ownship_min_mm;
6203 }
6204
6205 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6206
6207 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6208 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6209 &ship_mid_lon1);
6210
6211 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6212 &lShipMidPoint);
6213
6214 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6215 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6216
6217 float scale_factor = shipLength_px / ownShipLength;
6218
6219 // Calculate a scale factor that would produce a reasonably sized icon
6220 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6221
6222 // And choose the correct one
6223 scale_factor = wxMax(scale_factor, scale_factor_min);
6224
6225 scale_factor_y = scale_factor;
6226 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6227 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6228}
6229
6230void ChartCanvas::ShipDraw(ocpnDC &dc) {
6231 if (!GetVP().IsValid()) return;
6232
6233 wxPoint GPSOffsetPixels(0, 0);
6234 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6235
6236 // COG/SOG may be undefined in NMEA data stream
6237 float pCog = std::isnan(gCog) ? 0 : gCog;
6238 float pSog = std::isnan(gSog) ? 0 : gSog;
6239
6240 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6241
6242 lShipMidPoint = lGPSPoint;
6243
6244 // Draw the icon rotated to the COG
6245 // or to the Hdt if available
6246 float icon_hdt = pCog;
6247 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6248
6249 // COG may be undefined in NMEA data stream
6250 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6251
6252 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6253 // predictor
6254 double osd_head_lat, osd_head_lon;
6255 wxPoint osd_head_point;
6256
6257 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6258 &osd_head_lon);
6259
6260 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6261
6262 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6263 (float)(osd_head_point.x - lShipMidPoint.m_x));
6264 icon_rad += (float)PI;
6265
6266 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6267
6268 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6269 // nominal size and is just barely outside the viewport ....
6270 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6271
6272 // TODO: fix to include actual size of boat that will be rendered
6273 int img_height = 0;
6274 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6275 if (GetVP().chart_scale >
6276 300000) // According to S52, this should be 50,000
6277 {
6278 ShipDrawLargeScale(dc, lShipMidPoint);
6279 img_height = 20;
6280 } else {
6281 wxImage pos_image;
6282
6283 // Substitute user ownship image if found
6284 if (m_pos_image_user)
6285 pos_image = m_pos_image_user->Copy();
6286 else if (SHIP_NORMAL == m_ownship_state)
6287 pos_image = m_pos_image_red->Copy();
6288 if (SHIP_LOWACCURACY == m_ownship_state)
6289 pos_image = m_pos_image_yellow->Copy();
6290 else if (SHIP_NORMAL != m_ownship_state)
6291 pos_image = m_pos_image_grey->Copy();
6292
6293 // Substitute user ownship image if found
6294 if (m_pos_image_user) {
6295 pos_image = m_pos_image_user->Copy();
6296
6297 if (SHIP_LOWACCURACY == m_ownship_state)
6298 pos_image = m_pos_image_user_yellow->Copy();
6299 else if (SHIP_NORMAL != m_ownship_state)
6300 pos_image = m_pos_image_user_grey->Copy();
6301 }
6302
6303 img_height = pos_image.GetHeight();
6304
6305 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6306 g_OwnShipIconType > 0) // use large ship
6307 {
6308 int ownShipWidth = 22; // Default values from s_ownship_icon
6309 int ownShipLength = 84;
6310 if (g_OwnShipIconType == 1) {
6311 ownShipWidth = pos_image.GetWidth();
6312 ownShipLength = pos_image.GetHeight();
6313 }
6314
6315 float scale_factor_x, scale_factor_y;
6316 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6317 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6318 scale_factor_x, scale_factor_y);
6319
6320 if (g_OwnShipIconType == 1) { // Scaled bitmap
6321 pos_image.Rescale(ownShipWidth * scale_factor_x,
6322 ownShipLength * scale_factor_y,
6323 wxIMAGE_QUALITY_HIGH);
6324 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6325 wxImage rot_image =
6326 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6327
6328 // Simple sharpening algorithm.....
6329 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6330 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6331 if (rot_image.GetAlpha(ip, jp) > 64)
6332 rot_image.SetAlpha(ip, jp, 255);
6333
6334 wxBitmap os_bm(rot_image);
6335
6336 int w = os_bm.GetWidth();
6337 int h = os_bm.GetHeight();
6338 img_height = h;
6339
6340 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6341 lShipMidPoint.m_y - h / 2, true);
6342
6343 // Maintain dirty box,, missing in __WXMSW__ library
6344 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6345 lShipMidPoint.m_y - h / 2);
6346 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6347 lShipMidPoint.m_y - h / 2 + h);
6348 }
6349
6350 else if (g_OwnShipIconType == 2) { // Scaled Vector
6351 wxPoint ownship_icon[10];
6352
6353 for (int i = 0; i < 10; i++) {
6354 int j = i * 2;
6355 float pxa = (float)(s_ownship_icon[j]);
6356 float pya = (float)(s_ownship_icon[j + 1]);
6357 pya *= scale_factor_y;
6358 pxa *= scale_factor_x;
6359
6360 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6361 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6362
6363 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6364 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6365 }
6366
6367 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6368 dc.SetPen(ppPen1);
6369 dc.SetBrush(wxBrush(ShipColor()));
6370
6371 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6372
6373 // draw reference point (midships) cross
6374 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6375 ownship_icon[7].y);
6376 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6377 ownship_icon[9].y);
6378 }
6379
6380 img_height = ownShipLength * scale_factor_y;
6381
6382 // Reference point, where the GPS antenna is
6383 int circle_rad = 3;
6384 if (m_pos_image_user) circle_rad = 1;
6385
6386 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6387 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6388 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6389 } else { // Fixed bitmap icon.
6390 /* non opengl, or suboptimal opengl via ocpndc: */
6391 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6392 wxImage rot_image =
6393 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6394
6395 // Simple sharpening algorithm.....
6396 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6397 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6398 if (rot_image.GetAlpha(ip, jp) > 64)
6399 rot_image.SetAlpha(ip, jp, 255);
6400
6401 wxBitmap os_bm(rot_image);
6402
6403 if (g_ShipScaleFactorExp > 1) {
6404 wxImage scaled_image = os_bm.ConvertToImage();
6405 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6406 1.0; // soften the scale factor a bit
6407 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6408 scaled_image.GetHeight() * factor,
6409 wxIMAGE_QUALITY_HIGH));
6410 }
6411 int w = os_bm.GetWidth();
6412 int h = os_bm.GetHeight();
6413 img_height = h;
6414
6415 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6416 lShipMidPoint.m_y - h / 2, true);
6417
6418 // Reference point, where the GPS antenna is
6419 int circle_rad = 3;
6420 if (m_pos_image_user) circle_rad = 1;
6421
6422 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6423 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6424 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6425
6426 // Maintain dirty box,, missing in __WXMSW__ library
6427 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6428 lShipMidPoint.m_y - h / 2);
6429 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6430 lShipMidPoint.m_y - h / 2 + h);
6431 }
6432 } // ownship draw
6433 }
6434
6435 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6436}
6437
6438/* @ChartCanvas::CalcGridSpacing
6439 **
6440 ** Calculate the major and minor spacing between the lat/lon grid
6441 **
6442 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6443 *window
6444 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6445 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6446 ** @return [void]
6447 */
6448void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6449 float &MinorSpacing) {
6450 // table for calculating the distance between the grids
6451 // [0] view_scale ppm
6452 // [1] spacing between major grid lines in degrees
6453 // [2] spacing between minor grid lines in degrees
6454 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6455 {.000001f, 45.0f, 15.0f},
6456 {.0002f, 30.0f, 10.0f},
6457 {.0003f, 10.0f, 2.0f},
6458 {.0008f, 5.0f, 1.0f},
6459 {.001f, 2.0f, 30.0f / 60.0f},
6460 {.003f, 1.0f, 20.0f / 60.0f},
6461 {.006f, 0.5f, 10.0f / 60.0f},
6462 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6463 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6464 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6465 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6466 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6467 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6468 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6469 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6470
6471 unsigned int tabi;
6472 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6473 if (view_scale_ppm < lltab[tabi][0]) break;
6474 MajorSpacing = lltab[tabi][1]; // major latitude distance
6475 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6476 return;
6477}
6478/* @ChartCanvas::CalcGridText *************************************
6479 **
6480 ** Calculates text to display at the major grid lines
6481 **
6482 ** @param [r] latlon [float] latitude or longitude of grid line
6483 ** @param [r] spacing [float] distance between two major grid lines
6484 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6485 **
6486 ** @return
6487 */
6488
6489wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6490 int deg = (int)fabs(latlon); // degrees
6491 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6492 char postfix;
6493
6494 // calculate postfix letter (NSEW)
6495 if (latlon > 0.0) {
6496 if (bPostfix) {
6497 postfix = 'N';
6498 } else {
6499 postfix = 'E';
6500 }
6501 } else if (latlon < 0.0) {
6502 if (bPostfix) {
6503 postfix = 'S';
6504 } else {
6505 postfix = 'W';
6506 }
6507 } else {
6508 postfix = ' '; // no postfix for equator and greenwich
6509 }
6510 // calculate text, display minutes only if spacing is smaller than one degree
6511
6512 wxString ret;
6513 if (spacing >= 1.0) {
6514 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6515 } else if (spacing >= (1.0 / 60.0)) {
6516 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6517 } else {
6518 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6519 }
6520
6521 return ret;
6522}
6523
6524/* @ChartCanvas::GridDraw *****************************************
6525 **
6526 ** Draws major and minor Lat/Lon Grid on the chart
6527 ** - distance between Grid-lm ines are calculated automatic
6528 ** - major grid lines will be across the whole chart window
6529 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6530 **
6531 ** @param [w] dc [wxDC&] the wx drawing context
6532 **
6533 ** @return [void]
6534 ************************************************************************/
6535void ChartCanvas::GridDraw(ocpnDC &dc) {
6536 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6537
6538 double nlat, elon, slat, wlon;
6539 float lat, lon;
6540 float dlon;
6541 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6542 wxCoord w, h;
6543 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6544 dc.SetPen(GridPen);
6545 if (!m_pgridFont) SetupGridFont();
6546 dc.SetFont(*m_pgridFont);
6547 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6548
6549 w = m_canvas_width;
6550 h = m_canvas_height;
6551
6552 GetCanvasPixPoint(0, 0, nlat,
6553 wlon); // get lat/lon of upper left point of the window
6554 GetCanvasPixPoint(w, h, slat,
6555 elon); // get lat/lon of lower right point of the window
6556 dlon =
6557 elon -
6558 wlon; // calculate how many degrees of longitude are shown in the window
6559 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6560 {
6561 dlon = dlon + 360.0;
6562 }
6563 // calculate distance between latitude grid lines
6564 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6565
6566 // calculate position of first major latitude grid line
6567 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6568
6569 // Draw Major latitude grid lines and text
6570 while (lat < nlat) {
6571 wxPoint r;
6572 wxString st =
6573 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6574 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6575 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6576 dc.DrawText(st, 0, r.y); // draw text
6577 lat = lat + gridlatMajor;
6578
6579 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6580 }
6581
6582 // calculate position of first minor latitude grid line
6583 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6584
6585 // Draw minor latitude grid lines
6586 while (lat < nlat) {
6587 wxPoint r;
6588 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6589 dc.DrawLine(0, r.y, 10, r.y, false);
6590 dc.DrawLine(w - 10, r.y, w, r.y, false);
6591 lat = lat + gridlatMinor;
6592 }
6593
6594 // calculate distance between grid lines
6595 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6596
6597 // calculate position of first major latitude grid line
6598 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6599
6600 // draw major longitude grid lines
6601 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6602 wxPoint r;
6603 wxString st = CalcGridText(lon, gridlonMajor, false);
6604 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6605 dc.DrawLine(r.x, 0, r.x, h, false);
6606 dc.DrawText(st, r.x, 0);
6607 lon = lon + gridlonMajor;
6608 if (lon > 180.0) {
6609 lon = lon - 360.0;
6610 }
6611
6612 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6613 }
6614
6615 // calculate position of first minor longitude grid line
6616 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6617 // draw minor longitude grid lines
6618 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6619 wxPoint r;
6620 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6621 dc.DrawLine(r.x, 0, r.x, 10, false);
6622 dc.DrawLine(r.x, h - 10, r.x, h, false);
6623 lon = lon + gridlonMinor;
6624 if (lon > 180.0) {
6625 lon = lon - 360.0;
6626 }
6627 }
6628}
6629
6630void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6631 if (0 ) {
6632 double blat, blon, tlat, tlon;
6633 wxPoint r;
6634
6635 int x_origin = m_bDisplayGrid ? 60 : 20;
6636 int y_origin = m_canvas_height - 50;
6637
6638 float dist;
6639 int count;
6640 wxPen pen1, pen2;
6641
6642 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6643 {
6644 dist = 10.0;
6645 count = 5;
6646 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6647 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6648 } else // Draw 1 mile scale as SCALEB10
6649 {
6650 dist = 1.0;
6651 count = 10;
6652 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6653 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6654 }
6655
6656 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6657 double rotation = -VPoint.rotation;
6658
6659 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6660 GetCanvasPointPix(tlat, tlon, &r);
6661 int l1 = (y_origin - r.y) / count;
6662
6663 for (int i = 0; i < count; i++) {
6664 int y = l1 * i;
6665 if (i & 1)
6666 dc.SetPen(pen1);
6667 else
6668 dc.SetPen(pen2);
6669
6670 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6671 }
6672 } else {
6673 double blat, blon, tlat, tlon;
6674
6675 int x_origin = 5.0 * GetPixPerMM();
6676 int chartbar_height = GetChartbarHeight();
6677 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6678 // if (style->chartStatusWindowTransparent)
6679 // chartbar_height = 0;
6680 int y_origin = m_canvas_height - chartbar_height - 5;
6681#ifdef __WXOSX__
6682 if (!g_bopengl)
6683 y_origin =
6684 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6685#endif
6686
6687 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6688 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6689
6690 double d;
6691 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6692 d /= 2;
6693
6694 int unit = g_iDistanceFormat;
6695 if (d < .5 &&
6696 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6697 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6698
6699 // nice number
6700 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6701 float places = floor(logdist), rem = logdist - places;
6702 dist = pow(10, places);
6703
6704 if (rem < .2)
6705 dist /= 5;
6706 else if (rem < .5)
6707 dist /= 2;
6708
6709 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6710 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6711 double rotation = -VPoint.rotation;
6712
6713 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6714 &tlat, &tlon);
6715 wxPoint r;
6716 GetCanvasPointPix(tlat, tlon, &r);
6717 int l1 = r.x - x_origin;
6718
6719 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6720 12); // Store this for later reference
6721
6722 dc.SetPen(pen1);
6723
6724 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6725 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6726 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6727
6728 if (!m_pgridFont) SetupGridFont();
6729 dc.SetFont(*m_pgridFont);
6730 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6731 int w, h;
6732 dc.GetTextExtent(s, &w, &h);
6733 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6734 if (g_bopengl) {
6735 w /= dpi_factor;
6736 h /= dpi_factor;
6737 }
6738 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6739 }
6740}
6741
6742void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6743 // Constants?
6744 double da_min = 2.;
6745 double da_max = 6.;
6746 double ra_min = 0.;
6747 double ra_max = 40.;
6748
6749 wxPen pen_save = dc.GetPen();
6750
6751 wxDateTime now = wxDateTime::Now();
6752
6753 dc.SetPen(pen);
6754
6755 int x0, y0, x1, y1;
6756
6757 x0 = x1 = x + radius; // Start point
6758 y0 = y1 = y;
6759 double angle = 0.;
6760 int i = 0;
6761
6762 while (angle < 360.) {
6763 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6764 angle += da;
6765
6766 if (angle > 360.) angle = 360.;
6767
6768 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6769
6770 double r;
6771 if (i & 1)
6772 r = radius + ra;
6773 else
6774 r = radius - ra;
6775
6776 x1 = (int)(x + cos(angle * PI / 180.) * r);
6777 y1 = (int)(y + sin(angle * PI / 180.) * r);
6778
6779 dc.DrawLine(x0, y0, x1, y1);
6780
6781 x0 = x1;
6782 y0 = y1;
6783
6784 i++;
6785 }
6786
6787 dc.DrawLine(x + radius, y, x1, y1); // closure
6788
6789 dc.SetPen(pen_save);
6790}
6791
6792static bool bAnchorSoundPlaying = false;
6793
6794static void onAnchorSoundFinished(void *ptr) {
6795 o_sound::g_anchorwatch_sound->UnLoad();
6796 bAnchorSoundPlaying = false;
6797}
6798
6799void ChartCanvas::AlertDraw(ocpnDC &dc) {
6800 using namespace o_sound;
6801 // Visual and audio alert for anchorwatch goes here
6802 bool play_sound = false;
6803 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6804 if (AnchorAlertOn1) {
6805 wxPoint TargetPoint;
6807 &TargetPoint);
6808 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6809 TargetPoint.y, 100);
6810 play_sound = true;
6811 }
6812 } else
6813 AnchorAlertOn1 = false;
6814
6815 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6816 if (AnchorAlertOn2) {
6817 wxPoint TargetPoint;
6819 &TargetPoint);
6820 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6821 TargetPoint.y, 100);
6822 play_sound = true;
6823 }
6824 } else
6825 AnchorAlertOn2 = false;
6826
6827 if (play_sound) {
6828 if (!bAnchorSoundPlaying) {
6829 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6830 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6831 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6832 if (g_anchorwatch_sound->IsOk()) {
6833 bAnchorSoundPlaying = true;
6834 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6835 g_anchorwatch_sound->Play();
6836 }
6837 }
6838 }
6839}
6840
6841void ChartCanvas::UpdateShips() {
6842 // Get the rectangle in the current dc which bounds the "ownship" symbol
6843
6844 wxClientDC dc(this);
6845 if (!dc.IsOk()) return;
6846
6847 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6848 if (!test_bitmap.IsOk()) return;
6849
6850 wxMemoryDC temp_dc(test_bitmap);
6851
6852 temp_dc.ResetBoundingBox();
6853 temp_dc.DestroyClippingRegion();
6854 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6855
6856 // Draw the ownship on the temp_dc
6857 ocpnDC ocpndc = ocpnDC(temp_dc);
6858 ShipDraw(ocpndc);
6859
6860 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6861 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6862 if (p) {
6863 wxPoint px;
6864 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6865 ocpndc.CalcBoundingBox(px.x, px.y);
6866 }
6867 }
6868
6869 ship_draw_rect =
6870 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6871 temp_dc.MaxY() - temp_dc.MinY());
6872
6873 wxRect own_ship_update_rect = ship_draw_rect;
6874
6875 if (!own_ship_update_rect.IsEmpty()) {
6876 // The required invalidate rectangle is the union of the last drawn
6877 // rectangle and this drawn rectangle
6878 own_ship_update_rect.Union(ship_draw_last_rect);
6879 own_ship_update_rect.Inflate(2);
6880 }
6881
6882 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6883
6884 ship_draw_last_rect = ship_draw_rect;
6885
6886 temp_dc.SelectObject(wxNullBitmap);
6887}
6888
6889void ChartCanvas::UpdateAlerts() {
6890 // Get the rectangle in the current dc which bounds the detected Alert
6891 // targets
6892
6893 // Use this dc
6894 wxClientDC dc(this);
6895
6896 // Get dc boundary
6897 int sx, sy;
6898 dc.GetSize(&sx, &sy);
6899
6900 // Need a bitmap
6901 wxBitmap test_bitmap(sx, sy, -1);
6902
6903 // Create a memory DC
6904 wxMemoryDC temp_dc;
6905 temp_dc.SelectObject(test_bitmap);
6906
6907 temp_dc.ResetBoundingBox();
6908 temp_dc.DestroyClippingRegion();
6909 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6910
6911 // Draw the Alert Targets on the temp_dc
6912 ocpnDC ocpndc = ocpnDC(temp_dc);
6913 AlertDraw(ocpndc);
6914
6915 // Retrieve the drawing extents
6916 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6917 temp_dc.MaxX() - temp_dc.MinX(),
6918 temp_dc.MaxY() - temp_dc.MinY());
6919
6920 if (!alert_rect.IsEmpty())
6921 alert_rect.Inflate(2); // clear all drawing artifacts
6922
6923 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6924 // The required invalidate rectangle is the union of the last drawn
6925 // rectangle and this drawn rectangle
6926 wxRect alert_update_rect = alert_draw_rect;
6927 alert_update_rect.Union(alert_rect);
6928
6929 // Invalidate the rectangular region
6930 RefreshRect(alert_update_rect, false);
6931 }
6932
6933 // Save this rectangle for next time
6934 alert_draw_rect = alert_rect;
6935
6936 temp_dc.SelectObject(wxNullBitmap); // clean up
6937}
6938
6939void ChartCanvas::UpdateAIS() {
6940 if (!g_pAIS) return;
6941
6942 // Get the rectangle in the current dc which bounds the detected AIS targets
6943
6944 // Use this dc
6945 wxClientDC dc(this);
6946
6947 // Get dc boundary
6948 int sx, sy;
6949 dc.GetSize(&sx, &sy);
6950
6951 wxRect ais_rect;
6952
6953 // How many targets are there?
6954
6955 // If more than "some number", it will be cheaper to refresh the entire
6956 // screen than to build update rectangles for each target.
6957 if (g_pAIS->GetTargetList().size() > 10) {
6958 ais_rect = wxRect(0, 0, sx, sy); // full screen
6959 } else {
6960 // Need a bitmap
6961 wxBitmap test_bitmap(sx, sy, -1);
6962
6963 // Create a memory DC
6964 wxMemoryDC temp_dc;
6965 temp_dc.SelectObject(test_bitmap);
6966
6967 temp_dc.ResetBoundingBox();
6968 temp_dc.DestroyClippingRegion();
6969 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6970
6971 // Draw the AIS Targets on the temp_dc
6972 ocpnDC ocpndc = ocpnDC(temp_dc);
6973 AISDraw(ocpndc, GetVP(), this);
6974 AISDrawAreaNotices(ocpndc, GetVP(), this);
6975
6976 // Retrieve the drawing extents
6977 ais_rect =
6978 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6979 temp_dc.MaxY() - temp_dc.MinY());
6980
6981 if (!ais_rect.IsEmpty())
6982 ais_rect.Inflate(2); // clear all drawing artifacts
6983
6984 temp_dc.SelectObject(wxNullBitmap); // clean up
6985 }
6986
6987 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6988 // The required invalidate rectangle is the union of the last drawn
6989 // rectangle and this drawn rectangle
6990 wxRect ais_update_rect = ais_draw_rect;
6991 ais_update_rect.Union(ais_rect);
6992
6993 // Invalidate the rectangular region
6994 RefreshRect(ais_update_rect, false);
6995 }
6996
6997 // Save this rectangle for next time
6998 ais_draw_rect = ais_rect;
6999}
7000
7001void ChartCanvas::ToggleCPAWarn() {
7002 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7003 wxString mess;
7004 if (g_bCPAWarn) {
7005 g_bTCPA_Max = true;
7006 mess = _("ON");
7007 } else {
7008 g_bTCPA_Max = false;
7009 mess = _("OFF");
7010 }
7011 // Print to status bar if available.
7012 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7013 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7014 } else {
7015 if (!g_AisFirstTimeUse) {
7016 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7017 _("CPA") + " " + mess, 4, 4);
7018 }
7019 }
7020}
7021
7022void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7023
7024void ChartCanvas::OnSize(wxSizeEvent &event) {
7025 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7026 // GetClientSize returns the size of the canvas area in logical pixels.
7027 GetClientSize(&m_canvas_width, &m_canvas_height);
7028
7029#ifdef __WXOSX__
7030 // Support scaled HDPI displays.
7031 m_displayScale = GetContentScaleFactor();
7032#endif
7033
7034 // Convert to physical pixels.
7035 m_canvas_width *= m_displayScale;
7036 m_canvas_height *= m_displayScale;
7037
7038 // Resize the current viewport
7039 VPoint.pix_width = m_canvas_width;
7040 VPoint.pix_height = m_canvas_height;
7041 VPoint.SetPixelScale(m_displayScale);
7042
7043 // Get some canvas metrics
7044
7045 // Rescale to current value, in order to rebuild VPoint data
7046 // structures for new canvas size
7048
7049 m_absolute_min_scale_ppm =
7050 m_canvas_width /
7051 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7052
7053 // Inform the parent Frame that I am being resized...
7054 gFrame->ProcessCanvasResize();
7055
7056 // if MUIBar is active, size the bar
7057 // if(g_useMUI && !m_muiBar){ // rebuild if
7058 // necessary
7059 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7060 // m_muiBarHOSize = m_muiBar->GetSize();
7061 // }
7062
7063 if (m_muiBar) {
7064 SetMUIBarPosition();
7065 UpdateFollowButtonState();
7066 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7067 }
7068
7069 // Set up the scroll margins
7070 xr_margin = m_canvas_width * 95 / 100;
7071 xl_margin = m_canvas_width * 5 / 100;
7072 yt_margin = m_canvas_height * 5 / 100;
7073 yb_margin = m_canvas_height * 95 / 100;
7074
7075 if (m_pQuilt)
7076 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7077
7078 // Resize the scratch BM
7079 delete pscratch_bm;
7080 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7081 m_brepaint_piano = true;
7082
7083 // Resize the Route Calculation BM
7084 m_dc_route.SelectObject(wxNullBitmap);
7085 delete proute_bm;
7086 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7087 m_dc_route.SelectObject(*proute_bm);
7088
7089 // Resize the saved Bitmap
7090 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7091
7092 // Resize the working Bitmap
7093 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7094
7095 // Rescale again, to capture all the changes for new canvas size
7097
7098#ifdef ocpnUSE_GL
7099 if (/*g_bopengl &&*/ m_glcc) {
7100 // FIXME (dave) This can go away?
7101 m_glcc->OnSize(event);
7102 }
7103#endif
7104
7105 FormatPianoKeys();
7106 // Invalidate the whole window
7107 ReloadVP();
7108}
7109
7110void ChartCanvas::ProcessNewGUIScale() {
7111 // m_muiBar->Hide();
7112 delete m_muiBar;
7113 m_muiBar = 0;
7114
7115 CreateMUIBar();
7116}
7117
7118void ChartCanvas::CreateMUIBar() {
7119 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7120 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7121 m_muiBar->SetColorScheme(m_cs);
7122 m_muiBarHOSize = m_muiBar->m_size;
7123 }
7124
7125 if (m_muiBar) {
7126 // We need to update the m_bENCGroup flag, not least for the initial
7127 // creation of a MUIBar
7128 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7129
7130 SetMUIBarPosition();
7131 UpdateFollowButtonState();
7132 m_muiBar->UpdateDynamicValues();
7133 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7134 }
7135}
7136
7137void ChartCanvas::SetMUIBarPosition() {
7138 // if MUIBar is active, size the bar
7139 if (m_muiBar) {
7140 // We estimate the piano width based on the canvas width
7141 int pianoWidth = GetClientSize().x * 0.6f;
7142 // If the piano already exists, we can use its exact width
7143 // if(m_Piano)
7144 // pianoWidth = m_Piano->GetWidth();
7145
7146 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7147 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7148 delete m_muiBar;
7149 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7150 m_muiBar->SetColorScheme(m_cs);
7151 }
7152 }
7153
7154 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7155 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7156 delete m_muiBar;
7157 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7158 m_muiBar->SetColorScheme(m_cs);
7159 }
7160 }
7161
7162 m_muiBar->SetBestPosition();
7163 }
7164}
7165
7166void ChartCanvas::DestroyMuiBar() {
7167 if (m_muiBar) {
7168 delete m_muiBar;
7169 m_muiBar = NULL;
7170 }
7171}
7172
7173void ChartCanvas::ShowCompositeInfoWindow(
7174 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7175 if (n_charts > 0) {
7176 if (NULL == m_pCIWin) {
7177 m_pCIWin = new ChInfoWin(this);
7178 m_pCIWin->Hide();
7179 }
7180
7181 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7182 wxString s;
7183
7184 s = _("Composite of ");
7185
7186 wxString s1;
7187 s1.Printf("%d ", n_charts);
7188 if (n_charts > 1)
7189 s1 += _("charts");
7190 else
7191 s1 += _("chart");
7192 s += s1;
7193 s += '\n';
7194
7195 s1.Printf(_("Chart scale"));
7196 s1 += ": ";
7197 wxString s2;
7198 s2.Printf("1:%d\n", scale);
7199 s += s1;
7200 s += s2;
7201
7202 s1 = _("Zoom in for more information");
7203 s += s1;
7204 s += '\n';
7205
7206 int char_width = s1.Length();
7207 int char_height = 3;
7208
7209 if (g_bChartBarEx) {
7210 s += '\n';
7211 int j = 0;
7212 for (int i : index_vector) {
7213 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7214 wxString path = cte.GetFullSystemPath();
7215 s += path;
7216 s += '\n';
7217 char_height++;
7218 char_width = wxMax(char_width, path.Length());
7219 if (j++ >= 9) break;
7220 }
7221 if (j >= 9) {
7222 s += " .\n .\n .\n";
7223 char_height += 3;
7224 }
7225 s += '\n';
7226 char_height += 1;
7227
7228 char_width += 4; // Fluff
7229 }
7230
7231 m_pCIWin->SetString(s);
7232
7233 m_pCIWin->FitToChars(char_width, char_height);
7234
7235 wxPoint p;
7236 p.x = x / GetContentScaleFactor();
7237 if ((p.x + m_pCIWin->GetWinSize().x) >
7238 (m_canvas_width / GetContentScaleFactor()))
7239 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7240 m_pCIWin->GetWinSize().x) /
7241 2; // centered
7242
7243 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7244 4 - m_pCIWin->GetWinSize().y;
7245
7246 m_pCIWin->dbIndex = 0;
7247 m_pCIWin->chart_scale = 0;
7248 m_pCIWin->SetPosition(p);
7249 m_pCIWin->SetBitmap();
7250 m_pCIWin->Refresh();
7251 m_pCIWin->Show();
7252 }
7253 } else {
7254 HideChartInfoWindow();
7255 }
7256}
7257
7258void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7259 if (dbIndex >= 0) {
7260 if (NULL == m_pCIWin) {
7261 m_pCIWin = new ChInfoWin(this);
7262 m_pCIWin->Hide();
7263 }
7264
7265 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7266 wxString s;
7267 ChartBase *pc = NULL;
7268
7269 // TOCTOU race but worst case will reload chart.
7270 // need to lock it or the background spooler may evict charts in
7271 // OpenChartFromDBAndLock
7272 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7273 pc = ChartData->OpenChartFromDBAndLock(
7274 dbIndex, FULL_INIT); // this must come from cache
7275
7276 int char_width, char_height;
7277 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7278 if (pc) ChartData->UnLockCacheChart(dbIndex);
7279
7280 m_pCIWin->SetString(s);
7281 m_pCIWin->FitToChars(char_width, char_height);
7282
7283 wxPoint p;
7284 p.x = x / GetContentScaleFactor();
7285 if ((p.x + m_pCIWin->GetWinSize().x) >
7286 (m_canvas_width / GetContentScaleFactor()))
7287 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7288 m_pCIWin->GetWinSize().x) /
7289 2; // centered
7290
7291 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7292 4 - m_pCIWin->GetWinSize().y;
7293
7294 m_pCIWin->dbIndex = dbIndex;
7295 m_pCIWin->SetPosition(p);
7296 m_pCIWin->SetBitmap();
7297 m_pCIWin->Refresh();
7298 m_pCIWin->Show();
7299 }
7300 } else {
7301 HideChartInfoWindow();
7302 }
7303}
7304
7305void ChartCanvas::HideChartInfoWindow() {
7306 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7307 m_pCIWin->Hide();
7308 m_pCIWin->Destroy();
7309 m_pCIWin = NULL;
7310
7311#ifdef __ANDROID__
7312 androidForceFullRepaint();
7313#endif
7314 }
7315}
7316
7317void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7318 wxMouseEvent ev(wxEVT_MOTION);
7319 ev.m_x = mouse_x;
7320 ev.m_y = mouse_y;
7321 ev.m_leftDown = mouse_leftisdown;
7322
7323 wxEvtHandler *evthp = GetEventHandler();
7324
7325 ::wxPostEvent(evthp, ev);
7326}
7327
7328void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7329 if ((m_panx_target_final - m_panx_target_now) ||
7330 (m_pany_target_final - m_pany_target_now)) {
7331 DoTimedMovementTarget();
7332 } else
7333 DoTimedMovement();
7334}
7335
7336void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7337
7338bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7339 int delta) {
7340 if (m_disable_edge_pan) return false;
7341
7342 bool bft = false;
7343 int pan_margin = m_canvas_width * margin / 100;
7344 int pan_timer_set = 200;
7345 double pan_delta = GetVP().pix_width * delta / 100;
7346 int pan_x = 0;
7347 int pan_y = 0;
7348
7349 if (x > m_canvas_width - pan_margin) {
7350 bft = true;
7351 pan_x = pan_delta;
7352 }
7353
7354 else if (x < pan_margin) {
7355 bft = true;
7356 pan_x = -pan_delta;
7357 }
7358
7359 if (y < pan_margin) {
7360 bft = true;
7361 pan_y = -pan_delta;
7362 }
7363
7364 else if (y > m_canvas_height - pan_margin) {
7365 bft = true;
7366 pan_y = pan_delta;
7367 }
7368
7369 // Of course, if dragging, and the mouse left button is not down, we must
7370 // stop the event injection
7371 if (bdragging) {
7372 if (!g_btouch) {
7373 wxMouseState state = ::wxGetMouseState();
7374#if wxCHECK_VERSION(3, 0, 0)
7375 if (!state.LeftIsDown())
7376#else
7377 if (!state.LeftDown())
7378#endif
7379 bft = false;
7380 }
7381 }
7382 if ((bft) && !pPanTimer->IsRunning()) {
7383 PanCanvas(pan_x, pan_y);
7384 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7385 return true;
7386 }
7387
7388 // This mouse event must not be due to pan timer event injector
7389 // Mouse is out of the pan zone, so prevent any orphan event injection
7390 if ((!bft) && pPanTimer->IsRunning()) {
7391 pPanTimer->Stop();
7392 }
7393
7394 return (false);
7395}
7396
7397// Look for waypoints at the current position.
7398// Used to determine what a mouse event should act on.
7399
7400void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7401 bool setBeingEdited) {
7402 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7403 m_pRoutePointEditTarget = NULL;
7404 m_pFoundPoint = NULL;
7405
7406 SelectItem *pFind = NULL;
7407 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7408 SelectableItemList SelList = pSelect->FindSelectionList(
7409 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7410 for (SelectItem *pFind : SelList) {
7411 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7412
7413 // Get an array of all routes using this point
7414 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7415 // TODO: delete m_pEditRouteArray after use?
7416
7417 // Use route array to determine actual visibility for the point
7418 bool brp_viz = false;
7419 if (m_pEditRouteArray) {
7420 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7421 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7422 if (pr->IsVisible()) {
7423 brp_viz = true;
7424 break;
7425 }
7426 }
7427 } else
7428 brp_viz = frp->IsVisible(); // isolated point
7429
7430 if (brp_viz) {
7431 // Use route array to rubberband all affected routes
7432 if (m_pEditRouteArray) // Editing Waypoint as part of route
7433 {
7434 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7435 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7436 pr->m_bIsBeingEdited = setBeingEdited;
7437 }
7438 m_bRouteEditing = setBeingEdited;
7439 } else // editing Mark
7440 {
7441 frp->m_bRPIsBeingEdited = setBeingEdited;
7442 m_bMarkEditing = setBeingEdited;
7443 }
7444
7445 m_pRoutePointEditTarget = frp;
7446 m_pFoundPoint = pFind;
7447 break; // out of the while(node)
7448 }
7449 } // for (SelectItem...
7450}
7451std::shared_ptr<HostApi121::PiPointContext>
7452ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7453 // General Right Click
7454 // Look for selectable objects
7455 double slat, slon;
7456 GetCanvasPixPoint(x, y, slat, slon);
7457
7458 SelectItem *pFindAIS;
7459 SelectItem *pFindRP;
7460 SelectItem *pFindRouteSeg;
7461 SelectItem *pFindTrackSeg;
7462 SelectItem *pFindCurrent = NULL;
7463 SelectItem *pFindTide = NULL;
7464
7465 // Get all the selectable things at the selected point
7466 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7467 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7468 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7469 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7470 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7471
7472 if (m_bShowCurrent)
7473 pFindCurrent =
7474 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7475
7476 if (m_bShowTide) // look for tide stations
7477 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7478
7479 int seltype = 0;
7480
7481 // Try for AIS targets first
7482 int FoundAIS_MMSI = 0;
7483 if (pFindAIS) {
7484 FoundAIS_MMSI = pFindAIS->GetUserData();
7485
7486 // Make sure the target data is available
7487 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7488 seltype |= SELTYPE_AISTARGET;
7489 }
7490
7491 // Now the various Route Parts
7492
7493 RoutePoint *FoundRoutePoint = NULL;
7494 Route *SelectedRoute = NULL;
7495
7496 if (pFindRP) {
7497 RoutePoint *pFirstVizPoint = NULL;
7498 RoutePoint *pFoundActiveRoutePoint = NULL;
7499 RoutePoint *pFoundVizRoutePoint = NULL;
7500 Route *pSelectedActiveRoute = NULL;
7501 Route *pSelectedVizRoute = NULL;
7502
7503 // There is at least one routepoint, so get the whole list
7504 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7505 SelectableItemList SelList =
7506 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7507 for (SelectItem *pFindSel : SelList) {
7508 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7509
7510 // Get an array of all routes using this point
7511 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7512
7513 // Use route array (if any) to determine actual visibility for this point
7514 bool brp_viz = false;
7515 if (proute_array) {
7516 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7517 Route *pr = (Route *)proute_array->Item(ir);
7518 if (pr->IsVisible()) {
7519 brp_viz = true;
7520 break;
7521 }
7522 }
7523 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7524 // but still exists as a waypoint
7525 brp_viz = prp->IsVisible(); // so treat as isolated point
7526
7527 } else
7528 brp_viz = prp->IsVisible(); // isolated point
7529
7530 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7531
7532 // Use route array to choose the appropriate route
7533 // Give preference to any active route, otherwise select the first visible
7534 // route in the array for this point
7535 if (proute_array) {
7536 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7537 Route *pr = (Route *)proute_array->Item(ir);
7538 if (pr->m_bRtIsActive) {
7539 pSelectedActiveRoute = pr;
7540 pFoundActiveRoutePoint = prp;
7541 break;
7542 }
7543 }
7544
7545 if (NULL == pSelectedVizRoute) {
7546 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7547 Route *pr = (Route *)proute_array->Item(ir);
7548 if (pr->IsVisible()) {
7549 pSelectedVizRoute = pr;
7550 pFoundVizRoutePoint = prp;
7551 break;
7552 }
7553 }
7554 }
7555
7556 delete proute_array;
7557 }
7558 }
7559
7560 // Now choose the "best" selections
7561 if (pFoundActiveRoutePoint) {
7562 FoundRoutePoint = pFoundActiveRoutePoint;
7563 SelectedRoute = pSelectedActiveRoute;
7564 } else if (pFoundVizRoutePoint) {
7565 FoundRoutePoint = pFoundVizRoutePoint;
7566 SelectedRoute = pSelectedVizRoute;
7567 } else
7568 // default is first visible point in list
7569 FoundRoutePoint = pFirstVizPoint;
7570
7571 if (SelectedRoute) {
7572 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7573 } else if (FoundRoutePoint) {
7574 seltype |= SELTYPE_MARKPOINT;
7575 }
7576
7577 // Highlight the selected point, to verify the proper right click selection
7578#if 0
7579 if (m_pFoundRoutePoint) {
7580 m_pFoundRoutePoint->m_bPtIsSelected = true;
7581 wxRect wp_rect;
7582 RoutePointGui(*m_pFoundRoutePoint)
7583 .CalculateDCRect(m_dc_route, this, &wp_rect);
7584 RefreshRect(wp_rect, true);
7585 }
7586#endif
7587 }
7588
7589 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7590 // routes But call the popup handler with identifier appropriate to the type
7591 if (pFindRouteSeg) // there is at least one select item
7592 {
7593 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7594 SelectableItemList SelList =
7595 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7596
7597 if (NULL == SelectedRoute) // the case where a segment only is selected
7598 {
7599 // Choose the first visible route containing segment in the list
7600 for (SelectItem *pFindSel : SelList) {
7601 Route *pr = (Route *)pFindSel->m_pData3;
7602 if (pr->IsVisible()) {
7603 SelectedRoute = pr;
7604 break;
7605 }
7606 }
7607 }
7608
7609 if (SelectedRoute) {
7610 if (NULL == FoundRoutePoint)
7611 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7612
7613 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7614 seltype |= SELTYPE_ROUTESEGMENT;
7615 }
7616 }
7617
7618 if (pFindTrackSeg) {
7619 m_pSelectedTrack = NULL;
7620 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7621 SelectableItemList SelList =
7622 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7623
7624 // Choose the first visible track containing segment in the list
7625 for (SelectItem *pFindSel : SelList) {
7626 Track *pt = (Track *)pFindSel->m_pData3;
7627 if (pt->IsVisible()) {
7628 m_pSelectedTrack = pt;
7629 break;
7630 }
7631 }
7632 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7633 }
7634
7635 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7636
7637 // Populate the return struct
7638 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7639 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7640 rstruct->object_ident = "";
7641
7642 if (seltype == SELTYPE_AISTARGET) {
7643 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7644 wxString val;
7645 val.Printf("%d", FoundAIS_MMSI);
7646 rstruct->object_ident = val.ToStdString();
7647 } else if (seltype & SELTYPE_MARKPOINT) {
7648 if (FoundRoutePoint) {
7649 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7650 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7651 }
7652 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7653 if (SelectedRoute) {
7654 rstruct->object_type =
7655 HostApi121::PiContextObjectType::kObjectRoutesegment;
7656 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7657 }
7658 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7659 if (m_pSelectedTrack) {
7660 rstruct->object_type =
7661 HostApi121::PiContextObjectType::kObjectTracksegment;
7662 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7663 }
7664 }
7665
7666 return rstruct;
7667}
7668
7669void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7670 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7671 singleClickEventIsValid = false;
7672 m_DoubleClickTimer->Stop();
7673}
7674
7675bool leftIsDown;
7676
7677bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7678 if (!m_bChartDragging && !m_bDrawingRoute) {
7679 /*
7680 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7681 * mouse event coordinates are in logical pixels.
7682 */
7683 if (m_Compass && m_Compass->IsShown()) {
7684 wxRect logicalRect = m_Compass->GetLogicalRect();
7685 bool isInCompass = logicalRect.Contains(event.GetPosition());
7686 if (isInCompass || m_mouseWasInCompass) {
7687 if (m_Compass->MouseEvent(event)) {
7688 cursor_region = CENTER;
7689 if (!g_btouch) SetCanvasCursor(event);
7690 m_mouseWasInCompass = isInCompass;
7691 return true;
7692 }
7693 }
7694 m_mouseWasInCompass = isInCompass;
7695 }
7696
7697 if (m_notification_button && m_notification_button->IsShown()) {
7698 wxRect logicalRect = m_notification_button->GetLogicalRect();
7699 bool isinButton = logicalRect.Contains(event.GetPosition());
7700 if (isinButton) {
7701 SetCursor(*pCursorArrow);
7702 if (event.LeftDown()) HandleNotificationMouseClick();
7703 return true;
7704 }
7705 }
7706
7707 if (MouseEventToolbar(event)) return true;
7708
7709 if (MouseEventChartBar(event)) return true;
7710
7711 if (MouseEventMUIBar(event)) return true;
7712
7713 if (MouseEventIENCBar(event)) return true;
7714 }
7715 return false;
7716}
7717
7718void ChartCanvas::HandleNotificationMouseClick() {
7719 if (!m_NotificationsList) {
7720 m_NotificationsList = new NotificationsList(this);
7721
7722 // calculate best size for Notification list
7723 m_NotificationsList->RecalculateSize();
7724 m_NotificationsList->Hide();
7725 }
7726
7727 if (m_NotificationsList->IsShown()) {
7728 m_NotificationsList->Hide();
7729 } else {
7730 m_NotificationsList->RecalculateSize();
7731 m_NotificationsList->ReloadNotificationList();
7732 m_NotificationsList->Show();
7733 }
7734}
7735bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7736 if (!g_bShowChartBar) return false;
7737
7738 if (!m_Piano->MouseEvent(event)) return false;
7739
7740 cursor_region = CENTER;
7741 if (!g_btouch) SetCanvasCursor(event);
7742 return true;
7743}
7744
7745bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7746 if (!IsPrimaryCanvas()) return false;
7747
7748 if (g_MainToolbar) {
7749 if (!g_MainToolbar->MouseEvent(event))
7750 return false;
7751 else
7752 g_MainToolbar->RefreshToolbar();
7753 }
7754
7755 cursor_region = CENTER;
7756 if (!g_btouch) SetCanvasCursor(event);
7757 return true;
7758}
7759
7760bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7761 if (!IsPrimaryCanvas()) return false;
7762
7763 if (g_iENCToolbar) {
7764 if (!g_iENCToolbar->MouseEvent(event))
7765 return false;
7766 else {
7767 g_iENCToolbar->RefreshToolbar();
7768 return true;
7769 }
7770 }
7771 return false;
7772}
7773
7774bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7775 if (m_muiBar) {
7776 if (!m_muiBar->MouseEvent(event)) return false;
7777 }
7778
7779 cursor_region = CENTER;
7780 if (!g_btouch) SetCanvasCursor(event);
7781 if (m_muiBar)
7782 return true;
7783 else
7784 return false;
7785}
7786
7787bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7788 int x, y;
7789
7790 bool bret = false;
7791
7792 event.GetPosition(&x, &y);
7793
7794 x *= m_displayScale;
7795 y *= m_displayScale;
7796
7797 m_MouseDragging = event.Dragging();
7798
7799 // Some systems produce null drag events, where the pointer position has not
7800 // changed from the previous value. Detect this case, and abort further
7801 // processing (FS#1748)
7802#ifdef __WXMSW__
7803 if (event.Dragging()) {
7804 if ((x == mouse_x) && (y == mouse_y)) return true;
7805 }
7806#endif
7807
7808 mouse_x = x;
7809 mouse_y = y;
7810 mouse_leftisdown = event.LeftDown();
7812
7813 // Establish the event region
7814 cursor_region = CENTER;
7815
7816 int chartbar_height = GetChartbarHeight();
7817
7818 if (m_Compass && m_Compass->IsShown() &&
7819 m_Compass->GetRect().Contains(event.GetPosition())) {
7820 cursor_region = CENTER;
7821 } else if (x > xr_margin) {
7822 cursor_region = MID_RIGHT;
7823 } else if (x < xl_margin) {
7824 cursor_region = MID_LEFT;
7825 } else if (y > yb_margin - chartbar_height &&
7826 y < m_canvas_height - chartbar_height) {
7827 cursor_region = MID_TOP;
7828 } else if (y < yt_margin) {
7829 cursor_region = MID_BOT;
7830 } else {
7831 cursor_region = CENTER;
7832 }
7833
7834 if (!g_btouch) SetCanvasCursor(event);
7835
7836 // Protect from leftUp's coming from event handlers in child
7837 // windows who return focus to the canvas.
7838 leftIsDown = event.LeftDown();
7839
7840#ifndef __WXOSX__
7841 if (event.LeftDown()) {
7842 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7843 // The menu bar is temporarily visible due to alt having been pressed.
7844 // Clicking will hide it, and do nothing else.
7845 g_bTempShowMenuBar = false;
7846 parent_frame->ApplyGlobalSettings(false);
7847 return (true);
7848 }
7849 }
7850#endif
7851
7852 // Update modifiers here; some window managers never send the key event
7853 m_modkeys = 0;
7854 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7855 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7856
7857#ifdef __WXMSW__
7858 // TODO Test carefully in other platforms, remove ifdef....
7859 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7860 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7861#endif
7862
7863 event.SetEventObject(this);
7864 if (SendMouseEventToPlugins(event))
7865 return (true); // PlugIn did something, and does not want the canvas to
7866 // do anything else
7867
7868 // Capture LeftUp's and time them, unless it already came from the timer.
7869
7870 // Detect end of chart dragging
7871 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7872 StartChartDragInertia();
7873 }
7874
7875 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7876 !singleClickEventIsValid) {
7877 // Ignore the second LeftUp after the DClick.
7878 if (m_DoubleClickTimer->IsRunning()) {
7879 m_DoubleClickTimer->Stop();
7880 return (true);
7881 }
7882
7883 // Save the event for later running if there is no DClick.
7884 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7885 singleClickEvent = event;
7886 singleClickEventIsValid = true;
7887 return (true);
7888 }
7889
7890 // This logic is necessary on MSW to handle the case where
7891 // a context (right-click) menu is dismissed without action
7892 // by clicking on the chart surface.
7893 // We need to avoid an unintentional pan by eating some clicks...
7894#ifdef __WXMSW__
7895 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7896 if (g_click_stop > 0) {
7897 g_click_stop--;
7898 return (true);
7899 }
7900 }
7901#endif
7902
7903 // Kick off the Rotation control timer
7904 if (GetUpMode() == COURSE_UP_MODE) {
7905 m_b_rot_hidef = false;
7906 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7907 } else
7908 pRotDefTimer->Stop();
7909
7910 // Retrigger the route leg / AIS target popup timer
7911 bool bRoll = !g_btouch;
7912#ifdef __ANDROID__
7913 bRoll = g_bRollover;
7914#endif
7915 if (bRoll) {
7916 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7917 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7918 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7919 m_RolloverPopupTimer.Start(
7920 10,
7921 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7922 else
7923 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7924 }
7925
7926 // Retrigger the cursor tracking timer
7927 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7928
7929// Show cursor position on Status Bar, if present
7930// except for GTK, under which status bar updates are very slow
7931// due to Update() call.
7932// In this case, as a workaround, update the status window
7933// after an interval timer (pCurTrackTimer) pops, which will happen
7934// whenever the mouse has stopped moving for specified interval.
7935// See the method OnCursorTrackTimerEvent()
7936#if !defined(__WXGTK__) && !defined(__WXQT__)
7937 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7938#endif
7939
7940 // Send the current cursor lat/lon to all PlugIns requesting it
7941 if (g_pi_manager) {
7942 // Occasionally, MSW will produce nonsense events on right click....
7943 // This results in an error in cursor geo position, so we skip this case
7944 if ((x >= 0) && (y >= 0))
7945 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7946 }
7947
7948 if (!g_btouch) {
7949 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7950 wxPoint p = ClientToScreen(wxPoint(x, y));
7951 }
7952 }
7953
7954 if (1 ) {
7955 // Route Creation Rubber Banding
7956 if (m_routeState >= 2) {
7957 r_rband.x = x;
7958 r_rband.y = y;
7959 m_bDrawingRoute = true;
7960
7961 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7962 Refresh(false);
7963 }
7964
7965 // Measure Tool Rubber Banding
7966 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7967 r_rband.x = x;
7968 r_rband.y = y;
7969 m_bDrawingRoute = true;
7970
7971 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7972 Refresh(false);
7973 }
7974 }
7975 return bret;
7976}
7977
7978int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7979 // On general Right Click
7980 // Look for selectable objects
7981 double slat = lat;
7982 double slon = lon;
7983
7984#if defined(__WXMAC__) || defined(__ANDROID__)
7985 wxScreenDC sdc;
7986 ocpnDC dc(sdc);
7987#else
7988 wxClientDC cdc(GetParent());
7989 ocpnDC dc(cdc);
7990#endif
7991
7992 SelectItem *pFindAIS;
7993 SelectItem *pFindRP;
7994 SelectItem *pFindRouteSeg;
7995 SelectItem *pFindTrackSeg;
7996 SelectItem *pFindCurrent = NULL;
7997 SelectItem *pFindTide = NULL;
7998
7999 // Deselect any current objects
8000 if (m_pSelectedRoute) {
8001 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8002 m_pSelectedRoute->DeSelectRoute();
8003#ifdef ocpnUSE_GL
8004 if (g_bopengl && m_glcc) {
8005 InvalidateGL();
8006 Update();
8007 } else
8008#endif
8009 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8010 }
8011
8012 if (m_pFoundRoutePoint) {
8013 m_pFoundRoutePoint->m_bPtIsSelected = false;
8014 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8015 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8016 }
8017
8020 if (g_btouch && m_pRoutePointEditTarget) {
8021 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8022 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8023 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8024 }
8025
8026 // Get all the selectable things at the cursor
8027 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8028 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8029 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8030 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8031 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8032
8033 if (m_bShowCurrent)
8034 pFindCurrent =
8035 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8036
8037 if (m_bShowTide) // look for tide stations
8038 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8039
8040 int seltype = 0;
8041
8042 // Try for AIS targets first
8043 if (pFindAIS) {
8044 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8045
8046 // Make sure the target data is available
8047 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8048 seltype |= SELTYPE_AISTARGET;
8049 }
8050
8051 // Now examine the various Route parts
8052
8053 m_pFoundRoutePoint = NULL;
8054 if (pFindRP) {
8055 RoutePoint *pFirstVizPoint = NULL;
8056 RoutePoint *pFoundActiveRoutePoint = NULL;
8057 RoutePoint *pFoundVizRoutePoint = NULL;
8058 Route *pSelectedActiveRoute = NULL;
8059 Route *pSelectedVizRoute = NULL;
8060
8061 // There is at least one routepoint, so get the whole list
8062 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8063 SelectableItemList SelList =
8064 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8065 for (SelectItem *pFindSel : SelList) {
8066 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8067
8068 // Get an array of all routes using this point
8069 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8070
8071 // Use route array (if any) to determine actual visibility for this point
8072 bool brp_viz = false;
8073 if (proute_array) {
8074 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8075 Route *pr = (Route *)proute_array->Item(ir);
8076 if (pr->IsVisible()) {
8077 brp_viz = true;
8078 break;
8079 }
8080 }
8081 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8082 // but still exists as a waypoint
8083 brp_viz = prp->IsVisible(); // so treat as isolated point
8084
8085 } else
8086 brp_viz = prp->IsVisible(); // isolated point
8087
8088 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8089
8090 // Use route array to choose the appropriate route
8091 // Give preference to any active route, otherwise select the first visible
8092 // route in the array for this point
8093 m_pSelectedRoute = NULL;
8094 if (proute_array) {
8095 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8096 Route *pr = (Route *)proute_array->Item(ir);
8097 if (pr->m_bRtIsActive) {
8098 pSelectedActiveRoute = pr;
8099 pFoundActiveRoutePoint = prp;
8100 break;
8101 }
8102 }
8103
8104 if (NULL == pSelectedVizRoute) {
8105 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8106 Route *pr = (Route *)proute_array->Item(ir);
8107 if (pr->IsVisible()) {
8108 pSelectedVizRoute = pr;
8109 pFoundVizRoutePoint = prp;
8110 break;
8111 }
8112 }
8113 }
8114
8115 delete proute_array;
8116 }
8117 }
8118
8119 // Now choose the "best" selections
8120 if (pFoundActiveRoutePoint) {
8121 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8122 m_pSelectedRoute = pSelectedActiveRoute;
8123 } else if (pFoundVizRoutePoint) {
8124 m_pFoundRoutePoint = pFoundVizRoutePoint;
8125 m_pSelectedRoute = pSelectedVizRoute;
8126 } else
8127 // default is first visible point in list
8128 m_pFoundRoutePoint = pFirstVizPoint;
8129
8130 if (m_pSelectedRoute) {
8131 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8132 } else if (m_pFoundRoutePoint) {
8133 seltype |= SELTYPE_MARKPOINT;
8134 }
8135
8136 // Highlight the selected point, to verify the proper right click selection
8137 if (m_pFoundRoutePoint) {
8138 m_pFoundRoutePoint->m_bPtIsSelected = true;
8139 wxRect wp_rect;
8140 RoutePointGui(*m_pFoundRoutePoint)
8141 .CalculateDCRect(m_dc_route, this, &wp_rect);
8142 RefreshRect(wp_rect, true);
8143 }
8144 }
8145
8146 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8147 // routes But call the popup handler with identifier appropriate to the type
8148 if (pFindRouteSeg) // there is at least one select item
8149 {
8150 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8151 SelectableItemList SelList =
8152 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8153
8154 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8155 {
8156 // Choose the first visible route containing segment in the list
8157 for (SelectItem *pFindSel : SelList) {
8158 Route *pr = (Route *)pFindSel->m_pData3;
8159 if (pr->IsVisible()) {
8160 m_pSelectedRoute = pr;
8161 break;
8162 }
8163 }
8164 }
8165
8166 if (m_pSelectedRoute) {
8167 if (NULL == m_pFoundRoutePoint)
8168 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8169
8170 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8171 if (m_pSelectedRoute->m_bRtIsSelected) {
8172#ifdef ocpnUSE_GL
8173 if (g_bopengl && m_glcc) {
8174 InvalidateGL();
8175 Update();
8176 } else
8177#endif
8178 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8179 }
8180 seltype |= SELTYPE_ROUTESEGMENT;
8181 }
8182 }
8183
8184 if (pFindTrackSeg) {
8185 m_pSelectedTrack = NULL;
8186 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8187 SelectableItemList SelList =
8188 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8189
8190 // Choose the first visible track containing segment in the list
8191 for (SelectItem *pFindSel : SelList) {
8192 Track *pt = (Track *)pFindSel->m_pData3;
8193 if (pt->IsVisible()) {
8194 m_pSelectedTrack = pt;
8195 break;
8196 }
8197 }
8198 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8199 }
8200
8201#if 0 // disable tide and current graph on right click
8202 {
8203 if (pFindCurrent) {
8204 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8205 seltype |= SELTYPE_CURRENTPOINT;
8206 }
8207
8208 else if (pFindTide) {
8209 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8210 seltype |= SELTYPE_TIDEPOINT;
8211 }
8212 }
8213#endif
8214
8215 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8216
8217 return seltype;
8218}
8219
8220IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8221 // There may be multiple current entries at the same point.
8222 // For example, there often is a current substation (with directions
8223 // specified) co-located with its master. We want to select the
8224 // substation, so that the direction will be properly indicated on the
8225 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8226 // substation)
8227 IDX_entry *pIDX_best_candidate;
8228
8229 SelectItem *pFind = NULL;
8230 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8231 SelectableItemList SelList =
8232 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8233
8234 // Default is first entry
8235 pFind = *SelList.begin();
8236 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8237
8238 auto node = SelList.begin();
8239 if (SelList.size() > 1) {
8240 for (++node; node != SelList.end(); ++node) {
8241 pFind = *node;
8242 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8243 if (pIDX_candidate->IDX_type == 'c') {
8244 pIDX_best_candidate = pIDX_candidate;
8245 break;
8246 }
8247 } // while (node)
8248 } else {
8249 pFind = *SelList.begin();
8250 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8251 }
8252
8253 return pIDX_best_candidate;
8254}
8255void ChartCanvas::CallPopupMenu(int x, int y) {
8256 last_drag.x = x;
8257 last_drag.y = y;
8258 if (m_routeState) { // creating route?
8259 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8260 return;
8261 }
8262
8264
8265 // If tide or current point is selected, then show the TC dialog immediately
8266 // without context menu
8267 if (SELTYPE_CURRENTPOINT == seltype) {
8268 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8269 Refresh(false);
8270 return;
8271 }
8272
8273 if (SELTYPE_TIDEPOINT == seltype) {
8274 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8275 Refresh(false);
8276 return;
8277 }
8278
8279 InvokeCanvasMenu(x, y, seltype);
8280
8281 // Clean up if not deleted in InvokeCanvasMenu
8282 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8283 m_pSelectedRoute->m_bRtIsSelected = false;
8284 }
8285
8286 m_pSelectedRoute = NULL;
8287
8288 if (m_pFoundRoutePoint) {
8289 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8290 m_pFoundRoutePoint->m_bPtIsSelected = false;
8291 }
8292 m_pFoundRoutePoint = NULL;
8293
8294 Refresh(true);
8295 // Refresh(false); // needed for MSW, not GTK Why??
8296}
8297
8298bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8299 // For now just bail out completely if the point clicked is not on the chart
8300 if (std::isnan(m_cursor_lat)) return false;
8301
8302 // Mouse Clicks
8303 bool ret = false; // return true if processed
8304
8305 int x, y, mx, my;
8306 event.GetPosition(&x, &y);
8307 mx = x;
8308 my = y;
8309
8310 // Calculate meaningful SelectRadius
8311 float SelectRadius;
8312 SelectRadius = g_Platform->GetSelectRadiusPix() /
8313 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8314
8316 // We start with Double Click processing. The first left click just starts a
8317 // timer and is remembered, then we actually do something if there is a
8318 // LeftDClick. If there is, the two single clicks are ignored.
8319
8320 if (event.LeftDClick() && (cursor_region == CENTER)) {
8321 m_DoubleClickTimer->Start();
8322 singleClickEventIsValid = false;
8323
8324 double zlat, zlon;
8326 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8327
8328 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8329 if (m_bShowAIS) {
8330 SelectItem *pFindAIS;
8331 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8332
8333 if (pFindAIS) {
8334 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8335 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8336 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8337 }
8338 return true;
8339 }
8340 }
8341
8342 SelectableItemList rpSelList =
8343 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8344 bool b_onRPtarget = false;
8345 for (SelectItem *pFind : rpSelList) {
8346 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8347 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8348 b_onRPtarget = true;
8349 break;
8350 }
8351 }
8352
8353 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8354
8355 // Get and honor the plugin API ContextMenuMask
8356 std::unique_ptr<HostApi> host_api = GetHostApi();
8357 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8358
8359 if (m_pRoutePointEditTarget) {
8360 if (b_onRPtarget) {
8361 if ((api_121->GetContextMenuMask() &
8362 api_121->kContextMenuDisableWaypoint))
8363 return true;
8364 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8365 return true;
8366 } else {
8367 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8368 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8369 if (g_btouch)
8370 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8371 wxRect wp_rect;
8372 RoutePointGui(*m_pRoutePointEditTarget)
8373 .CalculateDCRect(m_dc_route, this, &wp_rect);
8374 m_pRoutePointEditTarget = NULL; // cancel selection
8375 RefreshRect(wp_rect, true);
8376 return true;
8377 }
8378 } else {
8379 auto node = rpSelList.begin();
8380 if (node != rpSelList.end()) {
8381 SelectItem *pFind = *node;
8382 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8383 if (frp) {
8384 wxArrayPtrVoid *proute_array =
8386
8387 // Use route array (if any) to determine actual visibility for this
8388 // point
8389 bool brp_viz = false;
8390 if (proute_array) {
8391 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8392 Route *pr = (Route *)proute_array->Item(ir);
8393 if (pr->IsVisible()) {
8394 brp_viz = true;
8395 break;
8396 }
8397 }
8398 delete proute_array;
8399 if (!brp_viz &&
8400 frp->IsShared()) // is not visible as part of route, but
8401 // still exists as a waypoint
8402 brp_viz = frp->IsVisible(); // so treat as isolated point
8403 } else
8404 brp_viz = frp->IsVisible(); // isolated point
8405
8406 if (brp_viz) {
8407 if ((api_121->GetContextMenuMask() &
8408 api_121->kContextMenuDisableWaypoint))
8409 return true;
8410
8411 ShowMarkPropertiesDialog(frp);
8412 return true;
8413 }
8414 }
8415 }
8416 }
8417
8418 SelectItem *cursorItem;
8419
8420 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8421 if (cursorItem) {
8422 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8423 return true;
8424 Route *pr = (Route *)cursorItem->m_pData3;
8425 if (pr->IsVisible()) {
8426 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8427 return true;
8428 }
8429 }
8430
8431 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8432 if (cursorItem) {
8433 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8434 return true;
8435 Track *pt = (Track *)cursorItem->m_pData3;
8436 if (pt->IsVisible()) {
8437 ShowTrackPropertiesDialog(pt);
8438 return true;
8439 }
8440 }
8441
8442 // Tide and current points
8443 SelectItem *pFindCurrent = NULL;
8444 SelectItem *pFindTide = NULL;
8445
8446 if (m_bShowCurrent) { // look for current stations
8447 pFindCurrent =
8448 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8449 if (pFindCurrent) {
8450 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8451 // Check for plugin graphic override
8452 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8453 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8454 PlugInContainer *pic = plugin_array->Item(i);
8455 if (pic->m_enabled && pic->m_init_state &&
8456 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8457 if (ptcmgr) {
8458 TCClickInfo info;
8459 if (m_pIDXCandidate) {
8460 info.point_type = CURRENT_STATION;
8461 info.index = m_pIDXCandidate->IDX_rec_num;
8462 info.name = m_pIDXCandidate->IDX_station_name;
8463 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8464 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8465 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8466 };
8467 auto plugin =
8468 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8469 if (plugin) plugin->OnTideCurrentClick(info);
8470 return true;
8471 }
8472 }
8473 }
8474 }
8475
8476 // Default action
8477 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8478 Refresh(false);
8479 return true;
8480 }
8481 }
8482
8483 if (m_bShowTide) { // look for tide stations
8484 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8485 if (pFindTide) {
8486 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8487 // Check for plugin graphic override
8488 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8489 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8490 PlugInContainer *pic = plugin_array->Item(i);
8491 if (pic->m_enabled && pic->m_init_state &&
8492 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8493 if (ptcmgr) {
8494 TCClickInfo info;
8495 if (m_pIDXCandidate) {
8496 info.point_type = TIDE_STATION;
8497 info.index = m_pIDXCandidate->IDX_rec_num;
8498 info.name = m_pIDXCandidate->IDX_station_name;
8499 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8500 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8501 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8502 };
8503 auto plugin =
8504 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8505 if (plugin) plugin->OnTideCurrentClick(info);
8506 return true;
8507 }
8508 }
8509 }
8510 }
8511
8512 // Default action
8513 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8514 Refresh(false);
8515 return true;
8516 }
8517 }
8518
8519 // Found no object to act on, so show chart info.
8520 ShowObjectQueryWindow(x, y, zlat, zlon);
8521 return true;
8522 }
8523
8525 if (event.LeftDown()) {
8526 // This really should not be needed, but....
8527 // on Windows, when using wxAUIManager, sometimes the focus is lost
8528 // when clicking into another pane, e.g.the AIS target list, and then back
8529 // to this pane. Oddly, some mouse events are not lost, however. Like this
8530 // one....
8531 SetFocus();
8532
8533 last_drag.x = mx;
8534 last_drag.y = my;
8535 leftIsDown = true;
8536
8537 if (!g_btouch) {
8538 if (m_routeState) // creating route?
8539 {
8540 double rlat, rlon;
8541 bool appending = false;
8542 bool inserting = false;
8543 Route *tail = 0;
8544
8545 SetCursor(*pCursorPencil);
8546 rlat = m_cursor_lat;
8547 rlon = m_cursor_lon;
8548
8549 m_bRouteEditing = true;
8550
8551 if (m_routeState == 1) {
8552 m_pMouseRoute = new Route();
8553 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8554 pRouteList->push_back(m_pMouseRoute);
8555 r_rband.x = x;
8556 r_rband.y = y;
8557 }
8558
8559 // Check to see if there is a nearby point which may be reused
8560 RoutePoint *pMousePoint = NULL;
8561
8562 // Calculate meaningful SelectRadius
8563 double nearby_radius_meters =
8564 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8565
8566 RoutePoint *pNearbyPoint =
8567 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8568 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8569 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8570 wxArrayPtrVoid *proute_array =
8571 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8572
8573 // Use route array (if any) to determine actual visibility for this
8574 // point
8575 bool brp_viz = false;
8576 if (proute_array) {
8577 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8578 Route *pr = (Route *)proute_array->Item(ir);
8579 if (pr->IsVisible()) {
8580 brp_viz = true;
8581 break;
8582 }
8583 }
8584 delete proute_array;
8585 if (!brp_viz &&
8586 pNearbyPoint->IsShared()) // is not visible as part of route,
8587 // but still exists as a waypoint
8588 brp_viz =
8589 pNearbyPoint->IsVisible(); // so treat as isolated point
8590 } else
8591 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8592
8593 if (brp_viz) {
8594 wxString msg = _("Use nearby waypoint?");
8595 // Don't add a mark without name to the route. Name it if needed
8596 const bool noname(pNearbyPoint->GetName() == "");
8597 if (noname) {
8598 msg =
8599 _("Use nearby nameless waypoint and name it M with"
8600 " a unique number?");
8601 }
8602 // Avoid route finish on focus change for message dialog
8603 m_FinishRouteOnKillFocus = false;
8604 int dlg_return =
8605 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8606 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8607 m_FinishRouteOnKillFocus = true;
8608 if (dlg_return == wxID_YES) {
8609 if (noname) {
8610 if (m_pMouseRoute) {
8611 int last_wp_num = m_pMouseRoute->GetnPoints();
8612 // AP-ECRMB will truncate to 6 characters
8613 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8614 wxString wp_name = wxString::Format(
8615 "M%002i-%s", last_wp_num + 1, guid_short);
8616 pNearbyPoint->SetName(wp_name);
8617 } else
8618 pNearbyPoint->SetName("WPXX");
8619 }
8620 pMousePoint = pNearbyPoint;
8621
8622 // Using existing waypoint, so nothing to delete for undo.
8623 if (m_routeState > 1)
8624 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8625 Undo_HasParent, NULL);
8626
8627 tail =
8628 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8629 bool procede = false;
8630 if (tail) {
8631 procede = true;
8632 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8633 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8634 procede = false;
8635 }
8636
8637 if (procede) {
8638 int dlg_return;
8639 m_FinishRouteOnKillFocus = false;
8640 if (m_routeState ==
8641 1) { // first point in new route, preceeding route to be
8642 // added? Not touch case
8643
8644 wxString dmsg =
8645 _("Insert first part of this route in the new route?");
8646 if (tail->GetIndexOf(pMousePoint) ==
8647 tail->GetnPoints()) // Starting on last point of another
8648 // route?
8649 dmsg = _("Insert this route in the new route?");
8650
8651 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8652 dlg_return = OCPNMessageBox(
8653 this, dmsg, _("OpenCPN Route Create"),
8654 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8655 m_FinishRouteOnKillFocus = true;
8656
8657 if (dlg_return == wxID_YES) {
8658 inserting = true; // part of the other route will be
8659 // preceeding the new route
8660 }
8661 }
8662 } else {
8663 wxString dmsg =
8664 _("Append last part of this route to the new route?");
8665 if (tail->GetIndexOf(pMousePoint) == 1)
8666 dmsg = _(
8667 "Append this route to the new route?"); // Picking the
8668 // first point
8669 // of another
8670 // route?
8671
8672 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8673 dlg_return = OCPNMessageBox(
8674 this, dmsg, _("OpenCPN Route Create"),
8675 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8676 m_FinishRouteOnKillFocus = true;
8677
8678 if (dlg_return == wxID_YES) {
8679 appending = true; // part of the other route will be
8680 // appended to the new route
8681 }
8682 }
8683 }
8684 }
8685
8686 // check all other routes to see if this point appears in any
8687 // other route If it appears in NO other route, then it should e
8688 // considered an isolated mark
8689 if (!FindRouteContainingWaypoint(pMousePoint))
8690 pMousePoint->SetShared(true);
8691 }
8692 }
8693 }
8694
8695 if (NULL == pMousePoint) { // need a new point
8696 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8697 "", wxEmptyString);
8698 pMousePoint->SetNameShown(false);
8699
8700 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8701
8702 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8703
8704 if (m_routeState > 1)
8705 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8706 Undo_IsOrphanded, NULL);
8707 }
8708
8709 if (m_pMouseRoute) {
8710 if (m_routeState == 1) {
8711 // First point in the route.
8712 m_pMouseRoute->AddPoint(pMousePoint);
8713 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8714 } else {
8715 if (m_pMouseRoute->m_NextLegGreatCircle) {
8716 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8717 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8718 &rhumbBearing, &rhumbDist);
8719 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8720 rlat, &gcDist, &gcBearing, NULL);
8721 double gcDistNM = gcDist / 1852.0;
8722
8723 // Empirically found expression to get reasonable route segments.
8724 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8725 pow(rhumbDist - gcDistNM - 1, 0.5);
8726
8727 wxString msg;
8728 msg << _("For this leg the Great Circle route is ")
8729 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8730 << _(" shorter than rhumbline.\n\n")
8731 << _("Would you like include the Great Circle routing points "
8732 "for this leg?");
8733
8734 m_FinishRouteOnKillFocus = false;
8735 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8736 // does not fully capture mouse
8737
8738 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8739 wxYES_NO | wxNO_DEFAULT);
8740
8741 m_disable_edge_pan = false;
8742 m_FinishRouteOnKillFocus = true;
8743
8744 if (answer == wxID_YES) {
8745 RoutePoint *gcPoint;
8746 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8747 wxRealPoint gcCoord;
8748
8749 for (int i = 1; i <= segmentCount; i++) {
8750 double fraction = (double)i * (1.0 / (double)segmentCount);
8751 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8752 gcDist * fraction, gcBearing,
8753 &gcCoord.x, &gcCoord.y, NULL);
8754
8755 if (i < segmentCount) {
8756 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8757 wxEmptyString);
8758 gcPoint->SetNameShown(false);
8759 // pConfig->AddNewWayPoint(gcPoint, -1);
8760 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8761
8762 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8763 gcPoint);
8764 } else {
8765 gcPoint = pMousePoint; // Last point, previously exsisting!
8766 }
8767
8768 m_pMouseRoute->AddPoint(gcPoint);
8769 pSelect->AddSelectableRouteSegment(
8770 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8771 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8772 prevGcPoint = gcPoint;
8773 }
8774
8775 undo->CancelUndoableAction(true);
8776
8777 } else {
8778 m_pMouseRoute->AddPoint(pMousePoint);
8779 pSelect->AddSelectableRouteSegment(
8780 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8781 pMousePoint, m_pMouseRoute);
8782 undo->AfterUndoableAction(m_pMouseRoute);
8783 }
8784 } else {
8785 // Ordinary rhumblinesegment.
8786 m_pMouseRoute->AddPoint(pMousePoint);
8787 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8788 rlon, m_prev_pMousePoint,
8789 pMousePoint, m_pMouseRoute);
8790 undo->AfterUndoableAction(m_pMouseRoute);
8791 }
8792 }
8793 }
8794 m_prev_rlat = rlat;
8795 m_prev_rlon = rlon;
8796 m_prev_pMousePoint = pMousePoint;
8797 if (m_pMouseRoute)
8798 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8799
8800 m_routeState++;
8801
8802 if (appending ||
8803 inserting) { // Appending a route or making a new route
8804 int connect = tail->GetIndexOf(pMousePoint);
8805 if (connect == 1) {
8806 inserting = false; // there is nothing to insert
8807 appending = true; // so append
8808 }
8809 int length = tail->GetnPoints();
8810
8811 int i;
8812 int start, stop;
8813 if (appending) {
8814 start = connect + 1;
8815 stop = length;
8816 } else { // inserting
8817 start = 1;
8818 stop = connect;
8819 m_pMouseRoute->RemovePoint(
8820 m_pMouseRoute
8821 ->GetLastPoint()); // Remove the first and only point
8822 }
8823 for (i = start; i <= stop; i++) {
8824 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8825 if (m_pMouseRoute)
8826 m_pMouseRoute->m_lastMousePointIndex =
8827 m_pMouseRoute->GetnPoints();
8828 m_routeState++;
8829 gFrame->RefreshAllCanvas();
8830 ret = true;
8831 }
8832 m_prev_rlat =
8833 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8834 m_prev_rlon =
8835 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8836 m_pMouseRoute->FinalizeForRendering();
8837 }
8838 gFrame->RefreshAllCanvas();
8839 ret = true;
8840 }
8841
8842 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8843 {
8844 SetCursor(*pCursorPencil);
8845
8846 if (!m_pMeasureRoute) {
8847 m_pMeasureRoute = new Route();
8848 pRouteList->push_back(m_pMeasureRoute);
8849 }
8850
8851 if (m_nMeasureState == 1) {
8852 r_rband.x = x;
8853 r_rband.y = y;
8854 }
8855
8856 RoutePoint *pMousePoint =
8857 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8858 wxEmptyString, wxEmptyString);
8859 pMousePoint->m_bShowName = false;
8860 pMousePoint->SetShowWaypointRangeRings(false);
8861
8862 m_pMeasureRoute->AddPoint(pMousePoint);
8863
8864 m_prev_rlat = m_cursor_lat;
8865 m_prev_rlon = m_cursor_lon;
8866 m_prev_pMousePoint = pMousePoint;
8867 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8868
8869 m_nMeasureState++;
8870 gFrame->RefreshAllCanvas();
8871 ret = true;
8872 }
8873
8874 else {
8875 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8876 }
8877 } // !g_btouch
8878 else { // g_btouch
8879 m_last_touch_down_pos = event.GetPosition();
8880
8881 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8882 // if near screen edge, pan with injection
8883 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8884 // return;
8885 // }
8886 }
8887 }
8888
8889 if (ret) return true;
8890 }
8891
8892 if (event.Dragging()) {
8893 // in touch screen mode ensure the finger/cursor is on the selected point's
8894 // radius to allow dragging
8895 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8896 if (g_btouch) {
8897 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8898 SelectItem *pFind = NULL;
8899 SelectableItemList SelList = pSelect->FindSelectionList(
8900 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8901 for (SelectItem *pFind : SelList) {
8902 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8903 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8904 }
8905 }
8906
8907 // Check for use of dragHandle
8908 if (m_pRoutePointEditTarget &&
8909 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8910 SelectItem *pFind = NULL;
8911 SelectableItemList SelList = pSelect->FindSelectionList(
8912 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8913 for (SelectItem *pFind : SelList) {
8914 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8915 if (m_pRoutePointEditTarget == frp) {
8916 m_bIsInRadius = true;
8917 break;
8918 }
8919 }
8920
8921 if (!m_dragoffsetSet) {
8922 RoutePointGui(*m_pRoutePointEditTarget)
8923 .PresetDragOffset(this, mouse_x, mouse_y);
8924 m_dragoffsetSet = true;
8925 }
8926 }
8927 }
8928
8929 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8930 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8931
8932 if (NULL == g_pMarkInfoDialog) {
8933 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8934 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8935 DraggingAllowed = false;
8936
8937 if (m_pRoutePointEditTarget &&
8938 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8939 DraggingAllowed = false;
8940
8941 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8942
8943 if (DraggingAllowed) {
8944 if (!undo->InUndoableAction()) {
8945 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8946 Undo_NeedsCopy, m_pFoundPoint);
8947 }
8948
8949 // Get the update rectangle for the union of the un-edited routes
8950 wxRect pre_rect;
8951
8952 if (!g_bopengl && m_pEditRouteArray) {
8953 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8954 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8955 // Need to validate route pointer
8956 // Route may be gone due to drgging close to ownship with
8957 // "Delete On Arrival" state set, as in the case of
8958 // navigating to an isolated waypoint on a temporary route
8959 if (g_pRouteMan->IsRouteValid(pr)) {
8960 wxRect route_rect;
8961 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8962 pre_rect.Union(route_rect);
8963 }
8964 }
8965 }
8966
8967 double new_cursor_lat = m_cursor_lat;
8968 double new_cursor_lon = m_cursor_lon;
8969
8970 if (CheckEdgePan(x, y, true, 5, 2))
8971 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8972
8973 // update the point itself
8974 if (g_btouch) {
8975 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8976 // new_cursor_lat, new_cursor_lon);
8977 RoutePointGui(*m_pRoutePointEditTarget)
8978 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8979 // update the Drag Handle entry in the pSelect list
8980 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8981 m_pRoutePointEditTarget,
8982 SELTYPE_DRAGHANDLE);
8983 m_pFoundPoint->m_slat =
8984 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8985 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8986 } else {
8987 m_pRoutePointEditTarget->m_lat =
8988 new_cursor_lat; // update the RoutePoint entry
8989 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8990 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8991 m_pFoundPoint->m_slat =
8992 new_cursor_lat; // update the SelectList entry
8993 m_pFoundPoint->m_slon = new_cursor_lon;
8994 }
8995
8996 // Update the MarkProperties Dialog, if currently shown
8997 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8998 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8999 g_pMarkInfoDialog->UpdateProperties(true);
9000 }
9001
9002 if (g_bopengl) {
9003 // InvalidateGL();
9004 Refresh(false);
9005 } else {
9006 // Get the update rectangle for the edited route
9007 wxRect post_rect;
9008
9009 if (m_pEditRouteArray) {
9010 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9011 ir++) {
9012 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9013 if (g_pRouteMan->IsRouteValid(pr)) {
9014 wxRect route_rect;
9015 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9016 post_rect.Union(route_rect);
9017 }
9018 }
9019 }
9020
9021 // Invalidate the union region
9022 pre_rect.Union(post_rect);
9023 RefreshRect(pre_rect, false);
9024 }
9025 gFrame->RefreshCanvasOther(this);
9026 m_bRoutePoinDragging = true;
9027 }
9028 ret = true;
9029 } // if Route Editing
9030
9031 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9032 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9033
9034 if (NULL == g_pMarkInfoDialog) {
9035 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9036 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9037 DraggingAllowed = false;
9038
9039 if (m_pRoutePointEditTarget &&
9040 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9041 DraggingAllowed = false;
9042
9043 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9044
9045 if (DraggingAllowed) {
9046 if (!undo->InUndoableAction()) {
9047 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9048 Undo_NeedsCopy, m_pFoundPoint);
9049 }
9050
9051 // The mark may be an anchorwatch
9052 double lpp1 = 0.;
9053 double lpp2 = 0.;
9054 double lppmax;
9055
9056 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9057 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9058 }
9059 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9060 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9061 }
9062 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9063
9064 // Get the update rectangle for the un-edited mark
9065 wxRect pre_rect;
9066 if (!g_bopengl) {
9067 RoutePointGui(*m_pRoutePointEditTarget)
9068 .CalculateDCRect(m_dc_route, this, &pre_rect);
9069 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9070 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9071 (int)(lppmax - (pre_rect.height / 2)));
9072 }
9073
9074 // update the point itself
9075 if (g_btouch) {
9076 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9077 // m_cursor_lat, m_cursor_lon);
9078 RoutePointGui(*m_pRoutePointEditTarget)
9079 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9080 // update the Drag Handle entry in the pSelect list
9081 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9082 m_pRoutePointEditTarget,
9083 SELTYPE_DRAGHANDLE);
9084 m_pFoundPoint->m_slat =
9085 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9086 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9087 } else {
9088 m_pRoutePointEditTarget->m_lat =
9089 m_cursor_lat; // update the RoutePoint entry
9090 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9091 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9092 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9093 m_pFoundPoint->m_slon = m_cursor_lon;
9094 }
9095
9096 // Update the MarkProperties Dialog, if currently shown
9097 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9098 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9099 g_pMarkInfoDialog->UpdateProperties(true);
9100 }
9101
9102 // Invalidate the union region
9103 if (g_bopengl) {
9104 if (!g_btouch) InvalidateGL();
9105 Refresh(false);
9106 } else {
9107 // Get the update rectangle for the edited mark
9108 wxRect post_rect;
9109 RoutePointGui(*m_pRoutePointEditTarget)
9110 .CalculateDCRect(m_dc_route, this, &post_rect);
9111 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9112 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9113 (int)(lppmax - (post_rect.height / 2)));
9114
9115 // Invalidate the union region
9116 pre_rect.Union(post_rect);
9117 RefreshRect(pre_rect, false);
9118 }
9119 gFrame->RefreshCanvasOther(this);
9120 m_bRoutePoinDragging = true;
9121 }
9122 ret = g_btouch ? m_bRoutePoinDragging : true;
9123 }
9124
9125 if (ret) return true;
9126 } // dragging
9127
9128 if (event.LeftUp()) {
9129 bool b_startedit_route = false;
9130 m_dragoffsetSet = false;
9131
9132 if (g_btouch) {
9133 m_bChartDragging = false;
9134 m_bIsInRadius = false;
9135
9136 if (m_routeState) // creating route?
9137 {
9138 if (m_ignore_next_leftup) {
9139 m_ignore_next_leftup = false;
9140 return false;
9141 }
9142
9143 if (m_bedge_pan) {
9144 m_bedge_pan = false;
9145 return false;
9146 }
9147
9148 double rlat, rlon;
9149 bool appending = false;
9150 bool inserting = false;
9151 Route *tail = 0;
9152
9153 rlat = m_cursor_lat;
9154 rlon = m_cursor_lon;
9155
9156 if (m_pRoutePointEditTarget) {
9157 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9158 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9159 if (!g_bopengl) {
9160 wxRect wp_rect;
9161 RoutePointGui(*m_pRoutePointEditTarget)
9162 .CalculateDCRect(m_dc_route, this, &wp_rect);
9163 RefreshRect(wp_rect, true);
9164 }
9165 m_pRoutePointEditTarget = NULL;
9166 }
9167 m_bRouteEditing = true;
9168
9169 if (m_routeState == 1) {
9170 m_pMouseRoute = new Route();
9171 m_pMouseRoute->SetHiLite(50);
9172 pRouteList->push_back(m_pMouseRoute);
9173 r_rband.x = x;
9174 r_rband.y = y;
9175 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9176 }
9177
9178 // Check to see if there is a nearby point which may be reused
9179 RoutePoint *pMousePoint = NULL;
9180
9181 // Calculate meaningful SelectRadius
9182 double nearby_radius_meters =
9183 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9184
9185 RoutePoint *pNearbyPoint =
9186 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9187 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9188 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9189 int dlg_return;
9190#ifndef __WXOSX__
9191 m_FinishRouteOnKillFocus =
9192 false; // Avoid route finish on focus change for message dialog
9193 dlg_return = OCPNMessageBox(
9194 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9195 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9196 m_FinishRouteOnKillFocus = true;
9197#else
9198 dlg_return = wxID_YES;
9199#endif
9200 if (dlg_return == wxID_YES) {
9201 pMousePoint = pNearbyPoint;
9202
9203 // Using existing waypoint, so nothing to delete for undo.
9204 if (m_routeState > 1)
9205 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9206 Undo_HasParent, NULL);
9207 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9208
9209 bool procede = false;
9210 if (tail) {
9211 procede = true;
9212 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9213 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9214 procede = false;
9215 }
9216
9217 if (procede) {
9218 int dlg_return;
9219 m_FinishRouteOnKillFocus = false;
9220 if (m_routeState == 1) { // first point in new route, preceeding
9221 // route to be added? touch case
9222
9223 wxString dmsg =
9224 _("Insert first part of this route in the new route?");
9225 if (tail->GetIndexOf(pMousePoint) ==
9226 tail->GetnPoints()) // Starting on last point of another
9227 // route?
9228 dmsg = _("Insert this route in the new route?");
9229
9230 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9231 dlg_return =
9232 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9233 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9234 m_FinishRouteOnKillFocus = true;
9235
9236 if (dlg_return == wxID_YES) {
9237 inserting = true; // part of the other route will be
9238 // preceeding the new route
9239 }
9240 }
9241 } else {
9242 wxString dmsg =
9243 _("Append last part of this route to the new route?");
9244 if (tail->GetIndexOf(pMousePoint) == 1)
9245 dmsg = _(
9246 "Append this route to the new route?"); // Picking the
9247 // first point of
9248 // another route?
9249
9250 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9251 dlg_return =
9252 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9253 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9254 m_FinishRouteOnKillFocus = true;
9255
9256 if (dlg_return == wxID_YES) {
9257 appending = true; // part of the other route will be
9258 // appended to the new route
9259 }
9260 }
9261 }
9262 }
9263
9264 // check all other routes to see if this point appears in any other
9265 // route If it appears in NO other route, then it should e
9266 // considered an isolated mark
9267 if (!FindRouteContainingWaypoint(pMousePoint))
9268 pMousePoint->SetShared(true);
9269 }
9270 }
9271
9272 if (NULL == pMousePoint) { // need a new point
9273 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9274 "", wxEmptyString);
9275 pMousePoint->SetNameShown(false);
9276
9277 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9278
9279 if (m_routeState > 1)
9280 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9281 Undo_IsOrphanded, NULL);
9282 }
9283
9284 if (m_routeState == 1) {
9285 // First point in the route.
9286 m_pMouseRoute->AddPoint(pMousePoint);
9287 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9288
9289 } else {
9290 if (m_pMouseRoute->m_NextLegGreatCircle) {
9291 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9292 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9293 &rhumbBearing, &rhumbDist);
9294 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9295 &gcDist, &gcBearing, NULL);
9296 double gcDistNM = gcDist / 1852.0;
9297
9298 // Empirically found expression to get reasonable route segments.
9299 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9300 pow(rhumbDist - gcDistNM - 1, 0.5);
9301
9302 wxString msg;
9303 msg << _("For this leg the Great Circle route is ")
9304 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9305 << _(" shorter than rhumbline.\n\n")
9306 << _("Would you like include the Great Circle routing points "
9307 "for this leg?");
9308
9309#ifndef __WXOSX__
9310 m_FinishRouteOnKillFocus = false;
9311 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9312 wxYES_NO | wxNO_DEFAULT);
9313 m_FinishRouteOnKillFocus = true;
9314#else
9315 int answer = wxID_NO;
9316#endif
9317
9318 if (answer == wxID_YES) {
9319 RoutePoint *gcPoint;
9320 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9321 wxRealPoint gcCoord;
9322
9323 for (int i = 1; i <= segmentCount; i++) {
9324 double fraction = (double)i * (1.0 / (double)segmentCount);
9325 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9326 gcDist * fraction, gcBearing,
9327 &gcCoord.x, &gcCoord.y, NULL);
9328
9329 if (i < segmentCount) {
9330 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9331 wxEmptyString);
9332 gcPoint->SetNameShown(false);
9333 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9334 gcPoint);
9335 } else {
9336 gcPoint = pMousePoint; // Last point, previously exsisting!
9337 }
9338
9339 m_pMouseRoute->AddPoint(gcPoint);
9340 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9341
9342 pSelect->AddSelectableRouteSegment(
9343 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9344 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9345 prevGcPoint = gcPoint;
9346 }
9347
9348 undo->CancelUndoableAction(true);
9349
9350 } else {
9351 m_pMouseRoute->AddPoint(pMousePoint);
9352 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9353 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9354 rlon, m_prev_pMousePoint,
9355 pMousePoint, m_pMouseRoute);
9356 undo->AfterUndoableAction(m_pMouseRoute);
9357 }
9358 } else {
9359 // Ordinary rhumblinesegment.
9360 m_pMouseRoute->AddPoint(pMousePoint);
9361 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9362
9363 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9364 rlon, m_prev_pMousePoint,
9365 pMousePoint, m_pMouseRoute);
9366 undo->AfterUndoableAction(m_pMouseRoute);
9367 }
9368 }
9369
9370 m_prev_rlat = rlat;
9371 m_prev_rlon = rlon;
9372 m_prev_pMousePoint = pMousePoint;
9373 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9374
9375 m_routeState++;
9376
9377 if (appending ||
9378 inserting) { // Appending a route or making a new route
9379 int connect = tail->GetIndexOf(pMousePoint);
9380 if (connect == 1) {
9381 inserting = false; // there is nothing to insert
9382 appending = true; // so append
9383 }
9384 int length = tail->GetnPoints();
9385
9386 int i;
9387 int start, stop;
9388 if (appending) {
9389 start = connect + 1;
9390 stop = length;
9391 } else { // inserting
9392 start = 1;
9393 stop = connect;
9394 m_pMouseRoute->RemovePoint(
9395 m_pMouseRoute
9396 ->GetLastPoint()); // Remove the first and only point
9397 }
9398 for (i = start; i <= stop; i++) {
9399 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9400 if (m_pMouseRoute)
9401 m_pMouseRoute->m_lastMousePointIndex =
9402 m_pMouseRoute->GetnPoints();
9403 m_routeState++;
9404 gFrame->RefreshAllCanvas();
9405 ret = true;
9406 }
9407 m_prev_rlat =
9408 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9409 m_prev_rlon =
9410 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9411 m_pMouseRoute->FinalizeForRendering();
9412 }
9413
9414 Refresh(true);
9415 ret = true;
9416 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9417 {
9418 if (m_bedge_pan) {
9419 m_bedge_pan = false;
9420 return false;
9421 }
9422
9423 if (m_ignore_next_leftup) {
9424 m_ignore_next_leftup = false;
9425 return false;
9426 }
9427
9428 if (m_nMeasureState == 1) {
9429 m_pMeasureRoute = new Route();
9430 pRouteList->push_back(m_pMeasureRoute);
9431 r_rband.x = x;
9432 r_rband.y = y;
9433 }
9434
9435 if (m_pMeasureRoute) {
9436 RoutePoint *pMousePoint =
9437 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9438 wxEmptyString, wxEmptyString);
9439 pMousePoint->m_bShowName = false;
9440
9441 m_pMeasureRoute->AddPoint(pMousePoint);
9442
9443 m_prev_rlat = m_cursor_lat;
9444 m_prev_rlon = m_cursor_lon;
9445 m_prev_pMousePoint = pMousePoint;
9446 m_pMeasureRoute->m_lastMousePointIndex =
9447 m_pMeasureRoute->GetnPoints();
9448
9449 m_nMeasureState++;
9450 } else {
9451 CancelMeasureRoute();
9452 }
9453
9454 Refresh(true);
9455 ret = true;
9456 } else {
9457 bool bSelectAllowed = true;
9458 if (NULL == g_pMarkInfoDialog) {
9459 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9460 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9461 bSelectAllowed = false;
9462
9463 // Avoid accidental selection of routepoint if last touchdown started
9464 // a significant chart drag operation
9465 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9466 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9467 significant_drag) ||
9468 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9469 significant_drag)) {
9470 bSelectAllowed = false;
9471 }
9472
9473 /*if this left up happens at the end of a route point dragging and if
9474 the cursor/thumb is on the draghandle icon, not on the point iself a new
9475 selection will select nothing and the drag will never be ended, so the
9476 legs around this point never selectable. At this step we don't need a
9477 new selection, just keep the previoulsly selected and dragged point */
9478 if (m_bRoutePoinDragging) bSelectAllowed = false;
9479
9480 if (bSelectAllowed) {
9481 bool b_was_editing_mark = m_bMarkEditing;
9482 bool b_was_editing_route = m_bRouteEditing;
9483 FindRoutePointsAtCursor(SelectRadius,
9484 true); // Possibly selecting a point in a
9485 // route for later dragging
9486
9487 /*route and a mark points in layer can't be dragged so should't be
9488 * selected and no draghandle icon*/
9489 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9490 m_pRoutePointEditTarget = NULL;
9491
9492 if (!b_was_editing_route) {
9493 if (m_pEditRouteArray) {
9494 b_startedit_route = true;
9495
9496 // Hide the track and route rollover during route point edit, not
9497 // needed, and may be confusing
9498 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9499 m_pTrackRolloverWin->IsActive(false);
9500 }
9501 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9502 m_pRouteRolloverWin->IsActive(false);
9503 }
9504
9505 wxRect pre_rect;
9506 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9507 ir++) {
9508 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9509 // Need to validate route pointer
9510 // Route may be gone due to drgging close to ownship with
9511 // "Delete On Arrival" state set, as in the case of
9512 // navigating to an isolated waypoint on a temporary route
9513 if (g_pRouteMan->IsRouteValid(pr)) {
9514 // pr->SetHiLite(50);
9515 wxRect route_rect;
9516 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9517 pre_rect.Union(route_rect);
9518 }
9519 }
9520 RefreshRect(pre_rect, true);
9521 }
9522 } else {
9523 b_startedit_route = false;
9524 }
9525
9526 // Mark editing in touch mode, left-up event.
9527 if (m_pRoutePointEditTarget) {
9528 if (b_was_editing_mark ||
9529 b_was_editing_route) { // kill previous hilight
9530 if (m_lastRoutePointEditTarget) {
9531 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9532 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9533 RoutePointGui(*m_lastRoutePointEditTarget)
9534 .EnableDragHandle(false);
9535 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9536 SELTYPE_DRAGHANDLE);
9537 }
9538 }
9539
9540 if (m_pRoutePointEditTarget) {
9541 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9542 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9543 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9544 wxPoint2DDouble dragHandlePoint =
9545 RoutePointGui(*m_pRoutePointEditTarget)
9546 .GetDragHandlePoint(this);
9547 pSelect->AddSelectablePoint(
9548 dragHandlePoint.m_y, dragHandlePoint.m_x,
9549 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9550 }
9551 } else { // Deselect everything
9552 if (m_lastRoutePointEditTarget) {
9553 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9554 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9555 RoutePointGui(*m_lastRoutePointEditTarget)
9556 .EnableDragHandle(false);
9557 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9558 SELTYPE_DRAGHANDLE);
9559
9560 // Clear any routes being edited, probably orphans
9561 wxArrayPtrVoid *lastEditRouteArray =
9563 m_lastRoutePointEditTarget);
9564 if (lastEditRouteArray) {
9565 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9566 ir++) {
9567 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9568 if (g_pRouteMan->IsRouteValid(pr)) {
9569 pr->m_bIsBeingEdited = false;
9570 }
9571 }
9572 delete lastEditRouteArray;
9573 }
9574 }
9575 }
9576
9577 // Do the refresh
9578
9579 if (g_bopengl) {
9580 InvalidateGL();
9581 Refresh(false);
9582 } else {
9583 if (m_lastRoutePointEditTarget) {
9584 wxRect wp_rect;
9585 RoutePointGui(*m_lastRoutePointEditTarget)
9586 .CalculateDCRect(m_dc_route, this, &wp_rect);
9587 RefreshRect(wp_rect, true);
9588 }
9589
9590 if (m_pRoutePointEditTarget) {
9591 wxRect wp_rect;
9592 RoutePointGui(*m_pRoutePointEditTarget)
9593 .CalculateDCRect(m_dc_route, this, &wp_rect);
9594 RefreshRect(wp_rect, true);
9595 }
9596 }
9597 }
9598 } // bSelectAllowed
9599
9600 // Check to see if there is a route or AIS target under the cursor
9601 // If so, start the rollover timer which creates the popup
9602 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9603 bool b_start_rollover = false;
9604 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9605 SelectItem *pFind = pSelectAIS->FindSelection(
9606 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9607 if (pFind) b_start_rollover = true;
9608 }
9609
9610 if (!b_start_rollover && !b_startedit_route) {
9611 SelectableItemList SelList = pSelect->FindSelectionList(
9612 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9613 for (SelectItem *pFindSel : SelList) {
9614 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9615 if (pr && pr->IsVisible()) {
9616 b_start_rollover = true;
9617 break;
9618 }
9619 } // while
9620 }
9621
9622 if (!b_start_rollover && !b_startedit_route) {
9623 SelectableItemList SelList = pSelect->FindSelectionList(
9624 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9625 for (SelectItem *pFindSel : SelList) {
9626 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9627 if (tr && tr->IsVisible()) {
9628 b_start_rollover = true;
9629 break;
9630 }
9631 } // while
9632 }
9633
9634 if (b_start_rollover)
9635 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9636 wxTIMER_ONE_SHOT);
9637 Route *tail = 0;
9638 Route *current = 0;
9639 bool appending = false;
9640 bool inserting = false;
9641 int connect = 0;
9642 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9643 // drag
9644 if (m_pRoutePointEditTarget) {
9645 // Check to see if there is a nearby point which may replace the
9646 // dragged one
9647 RoutePoint *pMousePoint = NULL;
9648
9649 int index_last;
9650 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9651 double nearby_radius_meters =
9652 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9653 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9654 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9655 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9656 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9657 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9658 bool duplicate =
9659 false; // ensure we won't create duplicate point in routes
9660 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9661 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9662 ir++) {
9663 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9664 if (pr && pr->pRoutePointList) {
9665 auto *list = pr->pRoutePointList;
9666 auto pos =
9667 std::find(list->begin(), list->end(), pNearbyPoint);
9668 if (pos != list->end()) {
9669 duplicate = true;
9670 break;
9671 }
9672 }
9673 }
9674 }
9675
9676 // Special case:
9677 // Allow "re-use" of a route's waypoints iff it is a simple
9678 // isolated route. This allows, for instance, creation of a closed
9679 // polygon route
9680 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9681
9682 if (!duplicate) {
9683 int dlg_return;
9684 dlg_return =
9685 OCPNMessageBox(this,
9686 _("Replace this RoutePoint by the nearby "
9687 "Waypoint?"),
9688 _("OpenCPN RoutePoint change"),
9689 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9690 if (dlg_return == wxID_YES) {
9691 /*double confirmation if the dragged point has been manually
9692 * created which can be important and could be deleted
9693 * unintentionally*/
9694
9695 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9696 pNearbyPoint);
9697 current =
9698 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9699
9700 if (tail && current && (tail != current)) {
9701 int dlg_return1;
9702 connect = tail->GetIndexOf(pNearbyPoint);
9703 int index_current_route =
9704 current->GetIndexOf(m_pRoutePointEditTarget);
9705 index_last = current->GetIndexOf(current->GetLastPoint());
9706 dlg_return1 = wxID_NO;
9707 if (index_last ==
9708 index_current_route) { // we are dragging the last
9709 // point of the route
9710 if (connect != tail->GetnPoints()) { // anything to do?
9711
9712 wxString dmsg(
9713 _("Last part of route to be appended to dragged "
9714 "route?"));
9715 if (connect == 1)
9716 dmsg =
9717 _("Full route to be appended to dragged route?");
9718
9719 dlg_return1 = OCPNMessageBox(
9720 this, dmsg, _("OpenCPN Route Create"),
9721 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9722 if (dlg_return1 == wxID_YES) {
9723 appending = true;
9724 }
9725 }
9726 } else if (index_current_route ==
9727 1) { // dragging the first point of the route
9728 if (connect != 1) { // anything to do?
9729
9730 wxString dmsg(
9731 _("First part of route to be inserted into dragged "
9732 "route?"));
9733 if (connect == tail->GetnPoints())
9734 dmsg = _(
9735 "Full route to be inserted into dragged route?");
9736
9737 dlg_return1 = OCPNMessageBox(
9738 this, dmsg, _("OpenCPN Route Create"),
9739 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9740 if (dlg_return1 == wxID_YES) {
9741 inserting = true;
9742 }
9743 }
9744 }
9745 }
9746
9747 if (m_pRoutePointEditTarget->IsShared()) {
9748 // dlg_return = wxID_NO;
9749 dlg_return = OCPNMessageBox(
9750 this,
9751 _("Do you really want to delete and replace this "
9752 "WayPoint") +
9753 "\n" + _("which has been created manually?"),
9754 ("OpenCPN RoutePoint warning"),
9755 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9756 }
9757 }
9758 if (dlg_return == wxID_YES) {
9759 pMousePoint = pNearbyPoint;
9760 if (pMousePoint->m_bIsolatedMark) {
9761 pMousePoint->SetShared(true);
9762 }
9763 pMousePoint->m_bIsolatedMark =
9764 false; // definitely no longer isolated
9765 pMousePoint->m_bIsInRoute = true;
9766 }
9767 }
9768 }
9769 }
9770 if (!pMousePoint)
9771 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9772
9773 if (m_pEditRouteArray) {
9774 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9775 ir++) {
9776 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9777 if (g_pRouteMan->IsRouteValid(pr)) {
9778 if (pMousePoint) { // remove the dragged point and insert the
9779 // nearby
9780 auto *list = pr->pRoutePointList;
9781 auto pos = std::find(list->begin(), list->end(),
9782 m_pRoutePointEditTarget);
9783
9784 pSelect->DeleteAllSelectableRoutePoints(pr);
9785 pSelect->DeleteAllSelectableRouteSegments(pr);
9786
9787 pr->pRoutePointList->insert(pos, pMousePoint);
9788 pos = std::find(list->begin(), list->end(),
9789 m_pRoutePointEditTarget);
9790 pr->pRoutePointList->erase(pos);
9791
9792 pSelect->AddAllSelectableRouteSegments(pr);
9793 pSelect->AddAllSelectableRoutePoints(pr);
9794 }
9795 pr->FinalizeForRendering();
9796 pr->UpdateSegmentDistances();
9797 if (m_bRoutePoinDragging) {
9798 // pConfig->UpdateRoute(pr);
9799 NavObj_dB::GetInstance().UpdateRoute(pr);
9800 }
9801 }
9802 }
9803 }
9804
9805 // Update the RouteProperties Dialog, if currently shown
9806 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9807 if (m_pEditRouteArray) {
9808 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9809 ir++) {
9810 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9811 if (g_pRouteMan->IsRouteValid(pr)) {
9812 if (pRoutePropDialog->GetRoute() == pr) {
9813 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9814 }
9815 /* cannot edit track points anyway
9816 else if ( ( NULL !=
9817 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9818 pTrackPropDialog->m_pTrack == pr ) {
9819 pTrackPropDialog->SetTrackAndUpdate(
9820 pr );
9821 }
9822 */
9823 }
9824 }
9825 }
9826 }
9827 if (pMousePoint) { // clear all about the dragged point
9828 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9829 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9830 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9831 // Hide mark properties dialog if open on the replaced point
9832 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9833 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9834 g_pMarkInfoDialog->Hide();
9835
9836 delete m_pRoutePointEditTarget;
9837 m_lastRoutePointEditTarget = NULL;
9838 m_pRoutePointEditTarget = NULL;
9839 undo->AfterUndoableAction(pMousePoint);
9840 undo->InvalidateUndo();
9841 }
9842 }
9843 }
9844
9845 else if (m_bMarkEditing) { // End of way point drag
9846 if (m_pRoutePointEditTarget)
9847 if (m_bRoutePoinDragging) {
9848 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9849 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9850 }
9851 }
9852
9853 if (m_pRoutePointEditTarget)
9854 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9855
9856 if (!m_pRoutePointEditTarget) {
9857 delete m_pEditRouteArray;
9858 m_pEditRouteArray = NULL;
9859 m_bRouteEditing = false;
9860 }
9861 m_bRoutePoinDragging = false;
9862
9863 if (appending) { // Appending to the route of which the last point is
9864 // dragged onto another route
9865
9866 // copy tail from connect until length to end of current after dragging
9867
9868 int length = tail->GetnPoints();
9869 for (int i = connect + 1; i <= length; i++) {
9870 current->AddPointAndSegment(tail->GetPoint(i), false);
9871 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9872 m_routeState++;
9873 gFrame->RefreshAllCanvas();
9874 ret = true;
9875 }
9876 current->FinalizeForRendering();
9877 current->m_bIsBeingEdited = false;
9878 FinishRoute();
9879 g_pRouteMan->DeleteRoute(tail);
9880 }
9881 if (inserting) {
9882 pSelect->DeleteAllSelectableRoutePoints(current);
9883 pSelect->DeleteAllSelectableRouteSegments(current);
9884 for (int i = 1; i < connect; i++) { // numbering in the tail route
9885 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9886 }
9887 pSelect->AddAllSelectableRouteSegments(current);
9888 pSelect->AddAllSelectableRoutePoints(current);
9889 current->FinalizeForRendering();
9890 current->m_bIsBeingEdited = false;
9891 g_pRouteMan->DeleteRoute(tail);
9892 }
9893
9894 // Update the RouteProperties Dialog, if currently shown
9895 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9896 if (m_pEditRouteArray) {
9897 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9898 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9899 if (g_pRouteMan->IsRouteValid(pr)) {
9900 if (pRoutePropDialog->GetRoute() == pr) {
9901 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9902 }
9903 }
9904 }
9905 }
9906 }
9907
9908 } // g_btouch
9909
9910 else { // !g_btouch
9911 if (m_bRouteEditing) { // End of RoutePoint drag
9912 Route *tail = 0;
9913 Route *current = 0;
9914 bool appending = false;
9915 bool inserting = false;
9916 int connect = 0;
9917 int index_last;
9918 if (m_pRoutePointEditTarget) {
9919 m_pRoutePointEditTarget->m_bBlink = false;
9920 // Check to see if there is a nearby point which may replace the
9921 // dragged one
9922 RoutePoint *pMousePoint = NULL;
9923 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9924 double nearby_radius_meters =
9925 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9926 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9927 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9928 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9929 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9930 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9931 bool duplicate = false; // don't create duplicate point in routes
9932 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9933 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9934 ir++) {
9935 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9936 if (pr && pr->pRoutePointList) {
9937 auto *list = pr->pRoutePointList;
9938 auto pos =
9939 std::find(list->begin(), list->end(), pNearbyPoint);
9940 if (pos != list->end()) {
9941 duplicate = true;
9942 break;
9943 }
9944 }
9945 }
9946 }
9947
9948 // Special case:
9949 // Allow "re-use" of a route's waypoints iff it is a simple
9950 // isolated route. This allows, for instance, creation of a closed
9951 // polygon route
9952 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9953
9954 if (!duplicate) {
9955 int dlg_return;
9956 dlg_return =
9957 OCPNMessageBox(this,
9958 _("Replace this RoutePoint by the nearby "
9959 "Waypoint?"),
9960 _("OpenCPN RoutePoint change"),
9961 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9962 if (dlg_return == wxID_YES) {
9963 /*double confirmation if the dragged point has been manually
9964 * created which can be important and could be deleted
9965 * unintentionally*/
9966 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9967 pNearbyPoint);
9968 current =
9969 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9970
9971 if (tail && current && (tail != current)) {
9972 int dlg_return1;
9973 connect = tail->GetIndexOf(pNearbyPoint);
9974 int index_current_route =
9975 current->GetIndexOf(m_pRoutePointEditTarget);
9976 index_last = current->GetIndexOf(current->GetLastPoint());
9977 dlg_return1 = wxID_NO;
9978 if (index_last ==
9979 index_current_route) { // we are dragging the last
9980 // point of the route
9981 if (connect != tail->GetnPoints()) { // anything to do?
9982
9983 wxString dmsg(
9984 _("Last part of route to be appended to dragged "
9985 "route?"));
9986 if (connect == 1)
9987 dmsg =
9988 _("Full route to be appended to dragged route?");
9989
9990 dlg_return1 = OCPNMessageBox(
9991 this, dmsg, _("OpenCPN Route Create"),
9992 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9993 if (dlg_return1 == wxID_YES) {
9994 appending = true;
9995 }
9996 }
9997 } else if (index_current_route ==
9998 1) { // dragging the first point of the route
9999 if (connect != 1) { // anything to do?
10000
10001 wxString dmsg(
10002 _("First part of route to be inserted into dragged "
10003 "route?"));
10004 if (connect == tail->GetnPoints())
10005 dmsg = _(
10006 "Full route to be inserted into dragged route?");
10007
10008 dlg_return1 = OCPNMessageBox(
10009 this, dmsg, _("OpenCPN Route Create"),
10010 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10011 if (dlg_return1 == wxID_YES) {
10012 inserting = true;
10013 }
10014 }
10015 }
10016 }
10017
10018 if (m_pRoutePointEditTarget->IsShared()) {
10019 dlg_return = wxID_NO;
10020 dlg_return = OCPNMessageBox(
10021 this,
10022 _("Do you really want to delete and replace this "
10023 "WayPoint") +
10024 "\n" + _("which has been created manually?"),
10025 ("OpenCPN RoutePoint warning"),
10026 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10027 }
10028 }
10029 if (dlg_return == wxID_YES) {
10030 pMousePoint = pNearbyPoint;
10031 if (pMousePoint->m_bIsolatedMark) {
10032 pMousePoint->SetShared(true);
10033 }
10034 pMousePoint->m_bIsolatedMark =
10035 false; // definitely no longer isolated
10036 pMousePoint->m_bIsInRoute = true;
10037 }
10038 }
10039 }
10040 }
10041 if (!pMousePoint)
10042 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10043
10044 if (m_pEditRouteArray) {
10045 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10046 ir++) {
10047 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10048 if (g_pRouteMan->IsRouteValid(pr)) {
10049 if (pMousePoint) { // replace dragged point by nearby one
10050 auto *list = pr->pRoutePointList;
10051 auto pos = std::find(list->begin(), list->end(),
10052 m_pRoutePointEditTarget);
10053
10054 pSelect->DeleteAllSelectableRoutePoints(pr);
10055 pSelect->DeleteAllSelectableRouteSegments(pr);
10056
10057 pr->pRoutePointList->insert(pos, pMousePoint);
10058 pos = std::find(list->begin(), list->end(),
10059 m_pRoutePointEditTarget);
10060 if (pos != list->end()) list->erase(pos);
10061 // pr->pRoutePointList->erase(pos + 1);
10062
10063 pSelect->AddAllSelectableRouteSegments(pr);
10064 pSelect->AddAllSelectableRoutePoints(pr);
10065 }
10066 pr->FinalizeForRendering();
10067 pr->UpdateSegmentDistances();
10068 pr->m_bIsBeingEdited = false;
10069
10070 if (m_bRoutePoinDragging) {
10071 // Special case optimization.
10072 // Dragging a single point of a route
10073 // without any point additions or re-ordering
10074 if (!pMousePoint)
10075 NavObj_dB::GetInstance().UpdateRoutePoint(
10076 m_pRoutePointEditTarget);
10077 else
10078 NavObj_dB::GetInstance().UpdateRoute(pr);
10079 }
10080 pr->SetHiLite(0);
10081 }
10082 }
10083 Refresh(false);
10084 }
10085
10086 if (appending) {
10087 // copy tail from connect until length to end of current after
10088 // dragging
10089
10090 int length = tail->GetnPoints();
10091 for (int i = connect + 1; i <= length; i++) {
10092 current->AddPointAndSegment(tail->GetPoint(i), false);
10093 if (current)
10094 current->m_lastMousePointIndex = current->GetnPoints();
10095 m_routeState++;
10096 gFrame->RefreshAllCanvas();
10097 ret = true;
10098 }
10099 current->FinalizeForRendering();
10100 current->m_bIsBeingEdited = false;
10101 FinishRoute();
10102 g_pRouteMan->DeleteRoute(tail);
10103 }
10104 if (inserting) {
10105 pSelect->DeleteAllSelectableRoutePoints(current);
10106 pSelect->DeleteAllSelectableRouteSegments(current);
10107 for (int i = 1; i < connect; i++) { // numbering in the tail route
10108 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10109 }
10110 pSelect->AddAllSelectableRouteSegments(current);
10111 pSelect->AddAllSelectableRoutePoints(current);
10112 current->FinalizeForRendering();
10113 current->m_bIsBeingEdited = false;
10114 g_pRouteMan->DeleteRoute(tail);
10115 }
10116
10117 // Update the RouteProperties Dialog, if currently shown
10118 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10119 if (m_pEditRouteArray) {
10120 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10121 ir++) {
10122 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10123 if (g_pRouteMan->IsRouteValid(pr)) {
10124 if (pRoutePropDialog->GetRoute() == pr) {
10125 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10126 }
10127 }
10128 }
10129 }
10130 }
10131
10132 if (pMousePoint) {
10133 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10134 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10135 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10136 // Hide mark properties dialog if open on the replaced point
10137 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10138 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10139 g_pMarkInfoDialog->Hide();
10140
10141 delete m_pRoutePointEditTarget;
10142 m_lastRoutePointEditTarget = NULL;
10143 undo->AfterUndoableAction(pMousePoint);
10144 undo->InvalidateUndo();
10145 } else {
10146 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10147 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10148
10149 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10150 }
10151
10152 delete m_pEditRouteArray;
10153 m_pEditRouteArray = NULL;
10154 }
10155
10156 InvalidateGL();
10157 m_bRouteEditing = false;
10158 m_pRoutePointEditTarget = NULL;
10159
10160 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10161 ret = true;
10162 }
10163
10164 else if (m_bMarkEditing) { // end of Waypoint drag
10165 if (m_pRoutePointEditTarget) {
10166 if (m_bRoutePoinDragging) {
10167 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10168 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10169 }
10170 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10171 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10172 if (!g_bopengl) {
10173 wxRect wp_rect;
10174 RoutePointGui(*m_pRoutePointEditTarget)
10175 .CalculateDCRect(m_dc_route, this, &wp_rect);
10176 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10177 RefreshRect(wp_rect, true);
10178 }
10179 }
10180 m_pRoutePointEditTarget = NULL;
10181 m_bMarkEditing = false;
10182 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10183 ret = true;
10184 }
10185
10186 else if (leftIsDown) { // left click for chart center
10187 leftIsDown = false;
10188 ret = false;
10189
10190 if (!g_btouch) {
10191 if (!m_bChartDragging && !m_bMeasure_Active) {
10192 } else {
10193 m_bChartDragging = false;
10194 }
10195 }
10196 }
10197 m_bRoutePoinDragging = false;
10198 } // !btouch
10199
10200 if (ret) return true;
10201 } // left up
10202
10203 if (event.RightDown()) {
10204 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10205 last_drag.x = mx;
10206 last_drag.y = my;
10207
10208 if (g_btouch) {
10209 // if( m_pRoutePointEditTarget )
10210 // return false;
10211 }
10212
10213 ret = true;
10214 m_FinishRouteOnKillFocus = false;
10215 CallPopupMenu(mx, my);
10216 m_FinishRouteOnKillFocus = true;
10217 } // Right down
10218
10219 return ret;
10220}
10221
10222bool panleftIsDown;
10223bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10224 // Skip all mouse processing if shift is held.
10225 // This allows plugins to implement shift+drag behaviors.
10226 if (event.ShiftDown()) {
10227 return false;
10228 }
10229 int x, y;
10230 event.GetPosition(&x, &y);
10231
10232 x *= m_displayScale;
10233 y *= m_displayScale;
10234
10235 // Check for wheel rotation
10236 // ideally, should be just longer than the time between
10237 // processing accumulated mouse events from the event queue
10238 // as would happen during screen redraws.
10239 int wheel_dir = event.GetWheelRotation();
10240
10241 if (wheel_dir) {
10242 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10243 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10244
10245 double factor = g_mouse_zoom_sensitivity;
10246 if (wheel_dir < 0) factor = 1 / factor;
10247
10248 if (g_bsmoothpanzoom) {
10249 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10250 if (wheel_dir == m_last_wheel_dir) {
10251 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10252 // m_zoom_target /= factor;
10253 } else
10254 StopMovement();
10255 } else {
10256 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10257 m_wheelstopwatch.Start(0);
10258 // m_zoom_target = VPoint.chart_scale / factor;
10259 }
10260 }
10261
10262 m_last_wheel_dir = wheel_dir;
10263
10264 ZoomCanvas(factor, true, false);
10265 }
10266
10267 if (event.LeftDown()) {
10268 // Skip the first left click if it will cause a canvas focus shift
10269 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10270 return false;
10271 }
10272
10273 last_drag.x = x, last_drag.y = y;
10274 panleftIsDown = true;
10275 }
10276
10277 if (event.LeftUp()) {
10278 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10279 // seen here.
10280 panleftIsDown = false;
10281
10282 if (!g_btouch) {
10283 if (!m_bChartDragging && !m_bMeasure_Active) {
10284 switch (cursor_region) {
10285 case MID_RIGHT: {
10286 PanCanvas(100, 0);
10287 break;
10288 }
10289
10290 case MID_LEFT: {
10291 PanCanvas(-100, 0);
10292 break;
10293 }
10294
10295 case MID_TOP: {
10296 PanCanvas(0, 100);
10297 break;
10298 }
10299
10300 case MID_BOT: {
10301 PanCanvas(0, -100);
10302 break;
10303 }
10304
10305 case CENTER: {
10306 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10307 break;
10308 }
10309 }
10310 } else {
10311 m_bChartDragging = false;
10312 }
10313 }
10314 }
10315 }
10316
10317 if (event.Dragging() && event.LeftIsDown()) {
10318 /*
10319 * fixed dragging.
10320 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10321 * before the drag event. Hence, as there is no mouse down event, last_drag
10322 * is not reset before the drag. And that results in one single drag
10323 * session, meaning you cannot drag the map a few miles north, lift your
10324 * finger, and the go even further north. Instead, the map resets itself
10325 * always to the very first drag start (since there is not reset of
10326 * last_drag).
10327 *
10328 * Besides, should not left down and dragging be enough of a situation to
10329 * start a drag procedure?
10330 *
10331 * Anyways, guarded it to be active in touch situations only.
10332 */
10333 if (g_btouch && !m_inPinch) {
10334 struct timespec now;
10335 clock_gettime(CLOCK_MONOTONIC, &now);
10336 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10337
10338 bool trigger_hold = false;
10339 if (false == m_bChartDragging) {
10340 if (m_DragTrigger < 0) {
10341 // printf("\ntrigger1\n");
10342 m_DragTrigger = 0;
10343 m_DragTriggerStartTime = tnow;
10344 trigger_hold = true;
10345 } else {
10346 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10347 m_DragTrigger = -1; // Reset trigger
10348 // printf("trigger fired\n");
10349 }
10350 }
10351 }
10352 if (trigger_hold) return true;
10353
10354 if (false == m_bChartDragging) {
10355 // printf("starting drag\n");
10356 // Reset drag calculation members
10357 last_drag.x = x - 1, last_drag.y = y - 1;
10358 m_bChartDragging = true;
10359 m_chart_drag_total_time = 0;
10360 m_chart_drag_total_x = 0;
10361 m_chart_drag_total_y = 0;
10362 m_inertia_last_drag_x = x;
10363 m_inertia_last_drag_y = y;
10364 m_drag_vec_x.clear();
10365 m_drag_vec_y.clear();
10366 m_drag_vec_t.clear();
10367 m_last_drag_time = tnow;
10368 }
10369
10370 // Calculate and store drag dynamics.
10371 uint64_t delta_t = tnow - m_last_drag_time;
10372 double delta_tf = delta_t / 1e9;
10373
10374 m_chart_drag_total_time += delta_tf;
10375 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10376 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10377
10378 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10379 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10380 m_drag_vec_t.push_back(delta_tf);
10381
10382 m_inertia_last_drag_x = x;
10383 m_inertia_last_drag_y = y;
10384 m_last_drag_time = tnow;
10385
10386 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10387 m_bChartDragging = true;
10388 StartTimedMovement();
10389 m_pan_drag.x += last_drag.x - x;
10390 m_pan_drag.y += last_drag.y - y;
10391 last_drag.x = x, last_drag.y = y;
10392 }
10393 } else if (!g_btouch) {
10394 if ((last_drag.x != x) || (last_drag.y != y)) {
10395 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10396 // dragging on route create.
10397 // github #2994
10398 m_bChartDragging = true;
10399 StartTimedMovement();
10400 m_pan_drag.x += last_drag.x - x;
10401 m_pan_drag.y += last_drag.y - y;
10402 last_drag.x = x, last_drag.y = y;
10403 }
10404 }
10405 }
10406
10407 // Handle some special cases
10408 if (g_btouch) {
10409 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10410 // deactivate next LeftUp to ovoid creating an unexpected point
10411 m_ignore_next_leftup = true;
10412 m_DoubleClickTimer->Start();
10413 singleClickEventIsValid = false;
10414 }
10415 }
10416 }
10417
10418 return true;
10419}
10420
10421void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10422 if (MouseEventOverlayWindows(event)) return;
10423
10424 if (MouseEventSetup(event)) return; // handled, no further action required
10425
10426 bool nm = MouseEventProcessObjects(event);
10427 if (!nm) MouseEventProcessCanvas(event);
10428}
10429
10430void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10431 // Switch to the appropriate cursor on mouse movement
10432
10433 wxCursor *ptarget_cursor = pCursorArrow;
10434 if (!pPlugIn_Cursor) {
10435 ptarget_cursor = pCursorArrow;
10436 if ((!m_routeState) &&
10437 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10438 if (cursor_region == MID_RIGHT) {
10439 ptarget_cursor = pCursorRight;
10440 } else if (cursor_region == MID_LEFT) {
10441 ptarget_cursor = pCursorLeft;
10442 } else if (cursor_region == MID_TOP) {
10443 ptarget_cursor = pCursorDown;
10444 } else if (cursor_region == MID_BOT) {
10445 ptarget_cursor = pCursorUp;
10446 } else {
10447 ptarget_cursor = pCursorArrow;
10448 }
10449 } else if (m_bMeasure_Active ||
10450 m_routeState) // If Measure tool use Pencil Cursor
10451 ptarget_cursor = pCursorPencil;
10452 } else {
10453 ptarget_cursor = pPlugIn_Cursor;
10454 }
10455
10456 SetCursor(*ptarget_cursor);
10457}
10458
10459void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10460 SetCursor(*pCursorArrow);
10461}
10462
10463void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10464 ChartPlugInWrapper *target_plugin_chart = NULL;
10465 s57chart *Chs57 = NULL;
10466 wxFileName file;
10467 wxArrayString files;
10468
10469 ChartBase *target_chart = GetChartAtCursor();
10470 if (target_chart) {
10471 file.Assign(target_chart->GetFullPath());
10472 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10473 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10474 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10475 else
10476 Chs57 = dynamic_cast<s57chart *>(target_chart);
10477 } else { // target_chart = null, might be mbtiles
10478 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10479 unsigned int im = stackIndexArray.size();
10480 int scale = 2147483647; // max 32b integer
10481 if (VPoint.b_quilt && im > 0) {
10482 for (unsigned int is = 0; is < im; is++) {
10483 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10484 CHART_TYPE_MBTILES) {
10485 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10486 double lat, lon;
10487 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10488 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10489 .GetBBox()
10490 .Contains(lat, lon)) {
10491 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10492 scale) {
10493 scale =
10494 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10495 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10496 }
10497 }
10498 }
10499 }
10500 }
10501 }
10502
10503 std::vector<Ais8_001_22 *> area_notices;
10504
10505 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10506 float vp_scale = GetVPScale();
10507
10508 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10509 auto target_data = target.second;
10510 if (!target_data->area_notices.empty()) {
10511 for (auto &ani : target_data->area_notices) {
10512 Ais8_001_22 &area_notice = ani.second;
10513
10514 BoundingBox bbox;
10515
10516 for (Ais8_001_22_SubAreaList::iterator sa =
10517 area_notice.sub_areas.begin();
10518 sa != area_notice.sub_areas.end(); ++sa) {
10519 switch (sa->shape) {
10520 case AIS8_001_22_SHAPE_CIRCLE: {
10521 wxPoint target_point;
10522 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10523 bbox.Expand(target_point);
10524 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10525 break;
10526 }
10527 case AIS8_001_22_SHAPE_RECT: {
10528 wxPoint target_point;
10529 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10530 bbox.Expand(target_point);
10531 if (sa->e_dim_m > sa->n_dim_m)
10532 bbox.EnLarge(sa->e_dim_m * vp_scale);
10533 else
10534 bbox.EnLarge(sa->n_dim_m * vp_scale);
10535 break;
10536 }
10537 case AIS8_001_22_SHAPE_POLYGON:
10538 case AIS8_001_22_SHAPE_POLYLINE: {
10539 for (int i = 0; i < 4; ++i) {
10540 double lat = sa->latitude;
10541 double lon = sa->longitude;
10542 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10543 &lat, &lon);
10544 wxPoint target_point;
10545 GetCanvasPointPix(lat, lon, &target_point);
10546 bbox.Expand(target_point);
10547 }
10548 break;
10549 }
10550 case AIS8_001_22_SHAPE_SECTOR: {
10551 double lat1 = sa->latitude;
10552 double lon1 = sa->longitude;
10553 double lat, lon;
10554 wxPoint target_point;
10555 GetCanvasPointPix(lat1, lon1, &target_point);
10556 bbox.Expand(target_point);
10557 for (int i = 0; i < 18; ++i) {
10558 ll_gc_ll(
10559 lat1, lon1,
10560 sa->left_bound_deg +
10561 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10562 sa->radius_m / 1852.0, &lat, &lon);
10563 GetCanvasPointPix(lat, lon, &target_point);
10564 bbox.Expand(target_point);
10565 }
10566 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10567 &lat, &lon);
10568 GetCanvasPointPix(lat, lon, &target_point);
10569 bbox.Expand(target_point);
10570 break;
10571 }
10572 }
10573 }
10574
10575 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10576 area_notices.push_back(&area_notice);
10577 }
10578 }
10579 }
10580 }
10581 }
10582
10583 if (target_chart || !area_notices.empty() || file.HasName()) {
10584 // Go get the array of all objects at the cursor lat/lon
10585 int sel_rad_pix = 5;
10586 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10587
10588 // Make sure we always get the lights from an object, even if we are
10589 // currently not displaying lights on the chart.
10590
10591 SetCursor(wxCURSOR_WAIT);
10592 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10593 if (!lightsVis) SetShowENCLights(true);
10594 ;
10595
10596 ListOfObjRazRules *rule_list = NULL;
10597 ListOfPI_S57Obj *pi_rule_list = NULL;
10598 if (Chs57)
10599 rule_list =
10600 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10601 else if (target_plugin_chart)
10602 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10603 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10604
10605 ListOfObjRazRules *overlay_rule_list = NULL;
10606 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10607 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10608
10609 if (CHs57_Overlay) {
10610 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10611 zlat, zlon, SelectRadius, &GetVP());
10612 }
10613
10614 if (!lightsVis) SetShowENCLights(false);
10615
10616 wxString objText;
10617 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10618 wxString face = dFont->GetFaceName();
10619
10620 if (NULL == g_pObjectQueryDialog) {
10622 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10623 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10624 }
10625
10626 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10627 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10628
10629#ifdef __WXOSX__
10630 // Auto Adjustment for dark mode
10631 fg = g_pObjectQueryDialog->GetForegroundColour();
10632#endif
10633
10634 objText.Printf(
10635 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10636 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10637
10638#ifdef __WXOSX__
10639 int points = dFont->GetPointSize();
10640#else
10641 int points = dFont->GetPointSize() + 1;
10642#endif
10643
10644 int sizes[7];
10645 for (int i = -2; i < 5; i++) {
10646 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10647 }
10648 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10649
10650 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10651
10652 if (overlay_rule_list && CHs57_Overlay) {
10653 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10654 objText << "<hr noshade>";
10655 }
10656
10657 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10658 an != area_notices.end(); ++an) {
10659 objText << "<b>AIS Area Notice:</b> ";
10660 objText << ais8_001_22_notice_names[(*an)->notice_type];
10661 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10662 (*an)->sub_areas.begin();
10663 sa != (*an)->sub_areas.end(); ++sa)
10664 if (!sa->text.empty()) objText << sa->text;
10665 objText << "<br>expires: " << (*an)->expiry_time.Format();
10666 objText << "<hr noshade>";
10667 }
10668
10669 if (Chs57)
10670 objText << Chs57->CreateObjDescriptions(rule_list);
10671 else if (target_plugin_chart)
10672 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10673 pi_rule_list);
10674
10675 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10676
10677 // Add the additional info files
10678 wxString AddFiles, filenameOK;
10679 int filecount = 0;
10680 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10681 // plugin
10682
10683 AddFiles = wxString::Format(
10684 "<hr noshade><br><b>Additional info files attached to: </b> "
10685 "<font "
10686 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10687 "cellpadding=3>",
10688 file.GetFullName());
10689 file.Normalize();
10690 file.Assign(file.GetPath(), "");
10691 wxDir dir(file.GetFullPath());
10692 wxString filename;
10693 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10694 while (cont) {
10695 file.Assign(dir.GetNameWithSep().append(filename));
10696 wxString FormatString =
10697 "<td valign=top><font size=-2><a "
10698 "href=\"%s\">%s</a></font></td>";
10699 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10700 filenameOK = file.GetFullPath(); // remember last valid name
10701 // we are making a 3 columns table. New row only every third file
10702 if (3 * ((int)filecount / 3) == filecount)
10703 FormatString.Prepend("<tr>"); // new row
10704 else
10705 FormatString.Prepend(
10706 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10707 // spacer column
10708
10709 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10710 file.GetFullName());
10711 filecount++;
10712 }
10713 cont = dir.GetNext(&filename);
10714 }
10715 objText << AddFiles << "</table>";
10716 }
10717 objText << "</font>";
10718 objText << "</body></html>";
10719
10720 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10721 g_pObjectQueryDialog->SetHTMLPage(objText);
10722 g_pObjectQueryDialog->Show();
10723 }
10724 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10725 // generate an event to avoid double code
10726 wxHtmlLinkInfo hli(filenameOK);
10727 wxHtmlLinkEvent hle(1, hli);
10728 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10729 }
10730
10731 if (rule_list) rule_list->Clear();
10732 delete rule_list;
10733
10734 if (overlay_rule_list) overlay_rule_list->Clear();
10735 delete overlay_rule_list;
10736
10737 if (pi_rule_list) pi_rule_list->Clear();
10738 delete pi_rule_list;
10739
10740 SetCursor(wxCURSOR_ARROW);
10741 }
10742}
10743
10744void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10745 bool bNew = false;
10746 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10747 // Dialog
10748 g_pMarkInfoDialog = new MarkInfoDlg(this);
10749 bNew = true;
10750 }
10751
10752 if (1 /*g_bresponsive*/) {
10753 wxSize canvas_size = GetSize();
10754
10755 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10756 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10757
10758 g_pMarkInfoDialog->Layout();
10759
10760 wxPoint canvas_pos = GetPosition();
10761 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10762
10763 bool newFit = false;
10764 if (canvas_size.x < fitted_size.x) {
10765 fitted_size.x = canvas_size.x - 40;
10766 if (canvas_size.y < fitted_size.y)
10767 fitted_size.y -= 40; // scrollbar added
10768 }
10769 if (canvas_size.y < fitted_size.y) {
10770 fitted_size.y = canvas_size.y - 40;
10771 if (canvas_size.x < fitted_size.x)
10772 fitted_size.x -= 40; // scrollbar added
10773 }
10774
10775 if (newFit) {
10776 g_pMarkInfoDialog->SetSize(fitted_size);
10777 g_pMarkInfoDialog->Centre();
10778 }
10779 }
10780
10781 markPoint->m_bRPIsBeingEdited = false;
10782
10783 wxString title_base = _("Mark Properties");
10784 if (markPoint->m_bIsInRoute) {
10785 title_base = _("Waypoint Properties");
10786 }
10787 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10788 g_pMarkInfoDialog->UpdateProperties();
10789 if (markPoint->m_bIsInLayer) {
10790 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10791 GetLayerName(markPoint->m_LayerID)));
10792 g_pMarkInfoDialog->SetDialogTitle(caption);
10793 } else
10794 g_pMarkInfoDialog->SetDialogTitle(title_base);
10795
10796 g_pMarkInfoDialog->Show();
10797 g_pMarkInfoDialog->Raise();
10798 g_pMarkInfoDialog->InitialFocus();
10799 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10800}
10801
10802void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10803 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10804 pRoutePropDialog->SetRouteAndUpdate(selected);
10805 // pNew->UpdateProperties();
10806 pRoutePropDialog->Show();
10807 pRoutePropDialog->Raise();
10808 return;
10809 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10810 this); // There is one global instance of the RouteProp Dialog
10811
10812 if (g_bresponsive) {
10813 wxSize canvas_size = GetSize();
10814 wxPoint canvas_pos = GetPosition();
10815 wxSize fitted_size = pRoutePropDialog->GetSize();
10816 ;
10817
10818 if (canvas_size.x < fitted_size.x) {
10819 fitted_size.x = canvas_size.x;
10820 if (canvas_size.y < fitted_size.y)
10821 fitted_size.y -= 20; // scrollbar added
10822 }
10823 if (canvas_size.y < fitted_size.y) {
10824 fitted_size.y = canvas_size.y;
10825 if (canvas_size.x < fitted_size.x)
10826 fitted_size.x -= 20; // scrollbar added
10827 }
10828
10829 pRoutePropDialog->SetSize(fitted_size);
10830 pRoutePropDialog->Centre();
10831
10832 // int xp = (canvas_size.x - fitted_size.x)/2;
10833 // int yp = (canvas_size.y - fitted_size.y)/2;
10834
10835 wxPoint xxp = ClientToScreen(canvas_pos);
10836 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10837 }
10838
10839 pRoutePropDialog->SetRouteAndUpdate(selected);
10840
10841 pRoutePropDialog->Show();
10842
10843 Refresh(false);
10844}
10845
10846void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10847 pTrackPropDialog = TrackPropDlg::getInstance(
10848 this); // There is one global instance of the RouteProp Dialog
10849
10850 pTrackPropDialog->SetTrackAndUpdate(selected);
10852
10853 pTrackPropDialog->Show();
10854
10855 Refresh(false);
10856}
10857
10858void pupHandler_PasteWaypoint() {
10859 Kml kml;
10860
10861 int pasteBuffer = kml.ParsePasteBuffer();
10862 RoutePoint *pasted = kml.GetParsedRoutePoint();
10863 if (!pasted) return;
10864
10865 double nearby_radius_meters =
10866 g_Platform->GetSelectRadiusPix() /
10867 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10868
10869 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10870 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10871
10872 int answer = wxID_NO;
10873 if (nearPoint && !nearPoint->m_bIsInLayer) {
10874 wxString msg;
10875 msg << _(
10876 "There is an existing waypoint at the same location as the one you are "
10877 "pasting. Would you like to merge the pasted data with it?\n\n");
10878 msg << _("Answering 'No' will create a new waypoint at the same location.");
10879 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10880 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10881 }
10882
10883 if (answer == wxID_YES) {
10884 nearPoint->SetName(pasted->GetName());
10885 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10886 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10887 pRouteManagerDialog->UpdateWptListCtrl();
10888 }
10889
10890 if (answer == wxID_NO) {
10891 RoutePoint *newPoint = new RoutePoint(pasted);
10892 newPoint->m_bIsolatedMark = true;
10893 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10894 newPoint);
10895 // pConfig->AddNewWayPoint(newPoint, -1);
10896 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10897
10898 pWayPointMan->AddRoutePoint(newPoint);
10899 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10900 pRouteManagerDialog->UpdateWptListCtrl();
10901 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10902 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10903 }
10904
10905 gFrame->InvalidateAllGL();
10906 gFrame->RefreshAllCanvas(false);
10907}
10908
10909void pupHandler_PasteRoute() {
10910 Kml kml;
10911
10912 int pasteBuffer = kml.ParsePasteBuffer();
10913 Route *pasted = kml.GetParsedRoute();
10914 if (!pasted) return;
10915
10916 double nearby_radius_meters =
10917 g_Platform->GetSelectRadiusPix() /
10918 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10919
10920 RoutePoint *curPoint;
10921 RoutePoint *nearPoint;
10922 RoutePoint *prevPoint = NULL;
10923
10924 bool mergepoints = false;
10925 bool createNewRoute = true;
10926 int existingWaypointCounter = 0;
10927
10928 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10929 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10930 nearPoint = pWayPointMan->GetNearbyWaypoint(
10931 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10932 if (nearPoint) {
10933 mergepoints = true;
10934 existingWaypointCounter++;
10935 // Small hack here to avoid both extending RoutePoint and repeating all
10936 // the GetNearbyWaypoint calculations. Use existin data field in
10937 // RoutePoint as temporary storage.
10938 curPoint->m_bPtIsSelected = true;
10939 }
10940 }
10941
10942 int answer = wxID_NO;
10943 if (mergepoints) {
10944 wxString msg;
10945 msg << _(
10946 "There are existing waypoints at the same location as some of the ones "
10947 "you are pasting. Would you like to just merge the pasted data into "
10948 "them?\n\n");
10949 msg << _("Answering 'No' will create all new waypoints for this route.");
10950 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10951 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10952
10953 if (answer == wxID_CANCEL) {
10954 return;
10955 }
10956 }
10957
10958 // If all waypoints exist since before, and a route with the same name, we
10959 // don't create a new route.
10960 if (mergepoints && answer == wxID_YES &&
10961 existingWaypointCounter == pasted->GetnPoints()) {
10962 for (Route *proute : *pRouteList) {
10963 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10964 createNewRoute = false;
10965 break;
10966 }
10967 }
10968 }
10969
10970 Route *newRoute = 0;
10971 RoutePoint *newPoint = 0;
10972
10973 if (createNewRoute) {
10974 newRoute = new Route();
10975 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10976 }
10977
10978 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10979 curPoint = pasted->GetPoint(i);
10980 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10981 curPoint->m_bPtIsSelected = false;
10982 newPoint = pWayPointMan->GetNearbyWaypoint(
10983 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10984 newPoint->SetName(curPoint->GetName());
10985 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10986
10987 if (createNewRoute) newRoute->AddPoint(newPoint);
10988 } else {
10989 curPoint->m_bPtIsSelected = false;
10990
10991 newPoint = new RoutePoint(curPoint);
10992 newPoint->m_bIsolatedMark = false;
10993 newPoint->SetIconName("circle");
10994 newPoint->m_bIsVisible = true;
10995 newPoint->m_bShowName = false;
10996 newPoint->SetShared(false);
10997
10998 newRoute->AddPoint(newPoint);
10999 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11000 newPoint);
11001 // pConfig->AddNewWayPoint(newPoint, -1);
11002 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11003 pWayPointMan->AddRoutePoint(newPoint);
11004 }
11005 if (i > 1 && createNewRoute)
11006 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11007 curPoint->m_lat, curPoint->m_lon,
11008 prevPoint, newPoint, newRoute);
11009 prevPoint = newPoint;
11010 }
11011
11012 if (createNewRoute) {
11013 pRouteList->push_back(newRoute);
11014 // pConfig->AddNewRoute(newRoute); // use auto next num
11015 NavObj_dB::GetInstance().InsertRoute(newRoute);
11016
11017 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11018 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11019 }
11020
11021 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11022 pRouteManagerDialog->UpdateRouteListCtrl();
11023 pRouteManagerDialog->UpdateWptListCtrl();
11024 }
11025 gFrame->InvalidateAllGL();
11026 gFrame->RefreshAllCanvas(false);
11027 }
11028 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11029 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11030}
11031
11032void pupHandler_PasteTrack() {
11033 Kml kml;
11034
11035 int pasteBuffer = kml.ParsePasteBuffer();
11036 Track *pasted = kml.GetParsedTrack();
11037 if (!pasted) return;
11038
11039 TrackPoint *curPoint;
11040
11041 Track *newTrack = new Track();
11042 TrackPoint *newPoint;
11043 TrackPoint *prevPoint = NULL;
11044
11045 newTrack->SetName(pasted->GetName());
11046
11047 for (int i = 0; i < pasted->GetnPoints(); i++) {
11048 curPoint = pasted->GetPoint(i);
11049
11050 newPoint = new TrackPoint(curPoint);
11051
11052 wxDateTime now = wxDateTime::Now();
11053 newPoint->SetCreateTime(curPoint->GetCreateTime());
11054
11055 newTrack->AddPoint(newPoint);
11056
11057 if (prevPoint)
11058 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11059 newPoint->m_lat, newPoint->m_lon,
11060 prevPoint, newPoint, newTrack);
11061
11062 prevPoint = newPoint;
11063 }
11064
11065 g_TrackList.push_back(newTrack);
11066 // pConfig->AddNewTrack(newTrack);
11067 NavObj_dB::GetInstance().InsertTrack(newTrack);
11068
11069 gFrame->InvalidateAllGL();
11070 gFrame->RefreshAllCanvas(false);
11071}
11072
11073bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11074 wxJSONValue v;
11075 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11076 v["CursorPosition_x"] = x;
11077 v["CursorPosition_y"] = y;
11078 // Send a limited set of selection types depending on what is
11079 // found under the mouse point.
11080 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11081 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11082 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11083
11084 wxJSONWriter w;
11085 wxString out;
11086 w.Write(v, out);
11087 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11088
11089 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11090
11091#if 0
11092#define SELTYPE_UNKNOWN 0x0001
11093#define SELTYPE_ROUTEPOINT 0x0002
11094#define SELTYPE_ROUTESEGMENT 0x0004
11095#define SELTYPE_TIDEPOINT 0x0008
11096#define SELTYPE_CURRENTPOINT 0x0010
11097#define SELTYPE_ROUTECREATE 0x0020
11098#define SELTYPE_AISTARGET 0x0040
11099#define SELTYPE_MARKPOINT 0x0080
11100#define SELTYPE_TRACKSEGMENT 0x0100
11101#define SELTYPE_DRAGHANDLE 0x0200
11102#endif
11103
11104 if (g_bhide_context_menus) return true;
11105 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11106 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11107 m_pIDXCandidate, m_nmea_log);
11108
11109 Connect(
11110 wxEVT_COMMAND_MENU_SELECTED,
11111 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11112
11113#ifdef __WXGTK__
11114 // Funny requirement here for gtk, to clear the menu trigger event
11115 // TODO
11116 // Causes a slight "flasH" of the menu,
11117 if (m_inLongPress) {
11118 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11119 m_inLongPress = false;
11120 }
11121#endif
11122
11123 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11124
11125 Disconnect(
11126 wxEVT_COMMAND_MENU_SELECTED,
11127 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11128
11129 delete m_canvasMenu;
11130 m_canvasMenu = NULL;
11131
11132#ifdef __WXQT__
11133 // gFrame->SurfaceToolbar();
11134 // g_MainToolbar->Raise();
11135#endif
11136
11137 return true;
11138}
11139
11140void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11141 // Pass menu events from the canvas to the menu handler
11142 // This is necessarily in ChartCanvas since that is the menu's parent.
11143 if (m_canvasMenu) {
11144 m_canvasMenu->PopupMenuHandler(event);
11145 }
11146 return;
11147}
11148
11149void ChartCanvas::StartRoute() {
11150 // Do not allow more than one canvas to create a route at one time.
11151 if (g_brouteCreating) return;
11152
11153 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11154
11155 g_brouteCreating = true;
11156 m_routeState = 1;
11157 m_bDrawingRoute = false;
11158 SetCursor(*pCursorPencil);
11159 // SetCanvasToolbarItemState(ID_ROUTE, true);
11160 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11161
11162 HideGlobalToolbar();
11163
11164#ifdef __ANDROID__
11165 androidSetRouteAnnunciator(true);
11166#endif
11167}
11168
11169wxString ChartCanvas::FinishRoute() {
11170 m_routeState = 0;
11171 m_prev_pMousePoint = NULL;
11172 m_bDrawingRoute = false;
11173 wxString rv = "";
11174 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11175
11176 // SetCanvasToolbarItemState(ID_ROUTE, false);
11177 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11178#ifdef __ANDROID__
11179 androidSetRouteAnnunciator(false);
11180#endif
11181
11182 SetCursor(*pCursorArrow);
11183
11184 if (m_pMouseRoute) {
11185 if (m_bAppendingRoute) {
11186 // pConfig->UpdateRoute(m_pMouseRoute);
11187 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11188 } else {
11189 if (m_pMouseRoute->GetnPoints() > 1) {
11190 // pConfig->AddNewRoute(m_pMouseRoute);
11191 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11192 } else {
11193 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11194 m_pMouseRoute = NULL;
11195 }
11196 }
11197 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11198
11199 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11200 (pRoutePropDialog->IsShown())) {
11201 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11202 }
11203
11204 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11205 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11206 pRouteManagerDialog->UpdateRouteListCtrl();
11207 }
11208 }
11209 m_bAppendingRoute = false;
11210 m_pMouseRoute = NULL;
11211
11212 m_pSelectedRoute = NULL;
11213
11214 undo->InvalidateUndo();
11215 gFrame->RefreshAllCanvas(true);
11216
11217 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11218
11219 ShowGlobalToolbar();
11220
11221 g_brouteCreating = false;
11222
11223 return rv;
11224}
11225
11226void ChartCanvas::HideGlobalToolbar() {
11227 if (m_canvasIndex == 0) {
11228 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11229 }
11230}
11231
11232void ChartCanvas::ShowGlobalToolbar() {
11233 if (m_canvasIndex == 0) {
11234 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11235 }
11236}
11237
11238void ChartCanvas::ShowAISTargetList() {
11239 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11240 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11241 }
11242
11243 g_pAISTargetList->UpdateAISTargetList();
11244}
11245
11246void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11247 if (!m_bShowOutlines) return;
11248
11249 if (!ChartData) return;
11250
11251 int nEntry = ChartData->GetChartTableEntries();
11252
11253 for (int i = 0; i < nEntry; i++) {
11254 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11255
11256 // Check to see if the candidate chart is in the currently active group
11257 bool b_group_draw = false;
11258 if (m_groupIndex > 0) {
11259 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11260 int index = pt->GetGroupArray()[ig];
11261 if (m_groupIndex == index) {
11262 b_group_draw = true;
11263 break;
11264 }
11265 }
11266 } else
11267 b_group_draw = true;
11268
11269 if (b_group_draw) RenderChartOutline(dc, i, vp);
11270 }
11271
11272 // On CM93 Composite Charts, draw the outlines of the next smaller
11273 // scale cell
11274 cm93compchart *pcm93 = NULL;
11275 if (VPoint.b_quilt) {
11276 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11277 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11278 pcm93 = (cm93compchart *)pch;
11279 break;
11280 }
11281 } else if (m_singleChart &&
11282 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11283 pcm93 = (cm93compchart *)m_singleChart;
11284
11285 if (pcm93) {
11286 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11287 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11288
11289 if (zoom_factor > 8.0) {
11290 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11291 dc.SetPen(mPen);
11292 } else {
11293 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11294 dc.SetPen(mPen);
11295 }
11296
11297 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11298 }
11299}
11300
11301void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11302#ifdef ocpnUSE_GL
11303 if (g_bopengl && m_glcc) {
11304 /* opengl version specially optimized */
11305 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11306 return;
11307 }
11308#endif
11309
11310 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11311 if (!ChartData->IsChartAvailable(dbIndex)) return;
11312 }
11313
11314 float plylat, plylon;
11315 float plylat1, plylon1;
11316
11317 int pixx, pixy, pixx1, pixy1;
11318
11319 LLBBox box;
11320 ChartData->GetDBBoundingBox(dbIndex, box);
11321
11322 // Don't draw an outline in the case where the chart covers the entire world
11323 // */
11324 if (box.GetLonRange() == 360) return;
11325
11326 double lon_bias = 0;
11327 // chart is outside of viewport lat/lon bounding box
11328 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11329
11330 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11331
11332 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11333 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11334
11335 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11336 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11337
11338 else
11339 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11340
11341 // Are there any aux ply entries?
11342 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11343 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11344 {
11345 wxPoint r, r1;
11346
11347 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11348 plylon += lon_bias;
11349
11350 GetCanvasPointPix(plylat, plylon, &r);
11351 pixx = r.x;
11352 pixy = r.y;
11353
11354 for (int i = 0; i < nPly - 1; i++) {
11355 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11356 plylon1 += lon_bias;
11357
11358 GetCanvasPointPix(plylat1, plylon1, &r1);
11359 pixx1 = r1.x;
11360 pixy1 = r1.y;
11361
11362 int pixxs1 = pixx1;
11363 int pixys1 = pixy1;
11364
11365 bool b_skip = false;
11366
11367 if (vp.chart_scale > 5e7) {
11368 // calculate projected distance between these two points in meters
11369 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11370 pow((double)(pixy1 - pixy), 2)) /
11371 vp.view_scale_ppm;
11372
11373 if (dist > 0.0) {
11374 // calculate GC distance between these two points in meters
11375 double distgc =
11376 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11377
11378 // If the distances are nonsense, it means that the scale is very
11379 // small and the segment wrapped the world So skip it....
11380 // TODO improve this to draw two segments
11381 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11382 b_skip = true;
11383 } else
11384 b_skip = true;
11385 }
11386
11387 ClipResult res = cohen_sutherland_line_clip_i(
11388 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11389 if (res != Invisible && !b_skip)
11390 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11391
11392 plylat = plylat1;
11393 plylon = plylon1;
11394 pixx = pixxs1;
11395 pixy = pixys1;
11396 }
11397
11398 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11399 plylon1 += lon_bias;
11400
11401 GetCanvasPointPix(plylat1, plylon1, &r1);
11402 pixx1 = r1.x;
11403 pixy1 = r1.y;
11404
11405 ClipResult res = cohen_sutherland_line_clip_i(
11406 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11407 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11408 }
11409
11410 else // Use Aux PlyPoints
11411 {
11412 wxPoint r, r1;
11413
11414 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11415 for (int j = 0; j < nAuxPlyEntries; j++) {
11416 int nAuxPly =
11417 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11418 GetCanvasPointPix(plylat, plylon, &r);
11419 pixx = r.x;
11420 pixy = r.y;
11421
11422 for (int i = 0; i < nAuxPly - 1; i++) {
11423 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11424
11425 GetCanvasPointPix(plylat1, plylon1, &r1);
11426 pixx1 = r1.x;
11427 pixy1 = r1.y;
11428
11429 int pixxs1 = pixx1;
11430 int pixys1 = pixy1;
11431
11432 bool b_skip = false;
11433
11434 if (vp.chart_scale > 5e7) {
11435 // calculate projected distance between these two points in meters
11436 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11437 ((pixy1 - pixy) * (pixy1 - pixy))) /
11438 vp.view_scale_ppm;
11439 if (dist > 0.0) {
11440 // calculate GC distance between these two points in meters
11441 double distgc =
11442 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11443
11444 // If the distances are nonsense, it means that the scale is very
11445 // small and the segment wrapped the world So skip it....
11446 // TODO improve this to draw two segments
11447 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11448 b_skip = true;
11449 } else
11450 b_skip = true;
11451 }
11452
11453 ClipResult res = cohen_sutherland_line_clip_i(
11454 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11455 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11456
11457 plylat = plylat1;
11458 plylon = plylon1;
11459 pixx = pixxs1;
11460 pixy = pixys1;
11461 }
11462
11463 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11464 GetCanvasPointPix(plylat1, plylon1, &r1);
11465 pixx1 = r1.x;
11466 pixy1 = r1.y;
11467
11468 ClipResult res = cohen_sutherland_line_clip_i(
11469 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11470 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11471 }
11472 }
11473}
11474
11475static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11476 const wxArrayString &legend) {
11477 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11478
11479 int pointsize = dFont->GetPointSize();
11480 pointsize /= OCPN_GetWinDIPScaleFactor();
11481
11482 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11483 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11484 false, dFont->GetFaceName());
11485
11486 dc.SetFont(*psRLI_font);
11487
11488 int h = 0;
11489 int w = 0;
11490 int hl, wl;
11491
11492 int xp, yp;
11493 int hilite_offset = 3;
11494
11495 for (wxString line : legend) {
11496#ifdef __WXMAC__
11497 wxScreenDC sdc;
11498 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11499#else
11500 dc.GetTextExtent(line, &wl, &hl);
11501 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11502 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11503#endif
11504 h += hl;
11505 w = wxMax(w, wl);
11506 }
11507 w += (hl / 2); // Add a little right pad
11508
11509 xp = ref_point.x - w;
11510 yp = ref_point.y;
11511 yp += hilite_offset;
11512
11513 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11514
11515 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11516 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11517
11518 for (wxString line : legend) {
11519 dc.DrawText(line, xp, yp);
11520 yp += hl;
11521 }
11522}
11523
11524void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11525 if (!g_bAllowShipToActive) return;
11526
11527 Route *rt = g_pRouteMan->GetpActiveRoute();
11528 if (!rt) return;
11529
11530 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11531 wxPoint2DDouble pa, pb;
11533 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11534
11535 // set pen
11536 int width =
11537 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11538 if (rt->m_width != wxPENSTYLE_INVALID)
11539 width = rt->m_width; // set route pen style if any
11540 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11541 g_shipToActiveStyle, 5)]; // get setting pen style
11542 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11543 wxColour color =
11544 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11545 : // set setting route pen color
11546 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11547 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11548
11549 dc.SetPen(*mypen);
11550 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11551
11552 if (!Use_Opengl)
11553 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11554 (int)pb.m_y, GetVP(), true);
11555
11556#ifdef ocpnUSE_GL
11557 else {
11558#ifdef USE_ANDROID_GLES2
11559 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11560#else
11561 if (style != wxPENSTYLE_SOLID) {
11562 if (glChartCanvas::dash_map.find(style) !=
11563 glChartCanvas::dash_map.end()) {
11564 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11565 dc.SetPen(*mypen);
11566 }
11567 }
11568 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11569#endif
11570
11571 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11572 (int)pb.m_x, (int)pb.m_y, GetVP());
11573 }
11574#endif
11575 }
11576}
11577
11578void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11579 Route *route = 0;
11580 if (m_routeState >= 2) route = m_pMouseRoute;
11581 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11582 route = m_pMeasureRoute;
11583
11584 if (!route) return;
11585
11586 // Validate route pointer
11587 if (!g_pRouteMan->IsRouteValid(route)) return;
11588
11589 double render_lat = m_cursor_lat;
11590 double render_lon = m_cursor_lon;
11591
11592 int np = route->GetnPoints();
11593 if (np) {
11594 if (g_btouch && (np > 1)) np--;
11595 RoutePoint rp = route->GetPoint(np);
11596 render_lat = rp.m_lat;
11597 render_lon = rp.m_lon;
11598 }
11599
11600 double rhumbBearing, rhumbDist;
11601 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11602 &rhumbBearing, &rhumbDist);
11603 double brg = rhumbBearing;
11604 double dist = rhumbDist;
11605
11606 // Skip GreatCircle rubberbanding on touch devices.
11607 if (!g_btouch) {
11608 double gcBearing, gcBearing2, gcDist;
11609 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11610 m_cursor_lat, &gcDist, &gcBearing,
11611 &gcBearing2);
11612 double gcDistm = gcDist / 1852.0;
11613
11614 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11615 rhumbBearing = 90.;
11616
11617 wxPoint destPoint, lastPoint;
11618
11619 route->m_NextLegGreatCircle = false;
11620 int milesDiff = rhumbDist - gcDistm;
11621 if (milesDiff > 1) {
11622 brg = gcBearing;
11623 dist = gcDistm;
11624 route->m_NextLegGreatCircle = true;
11625 }
11626
11627 // FIXME (MacOS, the first segment is rendered wrong)
11628 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11629 &lastPoint);
11630
11631 if (route->m_NextLegGreatCircle) {
11632 for (int i = 1; i <= milesDiff; i++) {
11633 double p = (double)i * (1.0 / (double)milesDiff);
11634 double pLat, pLon;
11635 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11636 &pLon, &pLat, &gcBearing2);
11637 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11638 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11639 false);
11640 lastPoint = destPoint;
11641 }
11642 } else {
11643 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11644 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11645 false);
11646 if (m_bMeasure_DistCircle) {
11647 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11648 powf((float)(r_rband.y - lastPoint.y), 2));
11649
11650 dc.SetPen(*g_pRouteMan->GetRoutePen());
11651 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11652 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11653 }
11654 }
11655 }
11656 }
11657
11658 wxString routeInfo;
11659 wxArrayString infoArray;
11660 double varBrg = 0;
11661 if (g_bShowTrue)
11662 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11663 0x00B0);
11664
11665 if (g_bShowMag) {
11666 double latAverage = (m_cursor_lat + render_lat) / 2;
11667 double lonAverage = (m_cursor_lon + render_lon) / 2;
11668 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11669
11670 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11671 (int)varBrg, 0x00B0);
11672 }
11673 routeInfo << " " << FormatDistanceAdaptive(dist);
11674 infoArray.Add(routeInfo);
11675 routeInfo.Clear();
11676
11677 // To make it easier to use a route as a bearing on a charted object add for
11678 // the first leg also the reverse bearing.
11679 if (np == 1) {
11680 routeInfo << "Reverse: ";
11681 if (g_bShowTrue)
11682 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11683 (int)(brg + 180.) % 360, 0x00B0);
11684 if (g_bShowMag)
11685 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11686 (int)(varBrg + 180.) % 360, 0x00B0);
11687 infoArray.Add(routeInfo);
11688 routeInfo.Clear();
11689 }
11690
11691 wxString s0;
11692 if (!route->m_bIsInLayer)
11693 s0.Append(_("Route") + ": ");
11694 else
11695 s0.Append(_("Layer Route: "));
11696
11697 double disp_length = route->m_route_length;
11698 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11699 s0 += FormatDistanceAdaptive(disp_length);
11700
11701 infoArray.Add(s0);
11702 routeInfo.Clear();
11703
11704 RouteLegInfo(dc, r_rband, infoArray);
11705
11706 m_brepaint_piano = true;
11707}
11708
11709void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11710 if (!m_bShowVisibleSectors) return;
11711
11712 if (g_bDeferredInitDone) {
11713 // need to re-evaluate sectors?
11714 double rhumbBearing, rhumbDist;
11715 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11716 &rhumbBearing, &rhumbDist);
11717
11718 if (rhumbDist > 0.05) // miles
11719 {
11720 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11721 m_sectorlegsVisible);
11722 m_sector_glat = gLat;
11723 m_sector_glon = gLon;
11724 }
11725 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11726 }
11727}
11728
11729void ChartCanvas::WarpPointerDeferred(int x, int y) {
11730 warp_x = x;
11731 warp_y = y;
11732 warp_flag = true;
11733}
11734
11735int s_msg;
11736
11737void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11738 if (!ps52plib) return;
11739
11740 if (VPoint.b_quilt) { // quilted
11741 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11742
11743 if (m_pQuilt->IsQuiltVector()) {
11744 if (ps52plib->GetStateHash() != m_s52StateHash) {
11745 UpdateS52State();
11746 m_s52StateHash = ps52plib->GetStateHash();
11747 }
11748 }
11749 } else {
11750 if (ps52plib->GetStateHash() != m_s52StateHash) {
11751 UpdateS52State();
11752 m_s52StateHash = ps52plib->GetStateHash();
11753 }
11754 }
11755
11756 // Plugin charts
11757 bool bSendPlibState = true;
11758 if (VPoint.b_quilt) { // quilted
11759 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11760 }
11761
11762 if (bSendPlibState) {
11763 wxJSONValue v;
11764 v["OpenCPN Version Major"] = VERSION_MAJOR;
11765 v["OpenCPN Version Minor"] = VERSION_MINOR;
11766 v["OpenCPN Version Patch"] = VERSION_PATCH;
11767 v["OpenCPN Version Date"] = VERSION_DATE;
11768 v["OpenCPN Version Full"] = VERSION_FULL;
11769
11770 // S52PLIB state
11771 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11772 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11773 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11774 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11775 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11776 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11777 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11778
11779 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11780
11781 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11782 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11783
11784 // Global S52 options
11785
11786 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11787 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11788 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11789 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11790 ps52plib->m_bShowS57ImportantTextOnly;
11791 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11792 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11793 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11794 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11795 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11796
11797 // Some global GUI parameters, for completeness
11798 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11799 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11800 v["OpenCPN Scale Factor Exp"] =
11801 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11802 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11803
11804 wxJSONWriter w;
11805 wxString out;
11806 w.Write(v, out);
11807
11808 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11809 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11810 g_lastS52PLIBPluginMessage = out;
11811 }
11812 }
11813}
11814int spaint;
11815int s_in_update;
11816void ChartCanvas::OnPaint(wxPaintEvent &event) {
11817 wxPaintDC dc(this);
11818
11819 // GetToolbar()->Show( m_bToolbarEnable );
11820
11821 // Paint updates may have been externally disabled (temporarily, to avoid
11822 // Yield() recursion performance loss) It is important that the wxPaintDC is
11823 // built, even if we elect to not process this paint message. Otherwise, the
11824 // paint message may not be removed from the message queue, esp on Windows.
11825 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11826
11827 if (!m_b_paint_enable) {
11828 return;
11829 }
11830
11831 // If necessary, reconfigure the S52 PLIB
11833
11834#ifdef ocpnUSE_GL
11835 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11836
11837 if (m_glcc && g_bopengl) {
11838 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11839 s_in_update++;
11840 m_glcc->Update();
11841 s_in_update--;
11842 }
11843
11844 return;
11845 }
11846#endif
11847
11848 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11849
11850 wxRegion ru = GetUpdateRegion();
11851
11852 int rx, ry, rwidth, rheight;
11853 ru.GetBox(rx, ry, rwidth, rheight);
11854
11855#ifdef ocpnUSE_DIBSECTION
11856 ocpnMemDC temp_dc;
11857#else
11858 wxMemoryDC temp_dc;
11859#endif
11860
11861 long height = GetVP().pix_height;
11862
11863#ifdef __WXMAC__
11864 // On OS X we have to explicitly extend the region for the piano area
11865 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11866 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11867 height += m_Piano->GetHeight();
11868#endif // __WXMAC__
11869 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11870
11871 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11872 if (pthumbwin) {
11873 int thumbx, thumby, thumbsx, thumbsy;
11874 pthumbwin->GetPosition(&thumbx, &thumby);
11875 pthumbwin->GetSize(&thumbsx, &thumbsy);
11876 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11877
11878 if (pthumbwin->IsShown()) {
11879 rgn_chart.Subtract(rgn_thumbwin);
11880 ru.Subtract(rgn_thumbwin);
11881 }
11882 }
11883
11884 // subtract the chart bar if it isn't transparent, and determine if we need to
11885 // paint it
11886 wxRegion rgn_blit = ru;
11887 if (g_bShowChartBar) {
11888 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11889 GetClientSize().x, m_Piano->GetHeight());
11890
11891 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11892 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11893 if (style->chartStatusWindowTransparent)
11894 m_brepaint_piano = true;
11895 else
11896 ru.Subtract(chart_bar_rect);
11897 }
11898 }
11899
11900 if (m_Compass && m_Compass->IsShown()) {
11901 wxRect compassRect = m_Compass->GetRect();
11902 if (ru.Contains(compassRect) != wxOutRegion) {
11903 ru.Subtract(compassRect);
11904 }
11905 }
11906
11907 if (m_notification_button) {
11908 wxRect noteRect = m_notification_button->GetRect();
11909 if (ru.Contains(noteRect) != wxOutRegion) {
11910 ru.Subtract(noteRect);
11911 }
11912 }
11913
11914 // Is this viewpoint the same as the previously painted one?
11915 bool b_newview = true;
11916
11917 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11918 (m_cache_vp.rotation == VPoint.rotation) &&
11919 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11920 m_cache_vp.IsValid()) {
11921 b_newview = false;
11922 }
11923
11924 // If the ViewPort is skewed or rotated, we may be able to use the cached
11925 // rotated bitmap.
11926 bool b_rcache_ok = false;
11927 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11928 b_rcache_ok = !b_newview;
11929
11930 // Make a special VP
11931 if (VPoint.b_MercatorProjectionOverride)
11932 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11933 ViewPort svp = VPoint;
11934
11935 svp.pix_width = svp.rv_rect.width;
11936 svp.pix_height = svp.rv_rect.height;
11937
11938 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11939 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11940 // VPoint.rv_rect.height);
11941
11942 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11943
11944 // If we are going to use the cached rotated image, there is no need to fetch
11945 // any chart data and this will do it...
11946 if (b_rcache_ok) chart_get_region.Clear();
11947
11948 // Blit pan acceleration
11949 if (VPoint.b_quilt) // quilted
11950 {
11951 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11952
11953 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11954
11955 bool busy = false;
11956 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11957 m_cache_vp.rotation != VPoint.rotation)) {
11958 AbstractPlatform::ShowBusySpinner();
11959 busy = true;
11960 }
11961
11962 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11963 (m_working_bm.GetHeight() != svp.pix_height))
11964 m_working_bm.Create(svp.pix_width, svp.pix_height,
11965 -1); // make sure the target is big enoug
11966
11967 if (fabs(VPoint.rotation) < 0.01) {
11968 bool b_save = true;
11969
11970 if (g_SencThreadManager) {
11971 if (g_SencThreadManager->GetJobCount()) {
11972 b_save = false;
11973 m_cache_vp.Invalidate();
11974 }
11975 }
11976
11977 // If the saved wxBitmap from last OnPaint is useable
11978 // calculate the blit parameters
11979
11980 // We can only do screen blit painting if subsequent ViewPorts differ by
11981 // whole pixels So, in small scale bFollow mode, force the full screen
11982 // render. This seems a hack....There may be better logic here.....
11983
11984 // if(m_bFollow)
11985 // b_save = false;
11986
11987 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11988 if (b_newview) {
11989 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11990 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11991
11992 int dy = c_new.y - c_old.y;
11993 int dx = c_new.x - c_old.x;
11994
11995 // printf("In OnPaint Trying Blit dx: %d
11996 // dy:%d\n\n", dx, dy);
11997
11998 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11999 if (dx || dy) {
12000 // Blit the reuseable portion of the cached wxBitmap to a working
12001 // bitmap
12002 temp_dc.SelectObject(m_working_bm);
12003
12004 wxMemoryDC cache_dc;
12005 cache_dc.SelectObject(m_cached_chart_bm);
12006
12007 if (dy > 0) {
12008 if (dx > 0) {
12009 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12010 VPoint.pix_height - dy, &cache_dc, dx, dy);
12011 } else {
12012 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12013 VPoint.pix_height - dy, &cache_dc, 0, dy);
12014 }
12015
12016 } else {
12017 if (dx > 0) {
12018 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12019 VPoint.pix_height + dy, &cache_dc, dx, 0);
12020 } else {
12021 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12022 VPoint.pix_height + dy, &cache_dc, 0, 0);
12023 }
12024 }
12025
12026 OCPNRegion update_region;
12027 if (dy) {
12028 if (dy > 0)
12029 update_region.Union(
12030 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12031 else
12032 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12033 }
12034
12035 if (dx) {
12036 if (dx > 0)
12037 update_region.Union(
12038 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12039 else
12040 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12041 }
12042
12043 // Render the new region
12044 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12045 update_region);
12046 cache_dc.SelectObject(wxNullBitmap);
12047 } else {
12048 // No sensible (dx, dy) change in the view, so use the cached
12049 // member bitmap
12050 temp_dc.SelectObject(m_cached_chart_bm);
12051 b_save = false;
12052 }
12053 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12054
12055 } else // not blitable
12056 {
12057 temp_dc.SelectObject(m_working_bm);
12058 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12059 chart_get_region);
12060 }
12061 } else {
12062 // No change in the view, so use the cached member bitmap2
12063 temp_dc.SelectObject(m_cached_chart_bm);
12064 b_save = false;
12065 }
12066 } else // cached bitmap is not yet valid
12067 {
12068 temp_dc.SelectObject(m_working_bm);
12069 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12070 chart_get_region);
12071 }
12072
12073 // Save the fully rendered quilt image as a wxBitmap member of this class
12074 if (b_save) {
12075 // if((m_cached_chart_bm.GetWidth() !=
12076 // svp.pix_width) ||
12077 // (m_cached_chart_bm.GetHeight() !=
12078 // svp.pix_height))
12079 // m_cached_chart_bm.Create(svp.pix_width,
12080 // svp.pix_height, -1); // target wxBitmap
12081 // is big enough
12082 wxMemoryDC scratch_dc_0;
12083 scratch_dc_0.SelectObject(m_cached_chart_bm);
12084 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12085
12086 scratch_dc_0.SelectObject(wxNullBitmap);
12087
12088 m_bm_cache_vp =
12089 VPoint; // save the ViewPort associated with the cached wxBitmap
12090 }
12091 }
12092
12093 else // quilted, rotated
12094 {
12095 temp_dc.SelectObject(m_working_bm);
12096 OCPNRegion chart_get_all_region(
12097 wxRect(0, 0, svp.pix_width, svp.pix_height));
12098 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12099 chart_get_all_region);
12100 }
12101
12102 AbstractPlatform::HideBusySpinner();
12103
12104 }
12105
12106 else // not quilted
12107 {
12108 if (!m_singleChart) {
12109 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12110 dc.Clear();
12111 return;
12112 }
12113
12114 if (!chart_get_region.IsEmpty()) {
12115 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12116 }
12117 }
12118
12119 if (temp_dc.IsOk()) {
12120 // Arrange to render the World Chart vector data behind the rendered
12121 // current chart so that uncovered canvas areas show at least the world
12122 // chart.
12123 OCPNRegion chartValidRegion;
12124 if (!VPoint.b_quilt) {
12125 // Make a region covering the current chart on the canvas
12126
12127 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12128 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12129 else {
12130 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12131 // require that the viewport passed here have pix_width and pix_height
12132 // set to the actual display, not the virtual (rv_rect) sizes
12133 // (the vector calculations require the virtual sizes in svp)
12134
12135 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12136 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12137 }
12138 } else
12139 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12140
12141 temp_dc.DestroyClippingRegion();
12142
12143 // Copy current chart region
12144 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12145
12146 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12147
12148 if (!backgroundRegion.IsEmpty()) {
12149 // Draw the Background Chart only in the areas NOT covered by the
12150 // current chart view
12151
12152 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12153 clipping regions with more than 1 rectangle so... */
12154 wxColour water = pWorldBackgroundChart->water;
12155 if (water.IsOk()) {
12156 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12157 temp_dc.SetBrush(wxBrush(water));
12158 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12159 while (upd.HaveRects()) {
12160 wxRect rect = upd.GetRect();
12161 temp_dc.DrawRectangle(rect);
12162 upd.NextRect();
12163 }
12164 }
12165 // Associate with temp_dc
12166 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12167 temp_dc.SetDeviceClippingRegion(*clip_region);
12168 delete clip_region;
12169
12170 ocpnDC bgdc(temp_dc);
12171 double r = VPoint.rotation;
12172 SetVPRotation(VPoint.skew);
12173
12174 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12175 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12176
12177 SetVPRotation(r);
12178 }
12179 } // temp_dc.IsOk();
12180
12181 wxMemoryDC *pChartDC = &temp_dc;
12182 wxMemoryDC rotd_dc;
12183
12184 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12185 // Can we use the current rotated image cache?
12186 if (!b_rcache_ok) {
12187#ifdef __WXMSW__
12188 wxMemoryDC tbase_dc;
12189 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12190 tbase_dc.SelectObject(bm_base);
12191 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12192 tbase_dc.SelectObject(wxNullBitmap);
12193#else
12194 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12195#endif
12196
12197 wxImage base_image;
12198 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12199
12200 // Use a local static image rotator to improve wxWidgets code profile
12201 // Especially, on GTK the wxRound and wxRealPoint functions are very
12202 // expensive.....
12203
12204 double angle = GetVP().skew - GetVP().rotation;
12205 wxImage ri;
12206 bool b_rot_ok = false;
12207 if (base_image.IsOk()) {
12208 ViewPort rot_vp = GetVP();
12209
12210 m_b_rot_hidef = false;
12211
12212 ri = Image_Rotate(
12213 base_image, angle,
12214 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12215 m_b_rot_hidef, &m_roffset);
12216
12217 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12218 (rot_vp.rotation == VPoint.rotation) &&
12219 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12220 rot_vp.IsValid() && (ri.IsOk())) {
12221 b_rot_ok = true;
12222 }
12223 }
12224
12225 if (b_rot_ok) {
12226 delete m_prot_bm;
12227 m_prot_bm = new wxBitmap(ri);
12228 }
12229
12230 m_roffset.x += VPoint.rv_rect.x;
12231 m_roffset.y += VPoint.rv_rect.y;
12232 }
12233
12234 if (m_prot_bm && m_prot_bm->IsOk()) {
12235 rotd_dc.SelectObject(*m_prot_bm);
12236 pChartDC = &rotd_dc;
12237 } else {
12238 pChartDC = &temp_dc;
12239 m_roffset = wxPoint(0, 0);
12240 }
12241 } else { // unrotated
12242 pChartDC = &temp_dc;
12243 m_roffset = wxPoint(0, 0);
12244 }
12245
12246 wxPoint offset = m_roffset;
12247
12248 // Save the PixelCache viewpoint for next time
12249 m_cache_vp = VPoint;
12250
12251 // Set up a scratch DC for overlay objects
12252 wxMemoryDC mscratch_dc;
12253 mscratch_dc.SelectObject(*pscratch_bm);
12254
12255 mscratch_dc.ResetBoundingBox();
12256 mscratch_dc.DestroyClippingRegion();
12257 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12258
12259 // Blit the externally invalidated areas of the chart onto the scratch dc
12260 wxRegionIterator upd(rgn_blit); // get the update rect list
12261 while (upd) {
12262 wxRect rect = upd.GetRect();
12263
12264 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12265 rect.x - offset.x, rect.y - offset.y);
12266 upd++;
12267 }
12268
12269 // If multi-canvas, indicate which canvas has keyboard focus
12270 // by drawing a simple blue bar at the top.
12271 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12272 if (this == wxWindow::FindFocus()) {
12273 g_focusCanvas = this;
12274
12275 wxColour colour = GetGlobalColor("BLUE4");
12276 mscratch_dc.SetPen(wxPen(colour));
12277 mscratch_dc.SetBrush(wxBrush(colour));
12278
12279 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12280 mscratch_dc.DrawRectangle(activeRect);
12281 }
12282 }
12283
12284 // Any MBtiles?
12285 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12286 unsigned int im = stackIndexArray.size();
12287 if (VPoint.b_quilt && im > 0) {
12288 std::vector<int> tiles_to_show;
12289 for (unsigned int is = 0; is < im; is++) {
12290 const ChartTableEntry &cte =
12291 ChartData->GetChartTableEntry(stackIndexArray[is]);
12292 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12293 continue;
12294 }
12295 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12296 tiles_to_show.push_back(stackIndexArray[is]);
12297 }
12298 }
12299
12300 if (tiles_to_show.size())
12301 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12302 }
12303
12304 // May get an unexpected OnPaint call while switching display modes
12305 // Guard for that.
12306 if (!g_bopengl) {
12307 ocpnDC scratch_dc(mscratch_dc);
12308 RenderAlertMessage(mscratch_dc, GetVP());
12309 }
12310
12311#if 0
12312 // quiting?
12313 if (g_bquiting) {
12314#ifdef ocpnUSE_DIBSECTION
12315 ocpnMemDC q_dc;
12316#else
12317 wxMemoryDC q_dc;
12318#endif
12319 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12320 q_dc.SelectObject(qbm);
12321
12322 // Get a copy of the screen
12323 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12324
12325 // Draw a rectangle over the screen with a stipple brush
12326 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12327 q_dc.SetBrush(qbr);
12328 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12329
12330 // Blit back into source
12331 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12332 wxCOPY);
12333
12334 q_dc.SelectObject(wxNullBitmap);
12335 }
12336#endif
12337
12338#if 0
12339 // It is possible that this two-step method may be reuired for some platforms.
12340 // So, retain in the code base to aid recovery if necessary
12341
12342 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12343 if( VPoint.b_quilt ) {
12344 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12345 ChartBase *chart = m_pQuilt->GetRefChart();
12346 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12347
12348 // Clear the text Global declutter list
12349 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12350 if(ChPI)
12351 ChPI->ClearPLIBTextList();
12352 else{
12353 if(ps52plib)
12354 ps52plib->ClearTextList();
12355 }
12356
12357 wxMemoryDC t_dc;
12358 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12359
12360 wxColor maskBackground = wxColour(1,0,0);
12361 t_dc.SelectObject( qbm );
12362 t_dc.SetBackground(wxBrush(maskBackground));
12363 t_dc.Clear();
12364
12365 // Copy the scratch DC into the new bitmap
12366 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12367
12368 // Render the text to the new bitmap
12369 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12370 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12371
12372 // Copy the new bitmap back to the scratch dc
12373 wxRegionIterator upd_final( ru );
12374 while( upd_final ) {
12375 wxRect rect = upd_final.GetRect();
12376 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12377 upd_final++;
12378 }
12379
12380 t_dc.SelectObject( wxNullBitmap );
12381 }
12382 }
12383 }
12384#endif
12385 // Direct rendering model...
12386 if (VPoint.b_quilt) {
12387 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12388 ChartBase *chart = m_pQuilt->GetRefChart();
12389 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12390 // Clear the text Global declutter list
12391 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12392 if (ChPI)
12393 ChPI->ClearPLIBTextList();
12394 else {
12395 if (ps52plib) ps52plib->ClearTextList();
12396 }
12397
12398 // Render the text directly to the scratch bitmap
12399 OCPNRegion chart_all_text_region(
12400 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12401
12402 if (g_bShowChartBar && m_Piano) {
12403 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12404 GetVP().pix_width, m_Piano->GetHeight());
12405
12406 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12407 if (!style->chartStatusWindowTransparent)
12408 chart_all_text_region.Subtract(chart_bar_rect);
12409 }
12410
12411 if (m_Compass && m_Compass->IsShown()) {
12412 wxRect compassRect = m_Compass->GetRect();
12413 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12414 chart_all_text_region.Subtract(compassRect);
12415 }
12416 }
12417
12418 mscratch_dc.DestroyClippingRegion();
12419
12420 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12421 chart_all_text_region);
12422 }
12423 }
12424 }
12425
12426 // Now that charts are fully rendered, apply the overlay objects as decals.
12427 ocpnDC scratch_dc(mscratch_dc);
12428 DrawOverlayObjects(scratch_dc, ru);
12429
12430 // And finally, blit the scratch dc onto the physical dc
12431 wxRegionIterator upd_final(rgn_blit);
12432 while (upd_final) {
12433 wxRect rect = upd_final.GetRect();
12434 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12435 rect.y);
12436 upd_final++;
12437 }
12438
12439 // Deselect the chart bitmap from the temp_dc, so that it will not be
12440 // destroyed in the temp_dc dtor
12441 temp_dc.SelectObject(wxNullBitmap);
12442 // And for the scratch bitmap
12443 mscratch_dc.SelectObject(wxNullBitmap);
12444
12445 dc.DestroyClippingRegion();
12446
12447 PaintCleanup();
12448}
12449
12450void ChartCanvas::PaintCleanup() {
12451 // Handle the current graphic window, if present
12452 if (m_inPinch) return;
12453
12454 if (pCwin) {
12455 pCwin->Show();
12456 if (m_bTCupdate) {
12457 pCwin->Refresh();
12458 pCwin->Update();
12459 }
12460 }
12461
12462 // And set flags for next time
12463 m_bTCupdate = false;
12464
12465 // Handle deferred WarpPointer
12466 if (warp_flag) {
12467 WarpPointer(warp_x, warp_y);
12468 warp_flag = false;
12469 }
12470
12471 // Start movement timers, this runs nearly immediately.
12472 // the reason we cannot simply call it directly is the
12473 // refresh events it emits may be blocked from this paint event
12474 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12475 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12476}
12477
12478#if 0
12479wxColour GetErrorGraphicColor(double val)
12480{
12481 /*
12482 double valm = wxMin(val_max, val);
12483
12484 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12485 unsigned char red = (unsigned char)(255 * (valm/val_max));
12486
12487 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12488
12489 hv.saturation = 1.0;
12490 hv.value = 1.0;
12491
12492 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12493 return wxColour(rv.red, rv.green, rv.blue);
12494 */
12495
12496 // HTML colors taken from NOAA WW3 Web representation
12497 wxColour c;
12498 if((val > 0) && (val < 1)) c.Set("#002ad9");
12499 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12500 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12501 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12502 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12503 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12504 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12505 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12506 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12507 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12508 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12509 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12510 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12511 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12512 else if((val >= 30) && (val < 36)) c.Set("#870000");
12513 else if((val >= 36) && (val < 42)) c.Set("#690000");
12514 else if((val >= 42) && (val < 48)) c.Set("#550000");
12515 else if( val >= 48) c.Set("#410000");
12516
12517 return c;
12518}
12519
12520void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12521{
12522 wxImage gr_image(vp->pix_width, vp->pix_height);
12523 gr_image.InitAlpha();
12524
12525 double maxval = -10000;
12526 double minval = 10000;
12527
12528 double rlat, rlon;
12529 double glat, glon;
12530
12531 GetCanvasPixPoint(0, 0, rlat, rlon);
12532
12533 for(int i=1; i < vp->pix_height-1; i++)
12534 {
12535 for(int j=0; j < vp->pix_width; j++)
12536 {
12537 // Reference mercator value
12538// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12539
12540 // Georef value
12541 GetCanvasPixPoint(j, i, glat, glon);
12542
12543 maxval = wxMax(maxval, (glat - rlat));
12544 minval = wxMin(minval, (glat - rlat));
12545
12546 }
12547 rlat = glat;
12548 }
12549
12550 GetCanvasPixPoint(0, 0, rlat, rlon);
12551 for(int i=1; i < vp->pix_height-1; i++)
12552 {
12553 for(int j=0; j < vp->pix_width; j++)
12554 {
12555 // Reference mercator value
12556// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12557
12558 // Georef value
12559 GetCanvasPixPoint(j, i, glat, glon);
12560
12561 double f = ((glat - rlat)-minval)/(maxval - minval);
12562
12563 double dy = (f * 40);
12564
12565 wxColour c = GetErrorGraphicColor(dy);
12566 unsigned char r = c.Red();
12567 unsigned char g = c.Green();
12568 unsigned char b = c.Blue();
12569
12570 gr_image.SetRGB(j, i, r,g,b);
12571 if((glat - rlat )!= 0)
12572 gr_image.SetAlpha(j, i, 128);
12573 else
12574 gr_image.SetAlpha(j, i, 255);
12575
12576 }
12577 rlat = glat;
12578 }
12579
12580 // Create a Bitmap
12581 wxBitmap *pbm = new wxBitmap(gr_image);
12582 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12583 pbm->SetMask(gr_mask);
12584
12585 pmdc->DrawBitmap(*pbm, 0,0);
12586
12587 delete pbm;
12588
12589}
12590
12591#endif
12592
12593void ChartCanvas::CancelMouseRoute() {
12594 m_routeState = 0;
12595 m_pMouseRoute = NULL;
12596 m_bDrawingRoute = false;
12597}
12598
12599int ChartCanvas::GetNextContextMenuId() {
12600 return CanvasMenuHandler::GetNextContextMenuId();
12601}
12602
12603bool ChartCanvas::SetCursor(const wxCursor &c) {
12604#ifdef ocpnUSE_GL
12605 if (g_bopengl && m_glcc)
12606 return m_glcc->SetCursor(c);
12607 else
12608#endif
12609 return wxWindow::SetCursor(c);
12610}
12611
12612void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12613 if (g_bquiting) return;
12614 // Keep the mouse position members up to date
12615 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12616
12617 // Retrigger the route leg popup timer
12618 // This handles the case when the chart is moving in auto-follow mode,
12619 // but no user mouse input is made. The timer handler may Hide() the
12620 // popup if the chart moved enough n.b. We use slightly longer oneshot
12621 // value to allow this method's Refresh() to complete before potentially
12622 // getting another Refresh() in the popup timer handler.
12623 if (!m_RolloverPopupTimer.IsRunning() &&
12624 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12625 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12626 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12627 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12628
12629#ifdef ocpnUSE_GL
12630 if (m_glcc && g_bopengl) {
12631 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12632 // overlay objects.
12633 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12634
12635 m_glcc->Refresh(eraseBackground,
12636 NULL); // We always are going to render the entire screen
12637 // anyway, so make
12638 // sure that the window managers understand the invalid area
12639 // is actually the entire client area.
12640
12641 // We need to selectively Refresh some child windows, if they are visible.
12642 // Note that some children are refreshed elsewhere on timer ticks, so don't
12643 // need attention here.
12644
12645 // Thumbnail chart
12646 if (pthumbwin && pthumbwin->IsShown()) {
12647 pthumbwin->Raise();
12648 pthumbwin->Refresh(false);
12649 }
12650
12651 // ChartInfo window
12652 if (m_pCIWin && m_pCIWin->IsShown()) {
12653 m_pCIWin->Raise();
12654 m_pCIWin->Refresh(false);
12655 }
12656
12657 // if(g_MainToolbar)
12658 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12659
12660 } else
12661#endif
12662 wxWindow::Refresh(eraseBackground, rect);
12663}
12664
12665void ChartCanvas::Update() {
12666 if (m_glcc && g_bopengl) {
12667#ifdef ocpnUSE_GL
12668 m_glcc->Update();
12669#endif
12670 } else
12671 wxWindow::Update();
12672}
12673
12674void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12675 if (!pemboss) return;
12676 int x = pemboss->x, y = pemboss->y;
12677 const double factor = 200;
12678
12679 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12680 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12681 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12682
12683 // Grab a snipped image out of the chart
12684 wxMemoryDC snip_dc;
12685 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12686 snip_dc.SelectObject(snip_bmp);
12687
12688 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12689 snip_dc.SelectObject(wxNullBitmap);
12690
12691 wxImage snip_img = snip_bmp.ConvertToImage();
12692
12693 // Apply Emboss map to the snip image
12694 unsigned char *pdata = snip_img.GetData();
12695 if (pdata) {
12696 for (int y = 0; y < pemboss->height; y++) {
12697 int map_index = (y * pemboss->width);
12698 for (int x = 0; x < pemboss->width; x++) {
12699 double val = (pemboss->pmap[map_index] * factor) / 256.;
12700
12701 int nred = (int)((*pdata) + val);
12702 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12703 *pdata++ = (unsigned char)nred;
12704
12705 int ngreen = (int)((*pdata) + val);
12706 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12707 *pdata++ = (unsigned char)ngreen;
12708
12709 int nblue = (int)((*pdata) + val);
12710 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12711 *pdata++ = (unsigned char)nblue;
12712
12713 map_index++;
12714 }
12715 }
12716 }
12717
12718 // Convert embossed snip to a bitmap
12719 wxBitmap emb_bmp(snip_img);
12720
12721 // Map to another memoryDC
12722 wxMemoryDC result_dc;
12723 result_dc.SelectObject(emb_bmp);
12724
12725 // Blit to target
12726 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12727
12728 result_dc.SelectObject(wxNullBitmap);
12729}
12730
12731emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12732 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12733
12734 if (GetQuiltMode()) {
12735 // disable Overzoom indicator for MBTiles
12736 int refIndex = GetQuiltRefChartdbIndex();
12737 if (refIndex >= 0) {
12738 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12739 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12740 if (current_type == CHART_TYPE_MBTILES) {
12741 ChartBase *pChart = m_pQuilt->GetRefChart();
12742 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12743 if (ptc) {
12744 zoom_factor = ptc->GetZoomFactor();
12745 }
12746 }
12747 }
12748
12749 if (zoom_factor <= 3.9) return NULL;
12750 } else {
12751 if (m_singleChart) {
12752 if (zoom_factor <= 3.9) return NULL;
12753 } else
12754 return NULL;
12755 }
12756
12757 if (m_pEM_OverZoom) {
12758 m_pEM_OverZoom->x = 4;
12759 m_pEM_OverZoom->y = 0;
12760 if (g_MainToolbar && IsPrimaryCanvas()) {
12761 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12762 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12763 }
12764 }
12765 return m_pEM_OverZoom;
12766}
12767
12768void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12769 GridDraw(dc);
12770
12771 // bool pluginOverlayRender = true;
12772 //
12773 // if(g_canvasConfig > 0){ // Multi canvas
12774 // if(IsPrimaryCanvas())
12775 // pluginOverlayRender = false;
12776 // }
12777
12778 g_overlayCanvas = this;
12779
12780 if (g_pi_manager) {
12781 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12782 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12784 }
12785
12786 AISDrawAreaNotices(dc, GetVP(), this);
12787
12788 wxDC *pdc = dc.GetDC();
12789 if (pdc) {
12790 pdc->DestroyClippingRegion();
12791 wxDCClipper(*pdc, ru);
12792 }
12793
12794 if (m_bShowNavobjects) {
12795 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12796 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12797 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12798 DrawAnchorWatchPoints(dc);
12799 } else {
12800 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12801 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12802 }
12803
12804 AISDraw(dc, GetVP(), this);
12805 ShipDraw(dc);
12806 AlertDraw(dc);
12807
12808 RenderVisibleSectorLights(dc);
12809
12810 RenderAllChartOutlines(dc, GetVP());
12811 RenderRouteLegs(dc);
12812 RenderShipToActive(dc, false);
12813 ScaleBarDraw(dc);
12814 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12815 if (g_pi_manager) {
12816 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12818 }
12819
12820 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12821 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12822
12823 if (g_pi_manager) {
12824 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12826 }
12827
12828 if (m_bShowTide) {
12829 RebuildTideSelectList(GetVP().GetBBox());
12830 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12831 }
12832
12833 if (m_bShowCurrent) {
12834 RebuildCurrentSelectList(GetVP().GetBBox());
12835 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12836 }
12837
12838 if (!g_PrintingInProgress) {
12839 if (IsPrimaryCanvas()) {
12840 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12841 }
12842
12843 if (IsPrimaryCanvas()) {
12844 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12845 }
12846
12847 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12848
12849 if (m_pTrackRolloverWin) {
12850 m_pTrackRolloverWin->Draw(dc);
12851 m_brepaint_piano = true;
12852 }
12853
12854 if (m_pRouteRolloverWin) {
12855 m_pRouteRolloverWin->Draw(dc);
12856 m_brepaint_piano = true;
12857 }
12858
12859 if (m_pAISRolloverWin) {
12860 m_pAISRolloverWin->Draw(dc);
12861 m_brepaint_piano = true;
12862 }
12863 if (m_brepaint_piano && g_bShowChartBar) {
12864 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12865 }
12866
12867 if (m_Compass) m_Compass->Paint(dc);
12868
12869 if (!g_CanvasHideNotificationIcon) {
12870 if (IsPrimaryCanvas()) {
12871 auto &noteman = NotificationManager::GetInstance();
12872 if (noteman.GetNotificationCount()) {
12873 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12874 if (m_notification_button->UpdateStatus()) Refresh();
12875 m_notification_button->Show(true);
12876 m_notification_button->Paint(dc);
12877 } else {
12878 m_notification_button->Show(false);
12879 }
12880 }
12881 }
12882 }
12883 if (g_pi_manager) {
12884 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12886 }
12887}
12888
12889emboss_data *ChartCanvas::EmbossDepthScale() {
12890 if (!m_bShowDepthUnits) return NULL;
12891
12892 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12893
12894 if (GetQuiltMode()) {
12895 wxString s = m_pQuilt->GetQuiltDepthUnit();
12896 s.MakeUpper();
12897 if (s == "FEET")
12898 depth_unit_type = DEPTH_UNIT_FEET;
12899 else if (s.StartsWith("FATHOMS"))
12900 depth_unit_type = DEPTH_UNIT_FATHOMS;
12901 else if (s.StartsWith("METERS"))
12902 depth_unit_type = DEPTH_UNIT_METERS;
12903 else if (s.StartsWith("METRES"))
12904 depth_unit_type = DEPTH_UNIT_METERS;
12905 else if (s.StartsWith("METRIC"))
12906 depth_unit_type = DEPTH_UNIT_METERS;
12907 else if (s.StartsWith("METER"))
12908 depth_unit_type = DEPTH_UNIT_METERS;
12909
12910 } else {
12911 if (m_singleChart) {
12912 depth_unit_type = m_singleChart->GetDepthUnitType();
12913 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12914 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12915 }
12916 }
12917
12918 emboss_data *ped = NULL;
12919 switch (depth_unit_type) {
12920 case DEPTH_UNIT_FEET:
12921 ped = m_pEM_Feet;
12922 break;
12923 case DEPTH_UNIT_METERS:
12924 ped = m_pEM_Meters;
12925 break;
12926 case DEPTH_UNIT_FATHOMS:
12927 ped = m_pEM_Fathoms;
12928 break;
12929 default:
12930 return NULL;
12931 }
12932
12933 ped->x = (GetVP().pix_width - ped->width);
12934
12935 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12936 wxRect r = m_Compass->GetRect();
12937 ped->y = r.y + r.height;
12938 } else {
12939 ped->y = 40;
12940 }
12941 return ped;
12942}
12943
12944void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12945 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12946 wxFont font;
12947 if (style->embossFont == wxEmptyString) {
12948 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12949 font = *dFont;
12950 font.SetPointSize(60);
12951 font.SetWeight(wxFONTWEIGHT_BOLD);
12952 } else
12953 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12954 wxFONTWEIGHT_BOLD, false, style->embossFont);
12955
12956 int emboss_width = 500;
12957 int emboss_height = 200;
12958
12959 // Free any existing emboss maps
12960 delete m_pEM_Feet;
12961 delete m_pEM_Meters;
12962 delete m_pEM_Fathoms;
12963
12964 // Create the 3 DepthUnit emboss map structures
12965 m_pEM_Feet =
12966 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12967 m_pEM_Meters =
12968 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12969 m_pEM_Fathoms =
12970 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12971}
12972
12973#define OVERZOOM_TEXT _("OverZoom")
12974
12975void ChartCanvas::SetOverzoomFont() {
12976 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12977 int w, h;
12978
12979 wxFont font;
12980 if (style->embossFont == wxEmptyString) {
12981 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12982 font = *dFont;
12983 font.SetPointSize(40);
12984 font.SetWeight(wxFONTWEIGHT_BOLD);
12985 } else
12986 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12987 wxFONTWEIGHT_BOLD, false, style->embossFont);
12988
12989 wxClientDC dc(this);
12990 dc.SetFont(font);
12991 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12992
12993 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12994 font.SetPointSize(font.GetPointSize() - 1);
12995 dc.SetFont(font);
12996 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12997 }
12998 m_overzoomFont = font;
12999 m_overzoomTextWidth = w;
13000 m_overzoomTextHeight = h;
13001}
13002
13003void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13004 delete m_pEM_OverZoom;
13005
13006 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13007 m_pEM_OverZoom =
13008 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13009 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13010}
13011
13012emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13013 int height, const wxString &str,
13014 ColorScheme cs) {
13015 int *pmap;
13016
13017 // Create a temporary bitmap
13018 wxBitmap bmp(width, height, -1);
13019
13020 // Create a memory DC
13021 wxMemoryDC temp_dc;
13022 temp_dc.SelectObject(bmp);
13023
13024 // Paint on it
13025 temp_dc.SetBackground(*wxWHITE_BRUSH);
13026 temp_dc.SetTextBackground(*wxWHITE);
13027 temp_dc.SetTextForeground(*wxBLACK);
13028
13029 temp_dc.Clear();
13030
13031 temp_dc.SetFont(font);
13032
13033 int str_w, str_h;
13034 temp_dc.GetTextExtent(str, &str_w, &str_h);
13035 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13036 temp_dc.DrawText(str, 1, 1);
13037
13038 // Deselect the bitmap
13039 temp_dc.SelectObject(wxNullBitmap);
13040
13041 // Convert bitmap the wxImage for manipulation
13042 wxImage img = bmp.ConvertToImage();
13043
13044 int image_width = str_w * 105 / 100;
13045 int image_height = str_h * 105 / 100;
13046 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13047 wxMin(image_height, img.GetHeight()));
13048 wxImage imgs = img.GetSubImage(r);
13049
13050 double val_factor;
13051 switch (cs) {
13052 case GLOBAL_COLOR_SCHEME_DAY:
13053 default:
13054 val_factor = 1;
13055 break;
13056 case GLOBAL_COLOR_SCHEME_DUSK:
13057 val_factor = .5;
13058 break;
13059 case GLOBAL_COLOR_SCHEME_NIGHT:
13060 val_factor = .25;
13061 break;
13062 }
13063
13064 int val;
13065 int index;
13066 const int w = imgs.GetWidth();
13067 const int h = imgs.GetHeight();
13068 pmap = (int *)calloc(w * h * sizeof(int), 1);
13069 // Create emboss map by differentiating the emboss image
13070 // and storing integer results in pmap
13071 // n.b. since the image is B/W, it is sufficient to check
13072 // one channel (i.e. red) only
13073 for (int y = 1; y < h - 1; y++) {
13074 for (int x = 1; x < w - 1; x++) {
13075 val =
13076 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13077 val = (int)(val * val_factor);
13078 index = (y * w) + x;
13079 pmap[index] = val;
13080 }
13081 }
13082
13083 emboss_data *pret = new emboss_data;
13084 pret->pmap = pmap;
13085 pret->width = w;
13086 pret->height = h;
13087
13088 return pret;
13089}
13090
13091void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13092 Track *active_track = NULL;
13093 for (Track *pTrackDraw : g_TrackList) {
13094 if (g_pActiveTrack == pTrackDraw) {
13095 active_track = pTrackDraw;
13096 continue;
13097 }
13098
13099 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13100 }
13101
13102 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13103}
13104
13105void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13106 Track *active_track = NULL;
13107 for (Track *pTrackDraw : g_TrackList) {
13108 if (g_pActiveTrack == pTrackDraw) {
13109 active_track = pTrackDraw;
13110 break;
13111 }
13112 }
13113 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13114}
13115
13116void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13117 Route *active_route = NULL;
13118 for (Route *pRouteDraw : *pRouteList) {
13119 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13120 active_route = pRouteDraw;
13121 continue;
13122 }
13123
13124 // if(m_canvasIndex == 1)
13125 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13126 }
13127
13128 // Draw any active or selected route (or track) last, so that is is always on
13129 // top
13130 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13131}
13132
13133void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13134 Route *active_route = NULL;
13135
13136 for (Route *pRouteDraw : *pRouteList) {
13137 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13138 active_route = pRouteDraw;
13139 break;
13140 }
13141 }
13142 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13143}
13144
13145void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13146 if (!pWayPointMan) return;
13147
13148 auto node = pWayPointMan->GetWaypointList()->begin();
13149
13150 while (node != pWayPointMan->GetWaypointList()->end()) {
13151 RoutePoint *pWP = *node;
13152 if (pWP) {
13153 if (pWP->m_bIsInRoute) {
13154 ++node;
13155 continue;
13156 }
13157
13158 /* technically incorrect... waypoint has bounding box */
13159 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13160 RoutePointGui(*pWP).Draw(dc, this, NULL);
13161 else {
13162 // Are Range Rings enabled?
13163 if (pWP->GetShowWaypointRangeRings() &&
13164 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13165 double factor = 1.00;
13166 if (pWP->GetWaypointRangeRingsStepUnits() ==
13167 1) // convert kilometers to NMi
13168 factor = 1 / 1.852;
13169
13170 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13171 pWP->GetWaypointRangeRingsStep() / 60.;
13172 radius *= 2; // Fudge factor
13173
13174 LLBBox radar_box;
13175 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13176 pWP->m_lat + radius, pWP->m_lon + radius);
13177 if (!BltBBox.IntersectOut(radar_box)) {
13178 RoutePointGui(*pWP).Draw(dc, this, NULL);
13179 }
13180 }
13181 }
13182 }
13183
13184 ++node;
13185 }
13186}
13187
13188void ChartCanvas::DrawBlinkObjects() {
13189 // All RoutePoints
13190 wxRect update_rect;
13191
13192 if (!pWayPointMan) return;
13193
13194 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13195 if (pWP) {
13196 if (pWP->m_bBlink) {
13197 update_rect.Union(pWP->CurrentRect_in_DC);
13198 }
13199 }
13200 }
13201 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13202}
13203
13204void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13205 // draw anchor watch rings, if activated
13206
13208 wxPoint r1, r2;
13209 wxPoint lAnchorPoint1, lAnchorPoint2;
13210 double lpp1 = 0.0;
13211 double lpp2 = 0.0;
13212 if (pAnchorWatchPoint1) {
13213 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13215 &lAnchorPoint1);
13216 }
13217 if (pAnchorWatchPoint2) {
13218 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13220 &lAnchorPoint2);
13221 }
13222
13223 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13224 wxPen ppPenr(GetGlobalColor("URED"), 2);
13225
13226 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13227 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13228 dc.SetBrush(*ppBrush);
13229
13230 if (lpp1 > 0) {
13231 dc.SetPen(ppPeng);
13232 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13233 }
13234
13235 if (lpp2 > 0) {
13236 dc.SetPen(ppPeng);
13237 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13238 }
13239
13240 if (lpp1 < 0) {
13241 dc.SetPen(ppPenr);
13242 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13243 }
13244
13245 if (lpp2 < 0) {
13246 dc.SetPen(ppPenr);
13247 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13248 }
13249 }
13250}
13251
13252double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13253 double lpp = 0.;
13254 wxPoint r1;
13255 wxPoint lAnchorPoint;
13256 double d1 = 0.0;
13257 double dabs;
13258 double tlat1, tlon1;
13259
13260 if (pAnchorWatchPoint) {
13261 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13262 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13263 dabs = fabs(d1 / 1852.);
13264 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13265 &tlat1, &tlon1);
13266 GetCanvasPointPix(tlat1, tlon1, &r1);
13267 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13268 &lAnchorPoint);
13269 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13270 pow((double)(lAnchorPoint.y - r1.y), 2));
13271
13272 // This is an entry watch
13273 if (d1 < 0) lpp = -lpp;
13274 }
13275 return lpp;
13276}
13277
13278//------------------------------------------------------------------------------------------
13279// Tides Support
13280//------------------------------------------------------------------------------------------
13281void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13282 if (!ptcmgr) return;
13283
13284 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13285
13286 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13287 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13288 double lon = pIDX->IDX_lon;
13289 double lat = pIDX->IDX_lat;
13290
13291 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13292 if ((type == 't') || (type == 'T')) {
13293 if (BBox.Contains(lat, lon)) {
13294 // Manage the point selection list
13295 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13296 }
13297 }
13298 }
13299}
13300
13301void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13302 if (!ptcmgr) return;
13303
13304 wxDateTime this_now = gTimeSource;
13305 bool cur_time = !gTimeSource.IsValid();
13306 if (cur_time) this_now = wxDateTime::Now();
13307 time_t t_this_now = this_now.GetTicks();
13308
13309 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13310 wxPENSTYLE_SOLID);
13311 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13312 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13313 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13314 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13315
13316 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13317 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13318 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13319 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13320 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13321 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13322
13323 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13324 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13325 int font_size = wxMax(10, dFont->GetPointSize());
13326 font_size /= g_Platform->GetDisplayDIPMult(this);
13327 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13328 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13329 false, dFont->GetFaceName());
13330
13331 dc.SetPen(*pblack_pen);
13332 dc.SetBrush(*pgreen_brush);
13333
13334 wxBitmap bm;
13335 switch (m_cs) {
13336 case GLOBAL_COLOR_SCHEME_DAY:
13337 bm = m_bmTideDay;
13338 break;
13339 case GLOBAL_COLOR_SCHEME_DUSK:
13340 bm = m_bmTideDusk;
13341 break;
13342 case GLOBAL_COLOR_SCHEME_NIGHT:
13343 bm = m_bmTideNight;
13344 break;
13345 default:
13346 bm = m_bmTideDay;
13347 break;
13348 }
13349
13350 int bmw = bm.GetWidth();
13351 int bmh = bm.GetHeight();
13352
13353 float scale_factor = 1.0;
13354
13355 // Set the onscreen size of the symbol
13356 // Compensate for various display resolutions
13357 float icon_pixelRefDim = 45;
13358
13359 // Tidal report graphic is scaled by the text size of the label in use
13360 wxScreenDC sdc;
13361 int height;
13362 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13363 height *= g_Platform->GetDisplayDIPMult(this);
13364 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13365
13366 scale_factor *= pix_factor;
13367
13368 float user_scale_factor = g_ChartScaleFactorExp;
13369 if (g_ChartScaleFactorExp > 1.0)
13370 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13371 1.2; // soften the scale factor a bit
13372
13373 scale_factor *= user_scale_factor;
13374 scale_factor *= GetContentScaleFactor();
13375
13376 {
13377 double marge = 0.05;
13378 std::vector<LLBBox> drawn_boxes;
13379 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13380 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13381
13382 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13383 if ((type == 't') || (type == 'T')) // only Tides
13384 {
13385 double lon = pIDX->IDX_lon;
13386 double lat = pIDX->IDX_lat;
13387
13388 if (BBox.ContainsMarge(lat, lon, marge)) {
13389 // Avoid drawing detailed graphic for duplicate tide stations
13390 if (GetVP().chart_scale < 500000) {
13391 bool bdrawn = false;
13392 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13393 if (drawn_boxes[i].Contains(lat, lon)) {
13394 bdrawn = true;
13395 break;
13396 }
13397 }
13398 if (bdrawn) continue; // the station loop
13399
13400 LLBBox this_box;
13401 this_box.Set(lat, lon, lat, lon);
13402 this_box.EnLarge(.005);
13403 drawn_boxes.push_back(this_box);
13404 }
13405
13406 wxPoint r;
13407 GetCanvasPointPix(lat, lon, &r);
13408 // draw standard icons
13409 if (GetVP().chart_scale > 500000) {
13410 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13411 }
13412 // draw "extended" icons
13413 else {
13414 dc.SetFont(*plabelFont);
13415 {
13416 {
13417 float val, nowlev;
13418 float ltleve = 0.;
13419 float htleve = 0.;
13420 time_t tctime;
13421 time_t lttime = 0;
13422 time_t httime = 0;
13423 bool wt;
13424 // define if flood or ebb in the last ten minutes and verify if
13425 // data are useable
13426 if (ptcmgr->GetTideFlowSens(
13427 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13428 pIDX->IDX_rec_num, nowlev, val, wt)) {
13429 // search forward the first HW or LW near "now" ( starting at
13430 // "now" - ten minutes )
13431 ptcmgr->GetHightOrLowTide(
13432 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13433 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13434 wt, pIDX->IDX_rec_num, val, tctime);
13435 if (wt) {
13436 httime = tctime;
13437 htleve = val;
13438 } else {
13439 lttime = tctime;
13440 ltleve = val;
13441 }
13442 wt = !wt;
13443
13444 // then search opposite tide near "now"
13445 if (tctime > t_this_now) // search backward
13446 ptcmgr->GetHightOrLowTide(
13447 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13448 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13449 pIDX->IDX_rec_num, val, tctime);
13450 else
13451 // or search forward
13452 ptcmgr->GetHightOrLowTide(
13453 t_this_now, FORWARD_TEN_MINUTES_STEP,
13454 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13455 val, tctime);
13456 if (wt) {
13457 httime = tctime;
13458 htleve = val;
13459 } else {
13460 lttime = tctime;
13461 ltleve = val;
13462 }
13463
13464 // draw the tide rectangle:
13465
13466 // tide icon rectangle has default pre-scaled width = 12 ,
13467 // height = 45
13468 int width = (int)(12 * scale_factor + 0.5);
13469 int height = (int)(45 * scale_factor + 0.5);
13470 int linew = wxMax(1, (int)(scale_factor));
13471 int xDraw = r.x - (width / 2);
13472 int yDraw = r.y - (height / 2);
13473
13474 // process tide state ( %height and flow sens )
13475 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13476 int hs = (httime > lttime) ? -4 : 4;
13477 hs *= (int)(scale_factor + 0.5);
13478 if (ts > 0.995 || ts < 0.005) hs = 0;
13479 int ht_y = (int)(height * ts);
13480
13481 // draw yellow tide rectangle outlined in black
13482 pblack_pen->SetWidth(linew);
13483 dc.SetPen(*pblack_pen);
13484 dc.SetBrush(*pyelo_brush);
13485 dc.DrawRectangle(xDraw, yDraw, width, height);
13486
13487 // draw blue rectangle as water height, smaller in width than
13488 // yellow rectangle
13489 dc.SetPen(*pblue_pen);
13490 dc.SetBrush(*pblue_brush);
13491 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13492 (width - (4 * linew)), height - ht_y);
13493
13494 // draw sens arrows (ensure they are not "under-drawn" by top
13495 // line of blue rectangle )
13496 int hl;
13497 wxPoint arrow[3];
13498 arrow[0].x = xDraw + 2 * linew;
13499 arrow[1].x = xDraw + width / 2;
13500 arrow[2].x = xDraw + width - 2 * linew;
13501 pyelo_pen->SetWidth(linew);
13502 pblue_pen->SetWidth(linew);
13503 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13504 {
13505 hl = (int)(height * 0.25) + yDraw;
13506 arrow[0].y = hl;
13507 arrow[1].y = hl + hs;
13508 arrow[2].y = hl;
13509 if (ts < 0.15)
13510 dc.SetPen(*pyelo_pen);
13511 else
13512 dc.SetPen(*pblue_pen);
13513 dc.DrawLines(3, arrow);
13514 }
13515 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13516 {
13517 hl = (int)(height * 0.5) + yDraw;
13518 arrow[0].y = hl;
13519 arrow[1].y = hl + hs;
13520 arrow[2].y = hl;
13521 if (ts < 0.40)
13522 dc.SetPen(*pyelo_pen);
13523 else
13524 dc.SetPen(*pblue_pen);
13525 dc.DrawLines(3, arrow);
13526 }
13527 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13528 {
13529 hl = (int)(height * 0.75) + yDraw;
13530 arrow[0].y = hl;
13531 arrow[1].y = hl + hs;
13532 arrow[2].y = hl;
13533 if (ts < 0.65)
13534 dc.SetPen(*pyelo_pen);
13535 else
13536 dc.SetPen(*pblue_pen);
13537 dc.DrawLines(3, arrow);
13538 }
13539 // draw tide level text
13540 wxString s;
13541 s.Printf("%3.1f", nowlev);
13542 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13543 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13544 int wx1;
13545 dc.GetTextExtent(s, &wx1, NULL);
13546 wx1 *= g_Platform->GetDisplayDIPMult(this);
13547 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13548 }
13549 }
13550 }
13551 }
13552 }
13553 }
13554 }
13555 }
13556}
13557
13558//------------------------------------------------------------------------------------------
13559// Currents Support
13560//------------------------------------------------------------------------------------------
13561
13562void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13563 if (!ptcmgr) return;
13564
13565 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13566
13567 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13568 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13569 double lon = pIDX->IDX_lon;
13570 double lat = pIDX->IDX_lat;
13571
13572 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13573 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13574 if ((BBox.Contains(lat, lon))) {
13575 // Manage the point selection list
13576 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13577 }
13578 }
13579 }
13580}
13581
13582void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13583 if (!ptcmgr) return;
13584
13585 float tcvalue, dir;
13586 bool bnew_val;
13587 char sbuf[20];
13588 wxFont *pTCFont;
13589 double lon_last = 0.;
13590 double lat_last = 0.;
13591 // arrow size for Raz Blanchard : 12 knots north
13592 double marge = 0.2;
13593 bool cur_time = !gTimeSource.IsValid();
13594
13595 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13596 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13597
13598 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13599 wxPENSTYLE_SOLID);
13600 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13601 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13602 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13603 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13604 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13605 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13606 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13607 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13608
13609 double skew_angle = GetVPRotation();
13610
13611 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13612 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13613 int font_size = wxMax(10, dFont->GetPointSize());
13614 font_size /= g_Platform->GetDisplayDIPMult(this);
13615 pTCFont = FontMgr::Get().FindOrCreateFont(
13616 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13617 false, dFont->GetFaceName());
13618
13619 float scale_factor = 1.0;
13620
13621 // Set the onscreen size of the symbol
13622 // Current report graphic is scaled by the text size of the label in use
13623 wxScreenDC sdc;
13624 int height;
13625 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13626 height *= g_Platform->GetDisplayDIPMult(this);
13627 float nominal_icon_size_pixels = 15;
13628 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13629
13630 scale_factor *= pix_factor;
13631
13632 float user_scale_factor = g_ChartScaleFactorExp;
13633 if (g_ChartScaleFactorExp > 1.0)
13634 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13635 1.2; // soften the scale factor a bit
13636
13637 scale_factor *= user_scale_factor;
13638
13639 scale_factor *= GetContentScaleFactor();
13640
13641 {
13642 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13643 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13644 double lon = pIDX->IDX_lon;
13645 double lat = pIDX->IDX_lat;
13646
13647 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13648 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13649 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13650 wxPoint r;
13651 GetCanvasPointPix(lat, lon, &r);
13652
13653 wxPoint d[4]; // points of a diamond at the current station location
13654 int dd = (int)(5.0 * scale_factor + 0.5);
13655 d[0].x = r.x;
13656 d[0].y = r.y + dd;
13657 d[1].x = r.x + dd;
13658 d[1].y = r.y;
13659 d[2].x = r.x;
13660 d[2].y = r.y - dd;
13661 d[3].x = r.x - dd;
13662 d[3].y = r.y;
13663
13664 if (1) {
13665 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13666 dc.SetPen(*pblack_pen);
13667 dc.SetBrush(*porange_brush);
13668 dc.DrawPolygon(4, d);
13669
13670 if (type == 'C') {
13671 dc.SetBrush(*pblack_brush);
13672 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13673 }
13674
13675 if (GetVP().chart_scale < 1000000) {
13676 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13677 continue;
13678 } else
13679 continue;
13680
13681 if (1 /*type == 'c'*/) {
13682 {
13683 // Get the display pixel location of the current station
13684 int pixxc, pixyc;
13685 pixxc = r.x;
13686 pixyc = r.y;
13687
13688 // Adjust drawing size using logarithmic scale. tcvalue is
13689 // current in knots
13690 double a1 = fabs(tcvalue) * 10.;
13691 // Current values <= 0.1 knot will have no arrow
13692 a1 = wxMax(1.0, a1);
13693 double a2 = log10(a1);
13694
13695 float cscale = scale_factor * a2 * 0.3;
13696
13697 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13698 dc.SetPen(*porange_pen);
13699 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13700 cscale);
13701 // Draw text, if enabled
13702
13703 if (bDrawCurrentValues) {
13704 dc.SetFont(*pTCFont);
13705 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13706 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13707 }
13708 }
13709 } // scale
13710 }
13711 /* This is useful for debugging the TC database
13712 else
13713 {
13714 dc.SetPen ( *porange_pen );
13715 dc.SetBrush ( *pgray_brush );
13716 dc.DrawPolygon ( 4, d );
13717 }
13718 */
13719 }
13720 lon_last = lon;
13721 lat_last = lat;
13722 }
13723 }
13724 }
13725}
13726
13727void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13728 ShowSingleTideDialog(x, y, pvIDX);
13729}
13730
13731void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13732 if (!pvIDX) return; // Validate input
13733
13734 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13735
13736 // Check if a tide dialog is already open and visible
13737 if (pCwin && pCwin->IsShown()) {
13738 // Same tide station: bring existing dialog to front (preserves user
13739 // context)
13740 if (pCwin->GetCurrentIDX() == pNewIDX) {
13741 pCwin->Raise();
13742 pCwin->SetFocus();
13743
13744 // Provide subtle visual feedback that dialog is already open
13745 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13746 return;
13747 }
13748
13749 // Different tide station: close current dialog before opening new one
13750 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13751 }
13752
13753 if (pCwin) {
13754 // This shouldn't happen but ensures clean state
13755 pCwin->Destroy();
13756 pCwin = NULL;
13757 }
13758
13759 // Create and display new tide dialog
13760 pCwin = new TCWin(this, x, y, pvIDX);
13761
13762 // Ensure the dialog is properly shown and focused
13763 if (pCwin) {
13764 pCwin->Show();
13765 pCwin->Raise();
13766 pCwin->SetFocus();
13767 }
13768}
13769
13770bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13771
13773 if (pCwin) {
13774 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13775 }
13776}
13777
13778#define NUM_CURRENT_ARROW_POINTS 9
13779static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13780 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13781 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13782 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13783
13784void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13785 double scale) {
13786 if (scale > 1e-2) {
13787 float sin_rot = sin(rot_angle * PI / 180.);
13788 float cos_rot = cos(rot_angle * PI / 180.);
13789
13790 // Move to the first point
13791
13792 float xt = CurrentArrowArray[0].x;
13793 float yt = CurrentArrowArray[0].y;
13794
13795 float xp = (xt * cos_rot) - (yt * sin_rot);
13796 float yp = (xt * sin_rot) + (yt * cos_rot);
13797 int x1 = (int)(xp * scale);
13798 int y1 = (int)(yp * scale);
13799
13800 // Walk thru the point list
13801 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13802 xt = CurrentArrowArray[ip].x;
13803 yt = CurrentArrowArray[ip].y;
13804
13805 float xp = (xt * cos_rot) - (yt * sin_rot);
13806 float yp = (xt * sin_rot) + (yt * cos_rot);
13807 int x2 = (int)(xp * scale);
13808 int y2 = (int)(yp * scale);
13809
13810 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13811
13812 x1 = x2;
13813 y1 = y2;
13814 }
13815 }
13816}
13817
13818wxString ChartCanvas::FindValidUploadPort() {
13819 wxString port;
13820 // Try to use the saved persistent upload port first
13821 if (!g_uploadConnection.IsEmpty() &&
13822 g_uploadConnection.StartsWith("Serial")) {
13823 port = g_uploadConnection;
13824 }
13825
13826 else {
13827 // If there is no persistent upload port recorded (yet)
13828 // then use the first available serial connection which has output defined.
13829 for (auto *cp : TheConnectionParams()) {
13830 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13831 port << "Serial:" << cp->Port;
13832 }
13833 }
13834 return port;
13835}
13836
13837void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13838 if (!win) return;
13839
13840 if (NULL == g_pais_query_dialog_active) {
13841 int pos_x = g_ais_query_dialog_x;
13842 int pos_y = g_ais_query_dialog_y;
13843
13844 if (g_pais_query_dialog_active) {
13845 g_pais_query_dialog_active->Destroy();
13846 g_pais_query_dialog_active = new AISTargetQueryDialog();
13847 } else {
13848 g_pais_query_dialog_active = new AISTargetQueryDialog();
13849 }
13850
13851 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13852 wxPoint(pos_x, pos_y));
13853
13854 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13855 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13856 g_pais_query_dialog_active->SetMMSI(mmsi);
13857 g_pais_query_dialog_active->UpdateText();
13858 wxSize sz = g_pais_query_dialog_active->GetSize();
13859
13860 bool b_reset_pos = false;
13861#ifdef __WXMSW__
13862 // Support MultiMonitor setups which an allow negative window positions.
13863 // If the requested window title bar does not intersect any installed
13864 // monitor, then default to simple primary monitor positioning.
13865 RECT frame_title_rect;
13866 frame_title_rect.left = pos_x;
13867 frame_title_rect.top = pos_y;
13868 frame_title_rect.right = pos_x + sz.x;
13869 frame_title_rect.bottom = pos_y + 30;
13870
13871 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13872 b_reset_pos = true;
13873#else
13874
13875 // Make sure drag bar (title bar) of window intersects wxClient Area of
13876 // screen, with a little slop...
13877 wxRect window_title_rect; // conservative estimate
13878 window_title_rect.x = pos_x;
13879 window_title_rect.y = pos_y;
13880 window_title_rect.width = sz.x;
13881 window_title_rect.height = 30;
13882
13883 wxRect ClientRect = wxGetClientDisplayRect();
13884 ClientRect.Deflate(
13885 60, 60); // Prevent the new window from being too close to the edge
13886 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13887
13888#endif
13889
13890 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13891
13892 } else {
13893 g_pais_query_dialog_active->SetMMSI(mmsi);
13894 g_pais_query_dialog_active->UpdateText();
13895 }
13896
13897 g_pais_query_dialog_active->Show();
13898}
13899
13900void ChartCanvas::ToggleCanvasQuiltMode() {
13901 bool cur_mode = GetQuiltMode();
13902
13903 if (!GetQuiltMode())
13904 SetQuiltMode(true);
13905 else if (GetQuiltMode()) {
13906 SetQuiltMode(false);
13907 g_sticky_chart = GetQuiltReferenceChartIndex();
13908 }
13909
13910 if (cur_mode != GetQuiltMode()) {
13911 SetupCanvasQuiltMode();
13912 DoCanvasUpdate();
13913 InvalidateGL();
13914 Refresh();
13915 }
13916 // TODO What to do about this?
13917 // g_bQuiltEnable = GetQuiltMode();
13918
13919 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13920 if (ps52plib) ps52plib->GenerateStateHash();
13921
13922 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13923 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13924}
13925
13926void ChartCanvas::DoCanvasStackDelta(int direction) {
13927 if (!GetQuiltMode()) {
13928 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13929 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13930 if ((current_stack_index + direction) < 0) return;
13931
13932 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13933 int new_dbIndex =
13934 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13935
13936 if (IsChartQuiltableRef(new_dbIndex)) {
13937 ToggleCanvasQuiltMode();
13938 SelectQuiltRefdbChart(new_dbIndex);
13939 m_bpersistent_quilt = false;
13940 }
13941 } else {
13942 SelectChartFromStack(current_stack_index + direction);
13943 }
13944 } else {
13945 std::vector<int> piano_chart_index_array =
13946 GetQuiltExtendedStackdbIndexArray();
13947 int refdb = GetQuiltRefChartdbIndex();
13948
13949 // Find the ref chart in the stack
13950 int current_index = -1;
13951 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13952 if (refdb == piano_chart_index_array[i]) {
13953 current_index = i;
13954 break;
13955 }
13956 }
13957 if (current_index == -1) return;
13958
13959 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13960 int target_family = ctet.GetChartFamily();
13961
13962 int new_index = -1;
13963 int check_index = current_index + direction;
13964 bool found = false;
13965 int check_dbIndex = -1;
13966 int new_dbIndex = -1;
13967
13968 // When quilted. switch within the same chart family
13969 while (!found &&
13970 (unsigned int)check_index < piano_chart_index_array.size() &&
13971 (check_index >= 0)) {
13972 check_dbIndex = piano_chart_index_array[check_index];
13973 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13974 if (target_family == cte.GetChartFamily()) {
13975 found = true;
13976 new_index = check_index;
13977 new_dbIndex = check_dbIndex;
13978 break;
13979 }
13980
13981 check_index += direction;
13982 }
13983
13984 if (!found) return;
13985
13986 if (!IsChartQuiltableRef(new_dbIndex)) {
13987 ToggleCanvasQuiltMode();
13988 SelectdbChart(new_dbIndex);
13989 m_bpersistent_quilt = true;
13990 } else {
13991 SelectQuiltRefChart(new_index);
13992 }
13993 }
13994
13995 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13996 // (checkmarks etc)
13997 SetQuiltChartHiLiteIndex(-1);
13998
13999 ReloadVP();
14000}
14001
14002//--------------------------------------------------------------------------------------------------------
14003//
14004// Toolbar support
14005//
14006//--------------------------------------------------------------------------------------------------------
14007
14008void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14009 // Handle the per-canvas toolbar clicks here
14010
14011 switch (event.GetId()) {
14012 case ID_ZOOMIN: {
14013 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14014 break;
14015 }
14016
14017 case ID_ZOOMOUT: {
14018 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14019 break;
14020 }
14021
14022 case ID_STKUP:
14023 DoCanvasStackDelta(1);
14024 DoCanvasUpdate();
14025 break;
14026
14027 case ID_STKDN:
14028 DoCanvasStackDelta(-1);
14029 DoCanvasUpdate();
14030 break;
14031
14032 case ID_FOLLOW: {
14033 TogglebFollow();
14034 break;
14035 }
14036
14037 case ID_CURRENT: {
14038 ShowCurrents(!GetbShowCurrent());
14039 ReloadVP();
14040 Refresh(false);
14041 break;
14042 }
14043
14044 case ID_TIDE: {
14045 ShowTides(!GetbShowTide());
14046 ReloadVP();
14047 Refresh(false);
14048 break;
14049 }
14050
14051 case ID_ROUTE: {
14052 if (0 == m_routeState) {
14053 StartRoute();
14054 } else {
14055 FinishRoute();
14056 }
14057
14058#ifdef __ANDROID__
14059 androidSetRouteAnnunciator(m_routeState == 1);
14060#endif
14061 break;
14062 }
14063
14064 case ID_AIS: {
14065 SetAISCanvasDisplayStyle(-1);
14066 break;
14067 }
14068
14069 default:
14070 break;
14071 }
14072
14073 // And then let gFrame handle the rest....
14074 event.Skip();
14075}
14076
14077void ChartCanvas::SetShowAIS(bool show) {
14078 m_bShowAIS = show;
14079 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14080 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14081}
14082
14083void ChartCanvas::SetAttenAIS(bool show) {
14084 m_bShowAISScaled = show;
14085 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14086 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14087}
14088
14089void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14090 // make some arrays to hold the dfferences between cycle steps
14091 // show all, scaled, hide all
14092 bool bShowAIS_Array[3] = {true, true, false};
14093 bool bShowScaled_Array[3] = {false, true, true};
14094 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14095 _("Attenuate less critical AIS targets"),
14096 _("Hide AIS Targets")};
14097 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14098 int ArraySize = 3;
14099 int AIS_Toolbar_Switch = 0;
14100 if (StyleIndx == -1) { // -1 means coming from toolbar button
14101 // find current state of switch
14102 for (int i = 1; i < ArraySize; i++) {
14103 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14104 (bShowScaled_Array[i] == m_bShowAISScaled))
14105 AIS_Toolbar_Switch = i;
14106 }
14107 AIS_Toolbar_Switch++; // we did click so continu with next item
14108 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14109 AIS_Toolbar_Switch++;
14110
14111 } else { // coming from menu bar.
14112 AIS_Toolbar_Switch = StyleIndx;
14113 }
14114 // make sure we are not above array
14115 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14116
14117 int AIS_Toolbar_Switch_Next =
14118 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14119 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14120 AIS_Toolbar_Switch_Next++;
14121 if (AIS_Toolbar_Switch_Next >= ArraySize)
14122 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14123
14124 // Set found values to global and member variables
14125 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14126 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14127}
14128
14129void ChartCanvas::TouchAISToolActive() {}
14130
14131void ChartCanvas::UpdateAISTBTool() {}
14132
14133//---------------------------------------------------------------------------------
14134//
14135// Compass/GPS status icon support
14136//
14137//---------------------------------------------------------------------------------
14138
14139void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14140 // Look for change in overlap or positions
14141 bool b_update = false;
14142 int cc1_edge_comp = 2;
14143 wxRect rect = m_Compass->GetRect();
14144 wxSize parent_size = GetSize();
14145
14146 parent_size *= m_displayScale;
14147
14148 // check to see if it would overlap if it was in its home position (upper
14149 // right)
14150 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14151 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14152 wxRect compass_rect(compass_pt, rect.GetSize());
14153
14154 m_Compass->Move(compass_pt);
14155
14156 if (m_Compass && m_Compass->IsShown())
14157 m_Compass->UpdateStatus(b_force_new | b_update);
14158
14159 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14160 scaler = wxMax(scaler, 1.0);
14161 wxPoint note_point = wxPoint(
14162 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14163 if (m_notification_button) {
14164 m_notification_button->Move(note_point);
14165 m_notification_button->UpdateStatus();
14166 }
14167
14168 if (b_force_new | b_update) Refresh();
14169}
14170
14171void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14172 ChartTypeEnum New_Type,
14173 ChartFamilyEnum New_Family) {
14174 if (!GetpCurrentStack()) return;
14175 if (!ChartData) return;
14176
14177 if (index < GetpCurrentStack()->nEntry) {
14178 // Open the new chart
14179 ChartBase *pTentative_Chart;
14180 pTentative_Chart = ChartData->OpenStackChartConditional(
14181 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14182
14183 if (pTentative_Chart) {
14184 if (m_singleChart) m_singleChart->Deactivate();
14185
14186 m_singleChart = pTentative_Chart;
14187 m_singleChart->Activate();
14188
14189 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14190 GetpCurrentStack(), m_singleChart->GetFullPath());
14191 }
14192
14193 // Setup the view
14194 double zLat, zLon;
14195 if (m_bFollow) {
14196 zLat = gLat;
14197 zLon = gLon;
14198 } else {
14199 zLat = m_vLat;
14200 zLon = m_vLon;
14201 }
14202
14203 double best_scale_ppm = GetBestVPScale(m_singleChart);
14204 double rotation = GetVPRotation();
14205 double oldskew = GetVPSkew();
14206 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14207
14208 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14209 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14210 if (fabs(newskew) > 0.0001) rotation = newskew;
14211 }
14212
14213 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14214
14215 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14216 }
14217
14218 // refresh Piano
14219 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14220 if (idx < 0) return;
14221
14222 std::vector<int> piano_active_chart_index_array;
14223 piano_active_chart_index_array.push_back(
14224 GetpCurrentStack()->GetCurrentEntrydbIndex());
14225 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14226}
14227
14228void ChartCanvas::SelectdbChart(int dbindex) {
14229 if (!GetpCurrentStack()) return;
14230 if (!ChartData) return;
14231
14232 if (dbindex >= 0) {
14233 // Open the new chart
14234 ChartBase *pTentative_Chart;
14235 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14236
14237 if (pTentative_Chart) {
14238 if (m_singleChart) m_singleChart->Deactivate();
14239
14240 m_singleChart = pTentative_Chart;
14241 m_singleChart->Activate();
14242
14243 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14244 GetpCurrentStack(), m_singleChart->GetFullPath());
14245 }
14246
14247 // Setup the view
14248 double zLat, zLon;
14249 if (m_bFollow) {
14250 zLat = gLat;
14251 zLon = gLon;
14252 } else {
14253 zLat = m_vLat;
14254 zLon = m_vLon;
14255 }
14256
14257 double best_scale_ppm = GetBestVPScale(m_singleChart);
14258
14259 if (m_singleChart)
14260 SetViewPoint(zLat, zLon, best_scale_ppm,
14261 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14262
14263 // SetChartUpdatePeriod( );
14264
14265 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14266 }
14267
14268 // TODO refresh_Piano();
14269}
14270
14271void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14272 double target_scale = GetVP().view_scale_ppm;
14273
14274 if (!GetQuiltMode()) {
14275 if (GetpCurrentStack()) {
14276 int stack_index = -1;
14277 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14278 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14279 if (check_dbIndex < 0) continue;
14280 const ChartTableEntry &cte =
14281 ChartData->GetChartTableEntry(check_dbIndex);
14282 if (type == cte.GetChartType()) {
14283 stack_index = i;
14284 break;
14285 } else if (family == cte.GetChartFamily()) {
14286 stack_index = i;
14287 break;
14288 }
14289 }
14290
14291 if (stack_index >= 0) {
14292 SelectChartFromStack(stack_index);
14293 }
14294 }
14295 } else {
14296 int sel_dbIndex = -1;
14297 std::vector<int> piano_chart_index_array =
14298 GetQuiltExtendedStackdbIndexArray();
14299 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14300 int check_dbIndex = piano_chart_index_array[i];
14301 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14302 if (type == cte.GetChartType()) {
14303 if (IsChartQuiltableRef(check_dbIndex)) {
14304 sel_dbIndex = check_dbIndex;
14305 break;
14306 }
14307 } else if (family == cte.GetChartFamily()) {
14308 if (IsChartQuiltableRef(check_dbIndex)) {
14309 sel_dbIndex = check_dbIndex;
14310 break;
14311 }
14312 }
14313 }
14314
14315 if (sel_dbIndex >= 0) {
14316 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14317 // Re-qualify the quilt reference chart selection
14318 AdjustQuiltRefChart();
14319 }
14320
14321 // Now reset the scale to the target...
14322 SetVPScale(target_scale);
14323 }
14324
14325 SetQuiltChartHiLiteIndex(-1);
14326
14327 ReloadVP();
14328}
14329
14330bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14331 return std::find(m_tile_yesshow_index_array.begin(),
14332 m_tile_yesshow_index_array.end(),
14333 index) != m_tile_yesshow_index_array.end();
14334}
14335
14336bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14337 return std::find(m_tile_noshow_index_array.begin(),
14338 m_tile_noshow_index_array.end(),
14339 index) != m_tile_noshow_index_array.end();
14340}
14341
14342void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14343 if (std::find(m_tile_noshow_index_array.begin(),
14344 m_tile_noshow_index_array.end(),
14345 index) == m_tile_noshow_index_array.end()) {
14346 m_tile_noshow_index_array.push_back(index);
14347 }
14348}
14349
14350//-------------------------------------------------------------------------------------------------------
14351//
14352// Piano support
14353//
14354//-------------------------------------------------------------------------------------------------------
14355
14356void ChartCanvas::HandlePianoClick(
14357 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14358 if (g_options && g_options->IsShown())
14359 return; // Piano might be invalid due to chartset updates.
14360 if (!m_pCurrentStack) return;
14361 if (!ChartData) return;
14362
14363 // stop movement or on slow computer we may get something like :
14364 // zoom out with the wheel (timer is set)
14365 // quickly click and display a chart, which may zoom in
14366 // but the delayed timer fires first and it zooms out again!
14367 StopMovement();
14368
14369 // When switching by piano key click, we may appoint the new target chart to
14370 // be any chart in the composite array.
14371 // As an improvement to UX, find the chart that is "closest" to the current
14372 // vp,
14373 // and select that chart. This will cause a jump to the centroid of that
14374 // chart
14375
14376 double distance = 25000; // RTW
14377 int closest_index = -1;
14378 for (int chart_index : selected_dbIndex_array) {
14379 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14380 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14381 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14382
14383 // measure distance as Manhattan style
14384 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14385 if (test_distance < distance) {
14386 distance = test_distance;
14387 closest_index = chart_index;
14388 }
14389 }
14390
14391 int selected_dbIndex = selected_dbIndex_array[0];
14392 if (closest_index >= 0) selected_dbIndex = closest_index;
14393
14394 if (!GetQuiltMode()) {
14395 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14396 if (IsChartQuiltableRef(selected_dbIndex)) {
14397 ToggleCanvasQuiltMode();
14398 SelectQuiltRefdbChart(selected_dbIndex);
14399 m_bpersistent_quilt = false;
14400 } else {
14401 SelectChartFromStack(selected_index);
14402 }
14403 } else {
14404 SelectChartFromStack(selected_index);
14405 g_sticky_chart = selected_dbIndex;
14406 }
14407
14408 if (m_singleChart)
14409 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14410 } else {
14411 // Handle MBTiles overlays first
14412 // Left click simply toggles the noshow array index entry
14413 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14414 bool bfound = false;
14415 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14416 if (m_tile_noshow_index_array[i] ==
14417 selected_dbIndex) { // chart is in the noshow list
14418 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14419 i); // erase it
14420 bfound = true;
14421 break;
14422 }
14423 }
14424 if (!bfound) {
14425 m_tile_noshow_index_array.push_back(selected_dbIndex);
14426 }
14427
14428 // If not already present, add this tileset to the "yes_show" array.
14429 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14430 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14431 }
14432
14433 else {
14434 if (IsChartQuiltableRef(selected_dbIndex)) {
14435 // if( ChartData ) ChartData->PurgeCache();
14436
14437 // If the chart is a vector chart, and of very large scale,
14438 // then we had better set the new scale directly to avoid excessive
14439 // underzoom on, eg, Inland ENCs
14440 bool set_scale = false;
14441 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14442 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14443 set_scale = true;
14444 }
14445 }
14446
14447 if (!set_scale) {
14448 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14449 } else {
14450 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14451
14452 // Adjust scale so that the selected chart is underzoomed/overzoomed
14453 // by a controlled amount
14454 ChartBase *pc =
14455 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14456 if (pc) {
14457 double proposed_scale_onscreen =
14459
14460 if (g_bPreserveScaleOnX) {
14461 proposed_scale_onscreen =
14462 wxMin(proposed_scale_onscreen,
14463 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14464 GetCanvasWidth()));
14465 } else {
14466 proposed_scale_onscreen =
14467 wxMin(proposed_scale_onscreen,
14468 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14469 GetCanvasWidth()));
14470
14471 proposed_scale_onscreen =
14472 wxMax(proposed_scale_onscreen,
14473 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14475 }
14476
14477 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14478 }
14479 }
14480 } else {
14481 ToggleCanvasQuiltMode();
14482 SelectdbChart(selected_dbIndex);
14483 m_bpersistent_quilt = true;
14484 }
14485 }
14486 }
14487
14488 SetQuiltChartHiLiteIndex(-1);
14489 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14490 // (checkmarks etc)
14491 HideChartInfoWindow();
14492 DoCanvasUpdate();
14493 ReloadVP(); // Pick up the new selections
14494}
14495
14496void ChartCanvas::HandlePianoRClick(
14497 int x, int y, int selected_index,
14498 const std::vector<int> &selected_dbIndex_array) {
14499 if (g_options && g_options->IsShown())
14500 return; // Piano might be invalid due to chartset updates.
14501 if (!GetpCurrentStack()) return;
14502
14503 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14504 UpdateCanvasControlBar();
14505
14506 SetQuiltChartHiLiteIndex(-1);
14507}
14508
14509void ChartCanvas::HandlePianoRollover(
14510 int selected_index, const std::vector<int> &selected_dbIndex_array,
14511 int n_charts, int scale) {
14512 if (g_options && g_options->IsShown())
14513 return; // Piano might be invalid due to chartset updates.
14514 if (!GetpCurrentStack()) return;
14515 if (!ChartData) return;
14516
14517 if (ChartData->IsBusy()) return;
14518
14519 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14520
14521 if (!GetQuiltMode()) {
14522 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14523 } else {
14524 // Select the correct vector
14525 std::vector<int> piano_chart_index_array;
14526 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14527 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14528 if ((GetpCurrentStack()->nEntry > 1) ||
14529 (piano_chart_index_array.size() >= 1)) {
14530 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14531
14532 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14533 ReloadVP(false); // no VP adjustment allowed
14534 } else if (GetpCurrentStack()->nEntry == 1) {
14535 const ChartTableEntry &cte =
14536 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14537 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14538 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14539 ReloadVP(false);
14540 } else if ((-1 == selected_index) &&
14541 (0 == selected_dbIndex_array.size())) {
14542 ShowChartInfoWindow(key_location.x, -1);
14543 }
14544 }
14545 } else {
14546 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14547
14548 if ((GetpCurrentStack()->nEntry > 1) ||
14549 (piano_chart_index_array.size() >= 1)) {
14550 if (n_charts > 1)
14551 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14552 selected_dbIndex_array);
14553 else if (n_charts == 1)
14554 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14555
14556 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14557 ReloadVP(false); // no VP adjustment allowed
14558 }
14559 }
14560 }
14561}
14562
14563void ChartCanvas::ClearPianoRollover() {
14564 ClearQuiltChartHiLiteIndexArray();
14565 ShowChartInfoWindow(0, -1);
14566 std::vector<int> vec;
14567 ShowCompositeInfoWindow(0, 0, 0, vec);
14568 ReloadVP(false);
14569}
14570
14571void ChartCanvas::UpdateCanvasControlBar() {
14572 if (m_pianoFrozen) return;
14573
14574 if (!GetpCurrentStack()) return;
14575 if (!ChartData) return;
14576 if (!g_bShowChartBar) return;
14577
14578 int sel_type = -1;
14579 int sel_family = -1;
14580
14581 std::vector<int> piano_chart_index_array;
14582 std::vector<int> empty_piano_chart_index_array;
14583
14584 wxString old_hash = m_Piano->GetStoredHash();
14585
14586 if (GetQuiltMode()) {
14587 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14588 GetQuiltFullScreendbIndexArray());
14589
14590 std::vector<int> piano_active_chart_index_array =
14591 GetQuiltCandidatedbIndexArray();
14592 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14593
14594 std::vector<int> piano_eclipsed_chart_index_array =
14595 GetQuiltEclipsedStackdbIndexArray();
14596 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14597
14598 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14599 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14600
14601 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14602 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14603 } else {
14604 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14605 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14606 // TODO refresh_Piano();
14607
14608 if (m_singleChart) {
14609 sel_type = m_singleChart->GetChartType();
14610 sel_family = m_singleChart->GetChartFamily();
14611 }
14612 }
14613
14614 // Set up the TMerc and Skew arrays
14615 std::vector<int> piano_skew_chart_index_array;
14616 std::vector<int> piano_tmerc_chart_index_array;
14617 std::vector<int> piano_poly_chart_index_array;
14618
14619 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14620 const ChartTableEntry &ctei =
14621 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14622 double skew_norm = ctei.GetChartSkew();
14623 if (skew_norm > 180.) skew_norm -= 360.;
14624
14625 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14626 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14627
14628 // Polyconic skewed charts should show as skewed
14629 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14630 if (fabs(skew_norm) > 1.)
14631 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14632 else
14633 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14634 } else if (fabs(skew_norm) > 1.)
14635 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14636 }
14637 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14638 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14639 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14640
14641 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14642 if (new_hash != old_hash) {
14643 m_Piano->FormatKeys();
14644 HideChartInfoWindow();
14645 m_Piano->ResetRollover();
14646 SetQuiltChartHiLiteIndex(-1);
14647 m_brepaint_piano = true;
14648 }
14649
14650 // Create a bitmask int that describes what Family/Type of charts are shown in
14651 // the bar, and notify the platform.
14652 int mask = 0;
14653 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14654 const ChartTableEntry &ctei =
14655 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14656 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14657 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14658 if (e == CHART_FAMILY_RASTER) mask |= 1;
14659 if (e == CHART_FAMILY_VECTOR) {
14660 if (t == CHART_TYPE_CM93COMP)
14661 mask |= 4;
14662 else
14663 mask |= 2;
14664 }
14665 }
14666
14667 wxString s_indicated;
14668 if (sel_type == CHART_TYPE_CM93COMP)
14669 s_indicated = "cm93";
14670 else {
14671 if (sel_family == CHART_FAMILY_RASTER)
14672 s_indicated = "raster";
14673 else if (sel_family == CHART_FAMILY_VECTOR)
14674 s_indicated = "vector";
14675 }
14676
14677 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14678}
14679
14680void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14681
14682void ChartCanvas::PianoPopupMenu(
14683 int x, int y, int selected_index,
14684 const std::vector<int> &selected_dbIndex_array) {
14685 if (!GetpCurrentStack()) return;
14686
14687 // No context menu if quilting is disabled
14688 if (!GetQuiltMode()) return;
14689
14690 m_piano_ctx_menu = new wxMenu();
14691
14692 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14693 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14694 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14695 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14696 } else {
14697 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14698 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14699 // wxEVT_COMMAND_MENU_SELECTED,
14700 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14701
14702 menu_selected_dbIndex = selected_dbIndex_array[0];
14703 menu_selected_index = selected_index;
14704
14705 // Search the no-show array
14706 bool b_is_in_noshow = false;
14707 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14708 if (m_quilt_noshow_index_array[i] ==
14709 menu_selected_dbIndex) // chart is in the noshow list
14710 {
14711 b_is_in_noshow = true;
14712 break;
14713 }
14714 }
14715
14716 if (b_is_in_noshow) {
14717 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14718 _("Show This Chart"));
14719 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14720 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14721 } else if (GetpCurrentStack()->nEntry > 1) {
14722 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14723 _("Hide This Chart"));
14724 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14725 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14726 }
14727 }
14728
14729 wxPoint pos = wxPoint(x, y - 30);
14730
14731 // Invoke the drop-down menu
14732 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14733 PopupMenu(m_piano_ctx_menu, pos);
14734
14735 delete m_piano_ctx_menu;
14736 m_piano_ctx_menu = NULL;
14737
14738 HideChartInfoWindow();
14739 m_Piano->ResetRollover();
14740
14741 SetQuiltChartHiLiteIndex(-1);
14742 ClearQuiltChartHiLiteIndexArray();
14743
14744 ReloadVP();
14745}
14746
14747void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14748 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14749 if (m_quilt_noshow_index_array[i] ==
14750 menu_selected_dbIndex) // chart is in the noshow list
14751 {
14752 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14753 break;
14754 }
14755 }
14756}
14757
14758void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14759 if (!GetpCurrentStack()) return;
14760 if (!ChartData) return;
14761
14762 RemoveChartFromQuilt(menu_selected_dbIndex);
14763
14764 // It could happen that the chart being disabled is the reference
14765 // chart....
14766 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14767 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14768
14769 int i = menu_selected_index + 1; // select next smaller scale chart
14770 bool b_success = false;
14771 while (i < GetpCurrentStack()->nEntry - 1) {
14772 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14773 if (type == ChartData->GetDBChartType(dbIndex)) {
14774 SelectQuiltRefChart(i);
14775 b_success = true;
14776 break;
14777 }
14778 i++;
14779 }
14780
14781 // If that did not work, try to select the next larger scale compatible
14782 // chart
14783 if (!b_success) {
14784 i = menu_selected_index - 1;
14785 while (i > 0) {
14786 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14787 if (type == ChartData->GetDBChartType(dbIndex)) {
14788 SelectQuiltRefChart(i);
14789 b_success = true;
14790 break;
14791 }
14792 i--;
14793 }
14794 }
14795 }
14796}
14797
14798void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14799 // Remove the item from the list (if it appears) to avoid multiple addition
14800 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14801 if (m_quilt_noshow_index_array[i] ==
14802 dbIndex) // chart is already in the noshow list
14803 {
14804 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14805 break;
14806 }
14807 }
14808
14809 m_quilt_noshow_index_array.push_back(dbIndex);
14810}
14811
14812bool ChartCanvas::UpdateS52State() {
14813 bool retval = false;
14814
14815 if (ps52plib) {
14816 ps52plib->SetShowS57Text(m_encShowText);
14817 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14818 ps52plib->m_bShowSoundg = m_encShowDepth;
14819 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14820 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14821
14822 // Lights
14823 if (!m_encShowLights) // On, going off
14824 ps52plib->AddObjNoshow("LIGHTS");
14825 else // Off, going on
14826 ps52plib->RemoveObjNoshow("LIGHTS");
14827 ps52plib->SetLightsOff(!m_encShowLights);
14828 ps52plib->m_bExtendLightSectors = true;
14829
14830 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14831 ps52plib->SetAnchorOn(m_encShowAnchor);
14832 ps52plib->SetQualityOfData(m_encShowDataQual);
14833 }
14834
14835 return retval;
14836}
14837
14838void ChartCanvas::SetShowENCDataQual(bool show) {
14839 m_encShowDataQual = show;
14840 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14841 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14842
14843 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14844}
14845
14846void ChartCanvas::SetShowENCText(bool show) {
14847 m_encShowText = show;
14848 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14849 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14850
14851 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14852}
14853
14854void ChartCanvas::SetENCDisplayCategory(int category) {
14855 m_encDisplayCategory = category;
14856 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14857}
14858
14859void ChartCanvas::SetShowENCDepth(bool show) {
14860 m_encShowDepth = 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::SetShowENCLightDesc(bool show) {
14868 m_encShowLightDesc = 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::SetShowENCBuoyLabels(bool show) {
14876 m_encShowBuoyLabels = show;
14877 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14878}
14879
14880void ChartCanvas::SetShowENCLights(bool show) {
14881 m_encShowLights = 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::SetShowENCAnchor(bool show) {
14889 m_encShowAnchor = show;
14890 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14891 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14892
14893 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14894}
14895
14896wxRect ChartCanvas::GetMUIBarRect() {
14897 wxRect rv;
14898 if (m_muiBar) {
14899 rv = m_muiBar->GetRect();
14900 }
14901
14902 return rv;
14903}
14904
14905void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14906 if (!GetAlertString().IsEmpty()) {
14907 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14908 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14909
14910 dc.SetFont(*pfont);
14911 dc.SetPen(*wxTRANSPARENT_PEN);
14912
14913 dc.SetBrush(wxColour(243, 229, 47));
14914 int w, h;
14915 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14916 h += 2;
14917 // int yp = vp.pix_height - 20 - h;
14918
14919 wxRect sbr = GetScaleBarRect();
14920 int xp = sbr.x + sbr.width + 10;
14921 int yp = (sbr.y + sbr.height) - h;
14922
14923 int wdraw = w + 10;
14924 dc.DrawRectangle(xp, yp, wdraw, h);
14925 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14926 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14927 }
14928}
14929
14930//--------------------------------------------------------------------------------------------------------
14931// Screen Brightness Control Support Routines
14932//
14933//--------------------------------------------------------------------------------------------------------
14934
14935#ifdef __UNIX__
14936#define BRIGHT_XCALIB
14937#define __OPCPN_USEICC__
14938#endif
14939
14940#ifdef __OPCPN_USEICC__
14941int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14942 double co_green, double co_blue);
14943
14944wxString temp_file_name;
14945#endif
14946
14947#if 0
14948class ocpnCurtain: public wxDialog
14949{
14950 DECLARE_CLASS( ocpnCurtain )
14951 DECLARE_EVENT_TABLE()
14952
14953public:
14954 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14955 ~ocpnCurtain( );
14956 bool ProcessEvent(wxEvent& event);
14957
14958};
14959
14960IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14961
14962BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14963END_EVENT_TABLE()
14964
14965ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14966{
14967 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14968}
14969
14970ocpnCurtain::~ocpnCurtain()
14971{
14972}
14973
14974bool ocpnCurtain::ProcessEvent(wxEvent& event)
14975{
14976 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14977 return GetParent()->GetEventHandler()->ProcessEvent(event);
14978}
14979#endif
14980
14981#ifdef _WIN32
14982#include <windows.h>
14983
14984HMODULE hGDI32DLL;
14985typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14986typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14987SetDeviceGammaRamp_ptr_type
14988 g_pSetDeviceGammaRamp; // the API entry points in the dll
14989GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14990
14991WORD *g_pSavedGammaMap;
14992
14993#endif
14994
14995int InitScreenBrightness() {
14996#ifdef _WIN32
14997#ifdef ocpnUSE_GL
14998 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14999 HDC hDC;
15000 BOOL bbr;
15001
15002 if (NULL == hGDI32DLL) {
15003 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15004
15005 if (NULL != hGDI32DLL) {
15006 // Get the entry points of the required functions
15007 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15008 hGDI32DLL, "SetDeviceGammaRamp");
15009 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15010 hGDI32DLL, "GetDeviceGammaRamp");
15011
15012 // If the functions are not found, unload the DLL and return false
15013 if ((NULL == g_pSetDeviceGammaRamp) ||
15014 (NULL == g_pGetDeviceGammaRamp)) {
15015 FreeLibrary(hGDI32DLL);
15016 hGDI32DLL = NULL;
15017 return 0;
15018 }
15019 }
15020 }
15021
15022 // Interface is ready, so....
15023 // Get some storage
15024 if (!g_pSavedGammaMap) {
15025 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15026
15027 hDC = GetDC(NULL); // Get the full screen DC
15028 bbr = g_pGetDeviceGammaRamp(
15029 hDC, g_pSavedGammaMap); // Get the existing ramp table
15030 ReleaseDC(NULL, hDC); // Release the DC
15031 }
15032
15033 // On Windows hosts, try to adjust the registry to allow full range
15034 // setting of Gamma table This is an undocumented Windows hack.....
15035 wxRegKey *pRegKey = new wxRegKey(
15036 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15037 "NT\\CurrentVersion\\ICM");
15038 if (!pRegKey->Exists()) pRegKey->Create();
15039 pRegKey->SetValue("GdiIcmGammaRange", 256);
15040
15041 g_brightness_init = true;
15042 return 1;
15043 }
15044#endif
15045
15046 {
15047 if (NULL == g_pcurtain) {
15048 if (gFrame->CanSetTransparent()) {
15049 // Build the curtain window
15050 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
15051 wxPoint(0, 0), ::wxGetDisplaySize(),
15052 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15053 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15054
15055 // g_pcurtain = new ocpnCurtain(gFrame,
15056 // wxPoint(0,0),::wxGetDisplaySize(),
15057 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15058 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15059
15060 g_pcurtain->Hide();
15061
15062 HWND hWnd = GetHwndOf(g_pcurtain);
15063 SetWindowLong(hWnd, GWL_EXSTYLE,
15064 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15065 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15066 g_pcurtain->SetTransparent(0);
15067
15068 g_pcurtain->Maximize();
15069 g_pcurtain->Show();
15070
15071 // All of this is obtuse, but necessary for Windows...
15072 g_pcurtain->Enable();
15073 g_pcurtain->Disable();
15074
15075 gFrame->Disable();
15076 gFrame->Enable();
15077 // SetFocus();
15078 }
15079 }
15080 g_brightness_init = true;
15081
15082 return 1;
15083 }
15084#else
15085 // Look for "xcalib" application
15086 wxString cmd("xcalib -version");
15087
15088 wxArrayString output;
15089 long r = wxExecute(cmd, output);
15090 if (0 != r)
15091 wxLogMessage(
15092 " External application \"xcalib\" not found. Screen brightness "
15093 "not changed.");
15094
15095 g_brightness_init = true;
15096 return 0;
15097#endif
15098}
15099
15100int RestoreScreenBrightness() {
15101#ifdef _WIN32
15102
15103 if (g_pSavedGammaMap) {
15104 HDC hDC = GetDC(NULL); // Get the full screen DC
15105 g_pSetDeviceGammaRamp(hDC,
15106 g_pSavedGammaMap); // Restore the saved ramp table
15107 ReleaseDC(NULL, hDC); // Release the DC
15108
15109 free(g_pSavedGammaMap);
15110 g_pSavedGammaMap = NULL;
15111 }
15112
15113 if (g_pcurtain) {
15114 g_pcurtain->Close();
15115 g_pcurtain->Destroy();
15116 g_pcurtain = NULL;
15117 }
15118
15119 g_brightness_init = false;
15120 return 1;
15121
15122#endif
15123
15124#ifdef BRIGHT_XCALIB
15125 if (g_brightness_init) {
15126 wxString cmd;
15127 cmd = "xcalib -clear";
15128 wxExecute(cmd, wxEXEC_ASYNC);
15129 g_brightness_init = false;
15130 }
15131
15132 return 1;
15133#endif
15134
15135 return 0;
15136}
15137
15138// Set brightness. [0..100]
15139int SetScreenBrightness(int brightness) {
15140#ifdef _WIN32
15141
15142 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15143 // some (most modern?) versions of gdi32.dll Load the required library dll,
15144 // if not already in place
15145#ifdef ocpnUSE_GL
15146 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15147 if (g_pcurtain) {
15148 g_pcurtain->Close();
15149 g_pcurtain->Destroy();
15150 g_pcurtain = NULL;
15151 }
15152
15153 InitScreenBrightness();
15154
15155 if (NULL == hGDI32DLL) {
15156 // Unicode stuff.....
15157 wchar_t wdll_name[80];
15158 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15159 LPCWSTR cstr = wdll_name;
15160
15161 hGDI32DLL = LoadLibrary(cstr);
15162
15163 if (NULL != hGDI32DLL) {
15164 // Get the entry points of the required functions
15165 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15166 hGDI32DLL, "SetDeviceGammaRamp");
15167 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15168 hGDI32DLL, "GetDeviceGammaRamp");
15169
15170 // If the functions are not found, unload the DLL and return false
15171 if ((NULL == g_pSetDeviceGammaRamp) ||
15172 (NULL == g_pGetDeviceGammaRamp)) {
15173 FreeLibrary(hGDI32DLL);
15174 hGDI32DLL = NULL;
15175 return 0;
15176 }
15177 }
15178 }
15179
15180 HDC hDC = GetDC(NULL); // Get the full screen DC
15181
15182 /*
15183 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15184 if (cmcap != CM_GAMMA_RAMP)
15185 {
15186 wxLogMessage(" Video hardware does not support brightness control by
15187 gamma ramp adjustment."); return false;
15188 }
15189 */
15190
15191 int increment = brightness * 256 / 100;
15192
15193 // Build the Gamma Ramp table
15194 WORD GammaTable[3][256];
15195
15196 int table_val = 0;
15197 for (int i = 0; i < 256; i++) {
15198 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15199 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15200 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15201
15202 table_val += increment;
15203
15204 if (table_val > 65535) table_val = 65535;
15205 }
15206
15207 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15208 ReleaseDC(NULL, hDC); // Release the DC
15209
15210 return 1;
15211 }
15212#endif
15213
15214 {
15215 if (g_pSavedGammaMap) {
15216 HDC hDC = GetDC(NULL); // Get the full screen DC
15217 g_pSetDeviceGammaRamp(hDC,
15218 g_pSavedGammaMap); // Restore the saved ramp table
15219 ReleaseDC(NULL, hDC); // Release the DC
15220 }
15221
15222 if (brightness < 100) {
15223 if (NULL == g_pcurtain) InitScreenBrightness();
15224
15225 if (g_pcurtain) {
15226 int sbrite = wxMax(1, brightness);
15227 sbrite = wxMin(100, sbrite);
15228
15229 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15230 }
15231 } else {
15232 if (g_pcurtain) {
15233 g_pcurtain->Close();
15234 g_pcurtain->Destroy();
15235 g_pcurtain = NULL;
15236 }
15237 }
15238
15239 return 1;
15240 }
15241
15242#endif
15243
15244#ifdef BRIGHT_XCALIB
15245
15246 if (!g_brightness_init) {
15247 last_brightness = 100;
15248 g_brightness_init = true;
15249 temp_file_name = wxFileName::CreateTempFileName("");
15250 InitScreenBrightness();
15251 }
15252
15253#ifdef __OPCPN_USEICC__
15254 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15255 // desired, and then activate this temporary profile using xcalib <filename>
15256 if (!CreateSimpleICCProfileFile(
15257 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15258 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15259 wxString cmd("xcalib ");
15260 cmd += temp_file_name;
15261
15262 wxExecute(cmd, wxEXEC_ASYNC);
15263 }
15264
15265#else
15266 // Or, use "xcalib -co" to set overall contrast value
15267 // This is not as nice, since the -co parameter wants to be a fraction of
15268 // the current contrast, and values greater than 100 are not allowed. As a
15269 // result, increases of contrast must do a "-clear" step first, which
15270 // produces objectionable flashing.
15271 if (brightness > last_brightness) {
15272 wxString cmd;
15273 cmd = "xcalib -clear";
15274 wxExecute(cmd, wxEXEC_ASYNC);
15275
15276 ::wxMilliSleep(10);
15277
15278 int brite_adj = wxMax(1, brightness);
15279 cmd.Printf("xcalib -co %2d -a", brite_adj);
15280 wxExecute(cmd, wxEXEC_ASYNC);
15281 } else {
15282 int brite_adj = wxMax(1, brightness);
15283 int factor = (brite_adj * 100) / last_brightness;
15284 factor = wxMax(1, factor);
15285 wxString cmd;
15286 cmd.Printf("xcalib -co %2d -a", factor);
15287 wxExecute(cmd, wxEXEC_ASYNC);
15288 }
15289
15290#endif
15291
15292 last_brightness = brightness;
15293
15294#endif
15295
15296 return 0;
15297}
15298
15299#ifdef __OPCPN_USEICC__
15300
15301#define MLUT_TAG 0x6d4c5554L
15302#define VCGT_TAG 0x76636774L
15303
15304int GetIntEndian(unsigned char *s) {
15305 int ret;
15306 unsigned char *p;
15307 int i;
15308
15309 p = (unsigned char *)&ret;
15310
15311 if (1)
15312 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15313 else
15314 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15315
15316 return ret;
15317}
15318
15319unsigned short GetShortEndian(unsigned char *s) {
15320 unsigned short ret;
15321 unsigned char *p;
15322 int i;
15323
15324 p = (unsigned char *)&ret;
15325
15326 if (1)
15327 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15328 else
15329 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15330
15331 return ret;
15332}
15333
15334// Create a very simple Gamma correction file readable by xcalib
15335int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15336 double co_green, double co_blue) {
15337 FILE *fp;
15338
15339 if (file_name) {
15340 fp = fopen(file_name, "wb");
15341 if (!fp) return -1; /* file can not be created */
15342 } else
15343 return -1; /* filename char pointer not valid */
15344
15345 // Write header
15346 char header[128];
15347 for (int i = 0; i < 128; i++) header[i] = 0;
15348
15349 fwrite(header, 128, 1, fp);
15350
15351 // Num tags
15352 int numTags0 = 1;
15353 int numTags = GetIntEndian((unsigned char *)&numTags0);
15354 fwrite(&numTags, 1, 4, fp);
15355
15356 int tagName0 = VCGT_TAG;
15357 int tagName = GetIntEndian((unsigned char *)&tagName0);
15358 fwrite(&tagName, 1, 4, fp);
15359
15360 int tagOffset0 = 128 + 4 * sizeof(int);
15361 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15362 fwrite(&tagOffset, 1, 4, fp);
15363
15364 int tagSize0 = 1;
15365 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15366 fwrite(&tagSize, 1, 4, fp);
15367
15368 fwrite(&tagName, 1, 4, fp); // another copy of tag
15369
15370 fwrite(&tagName, 1, 4, fp); // dummy
15371
15372 // Table type
15373
15374 /* VideoCardGammaTable (The simplest type) */
15375 int gammatype0 = 0;
15376 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15377 fwrite(&gammatype, 1, 4, fp);
15378
15379 int numChannels0 = 3;
15380 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15381 fwrite(&numChannels, 1, 2, fp);
15382
15383 int numEntries0 = 256;
15384 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15385 fwrite(&numEntries, 1, 2, fp);
15386
15387 int entrySize0 = 1;
15388 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15389 fwrite(&entrySize, 1, 2, fp);
15390
15391 unsigned char ramp[256];
15392
15393 // Red ramp
15394 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15395 fwrite(ramp, 256, 1, fp);
15396
15397 // Green ramp
15398 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15399 fwrite(ramp, 256, 1, fp);
15400
15401 // Blue ramp
15402 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15403 fwrite(ramp, 256, 1, fp);
15404
15405 fclose(fp);
15406
15407 return 0;
15408}
15409#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:837
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:72
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:56
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1314
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:174
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1313
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1314
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1313
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.
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:49
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:125
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:157
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13772
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:4547
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11816
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4543
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3641
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13731
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:4493
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:782
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:485
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:516
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2342
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7978
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7787
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5075
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:473
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:873
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4624
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5355
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:766
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4568
bool IsTideDialogOpen() const
Definition chcanv.cpp:13770
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:4630
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13727
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4488
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5374
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10223
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:469
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
Main application frame.
Definition ocpn_frame.h:137
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:1779
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:44
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:72
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:58
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.
Definition routeman.cpp:998
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
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:77
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
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.
OpenCPN top window.
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.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
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.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:183
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:65
OpenCPN Toolbar.
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.