OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/ocpn_utils.h"
58#include "model/own_ship.h"
59#include "model/plugin_comm.h"
60#include "model/route.h"
61#include "model/routeman.h"
62#include "model/select.h"
63#include "model/select_item.h"
64#include "model/track.h"
65#include "model/ocpn_utils.h"
66
67#include "ais.h"
70#include "canvas_config.h"
71#include "canvas_menu.h"
72#include "canvas_options.h"
73#include "chartdb.h"
74#include "chartimg.h"
75#include "chcanv.h"
76#include "ch_info_win.h"
77#include "cm93.h" // for chart outline draw
78#include "compass.h"
79#include "concanv.h"
80#include "detail_slider.h"
81#include "displays.h"
82#include "hotkeys_dlg.h"
83#include "font_mgr.h"
84#include "gl_texture_descr.h"
85#include "go_to_position_dlg.h"
86#include "gshhs.h"
87#include "ienc_toolbar.h"
88#include "kml.h"
89#include "line_clip.h"
90#include "mark_info.h"
91#include "mbtiles.h"
92#include "mui_bar.h"
93#include "navutil.h"
94#include "ocpn_aui_manager.h"
95#include "ocpndc.h"
96#include "ocpn_pixel.h"
97#include "ocpn_region.h"
98#include "options.h"
99#include "piano.h"
100#include "pluginmanager.h"
101#include "quilt.h"
102#include "route_gui.h"
103#include "routemanagerdialog.h"
104#include "route_point_gui.h"
105#include "route_prop_dlg_impl.h"
106#include "s52plib.h"
107#include "s52utils.h"
108#include "s57_query_dlg.h"
109#include "s57chart.h" // for ArrayOfS57Obj
110#include "senc_manager.h"
111#include "shapefile_basemap.h"
112#include "styles.h"
113#include "tcmgr.h"
114#include "tc_win.h"
115#include "thumbwin.h"
116#include "tide_time.h"
117#include "timers.h"
118#include "toolbar.h"
119#include "top_frame.h"
120#include "track_gui.h"
121#include "track_prop_dlg.h"
122#include "undo.h"
123#include "user_colors.h"
124
125#include "s57_ocpn_utils.h"
126
127#ifdef __ANDROID__
128#include "androidUTIL.h"
129#endif
130
131#ifdef ocpnUSE_GL
132#include "gl_chart_canvas.h"
135#endif
136
137#ifdef __VISUALC__
138#include <wx/msw/msvcrt.h>
139#endif
140
141#ifndef __WXMSW__
142#include <signal.h>
143#include <setjmp.h>
144#endif
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173arrayofCanvasPtr g_canvasArray;
175static bool g_bSmoothRecenter = true;
176static bool bDrawCurrentValues;
186static int mouse_x;
196static int mouse_y;
197static bool mouse_leftisdown;
198static bool g_brouteCreating;
199static int r_gamma_mult;
200static int g_gamma_mult;
201static int b_gamma_mult;
202static int gamma_state;
203static bool g_brightness_init;
204static int last_brightness;
205static wxGLContext *g_pGLcontext; // shared common context
206
207// "Curtain" mode parameters
208static wxDialog *g_pcurtain;
209
210static wxString g_lastS52PLIBPluginMessage;
211
212#define MIN_BRIGHT 10
213#define MAX_BRIGHT 100
214
215//------------------------------------------------------------------------------
216// ChartCanvas Implementation
217//------------------------------------------------------------------------------
218BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
219EVT_PAINT(ChartCanvas::OnPaint)
220EVT_ACTIVATE(ChartCanvas::OnActivate)
221EVT_SIZE(ChartCanvas::OnSize)
222#ifndef HAVE_WX_GESTURE_EVENTS
223EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
224#endif
225EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
226EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
227EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
228EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
229EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
230EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
231EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
232EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
233EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
234EVT_KEY_UP(ChartCanvas::OnKeyUp)
235EVT_CHAR(ChartCanvas::OnKeyChar)
236EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
237EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
238EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
239EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
240EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
241EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
242EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
243EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
244EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
245EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
246
247END_EVENT_TABLE()
248
249// Define a constructor for my canvas
250ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
251 : AbstractChartCanvas(frame, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
252 m_nmea_log(nmea_log) {
253 parent_frame = frame; // save a pointer to parent
254 m_canvasIndex = canvasIndex;
255
256 pscratch_bm = NULL;
257
258 SetBackgroundColour(wxColour(0, 0, 0));
259 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
260 // color scheme change
261
262 m_groupIndex = 0;
263 m_bDrawingRoute = false;
264 m_bRouteEditing = false;
265 m_bMarkEditing = false;
266 m_bRoutePoinDragging = false;
267 m_bIsInRadius = false;
268 m_bMayToggleMenuBar = true;
269
270 m_bFollow = false;
271 m_bShowNavobjects = true;
272 m_bTCupdate = false;
273 m_bAppendingRoute = false; // was true in MSW, why??
274 pThumbDIBShow = NULL;
275 m_bShowCurrent = false;
276 m_bShowTide = false;
277 bShowingCurrent = false;
278 pCwin = NULL;
279 warp_flag = false;
280 m_bzooming = false;
281 m_b_paint_enable = true;
282 m_routeState = 0;
283
284 pss_overlay_bmp = NULL;
285 pss_overlay_mask = NULL;
286 m_bChartDragging = false;
287 m_bMeasure_Active = false;
288 m_bMeasure_DistCircle = false;
289 m_pMeasureRoute = NULL;
290 m_pTrackRolloverWin = NULL;
291 m_pRouteRolloverWin = NULL;
292 m_pAISRolloverWin = NULL;
293 m_bedge_pan = false;
294 m_disable_edge_pan = false;
295 m_dragoffsetSet = false;
296 m_bautofind = false;
297 m_bFirstAuto = true;
298 m_groupIndex = 0;
299 m_singleChart = NULL;
300 m_upMode = NORTH_UP_MODE;
301 m_bShowAIS = true;
302 m_bShowAISScaled = false;
303 m_timed_move_vp_active = false;
304 m_inPinch = false;
305 m_disable_adjust_on_zoom = false;
306
307 m_vLat = 0.;
308 m_vLon = 0.;
309
310 m_pCIWin = NULL;
311
312 m_pSelectedRoute = NULL;
313 m_pSelectedTrack = NULL;
314 m_pRoutePointEditTarget = NULL;
315 m_pFoundPoint = NULL;
316 m_pMouseRoute = NULL;
317 m_prev_pMousePoint = NULL;
318 m_pEditRouteArray = NULL;
319 m_pFoundRoutePoint = NULL;
320 m_FinishRouteOnKillFocus = true;
321
322 m_pRolloverRouteSeg = NULL;
323 m_pRolloverTrackSeg = NULL;
324 m_bsectors_shown = false;
325
326 m_bbrightdir = false;
327 r_gamma_mult = 1;
328 g_gamma_mult = 1;
329 b_gamma_mult = 1;
330
331 m_pos_image_user_day = NULL;
332 m_pos_image_user_dusk = NULL;
333 m_pos_image_user_night = NULL;
334 m_pos_image_user_grey_day = NULL;
335 m_pos_image_user_grey_dusk = NULL;
336 m_pos_image_user_grey_night = NULL;
337
338 m_zoom_factor = 1;
339 m_rotation_speed = 0;
340 m_mustmove = 0;
341
342 m_OSoffsetx = 0.;
343 m_OSoffsety = 0.;
344
345 m_pos_image_user_yellow_day = NULL;
346 m_pos_image_user_yellow_dusk = NULL;
347 m_pos_image_user_yellow_night = NULL;
348
349 SetOwnShipState(SHIP_INVALID);
350
351 undo = new Undo(this);
352
353 VPoint.Invalidate();
354
355 m_glcc = NULL;
356
357 m_focus_indicator_pix = 1;
358
359 m_pCurrentStack = NULL;
360 m_bpersistent_quilt = false;
361 m_piano_ctx_menu = NULL;
362 m_Compass = NULL;
363 m_NotificationsList = NULL;
364 m_notification_button = NULL;
365
366 g_ChartNotRenderScaleFactor = 2.0;
367 m_bShowScaleInStatusBar = true;
368
369 m_muiBar = NULL;
370 m_bShowScaleInStatusBar = false;
371 m_show_focus_bar = true;
372
373 m_bShowOutlines = false;
374 m_bDisplayGrid = false;
375 m_bShowDepthUnits = true;
376 m_encDisplayCategory = (int)STANDARD;
377
378 m_encShowLights = true;
379 m_encShowAnchor = true;
380 m_encShowDataQual = false;
381 m_bShowGPS = true;
382 m_pQuilt = new Quilt(this);
383 SetQuiltMode(true);
384 SetAlertString("");
385 m_sector_glat = 0;
386 m_sector_glon = 0;
387 g_PrintingInProgress = false;
388
389#ifdef HAVE_WX_GESTURE_EVENTS
390 m_oldVPSScale = -1.0;
391 m_popupWanted = false;
392 m_leftdown = false;
393#endif /* HAVE_WX_GESTURE_EVENTS */
394 m_inLongPress = false;
395 m_sw_down_time = 0;
396 m_sw_up_time = 0;
397 m_sw_left_down.Start();
398 m_sw_left_up.Start();
399
400 SetupGlCanvas();
401
402 singleClickEventIsValid = false;
403
404 // Build the cursors
405
406 pCursorLeft = NULL;
407 pCursorRight = NULL;
408 pCursorUp = NULL;
409 pCursorDown = NULL;
410 pCursorArrow = NULL;
411 pCursorPencil = NULL;
412 pCursorCross = NULL;
413
414 RebuildCursors();
415
416 SetCursor(*pCursorArrow);
417
418 pPanTimer = new wxTimer(this, m_MouseDragging);
419 pPanTimer->Stop();
420
421 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
422 pMovementTimer->Stop();
423
424 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
425 pMovementStopTimer->Stop();
426
427 pRotDefTimer = new wxTimer(this, ROT_TIMER);
428 pRotDefTimer->Stop();
429
430 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
431 m_DoubleClickTimer->Stop();
432
433 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
434 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
435 m_chart_drag_inertia_active = false;
436
437 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
438 m_animationActive = false;
439 m_menuTimer.SetOwner(this, MENU_TIMER);
440 m_tap_timer.SetOwner(this, TAP_TIMER);
441
442 m_panx = m_pany = 0;
443 m_panspeed = 0;
444 m_panx_target_final = m_pany_target_final = 0;
445 m_panx_target_now = m_pany_target_now = 0;
446 m_DragTrigger = -1;
447
448 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
449 pCurTrackTimer->Stop();
450 m_curtrack_timer_msec = 10;
451
452 m_wheelzoom_stop_oneshot = 0;
453 m_last_wheel_dir = 0;
454
455 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
456
457 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
458
459 m_rollover_popup_timer_msec = 20;
460
461 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
462
463 m_b_rot_hidef = true;
464
465 proute_bm = NULL;
466 m_prot_bm = NULL;
467
468 m_upMode = NORTH_UP_MODE;
469 m_bLookAhead = false;
470
471 // Set some benign initial values
472
473 m_cs = GLOBAL_COLOR_SCHEME_DAY;
474 VPoint.clat = 0;
475 VPoint.clon = 0;
476 VPoint.view_scale_ppm = 1;
477 VPoint.Invalidate();
478 m_nMeasureState = 0;
479 m_ignore_next_leftup = false;
480
481 m_canvas_scale_factor = 1.;
482
483 m_canvas_width = 1000;
484
485 m_overzoomTextWidth = 0;
486 m_overzoomTextHeight = 0;
487
488 // Create the default world chart
489 pWorldBackgroundChart = new GSHHSChart;
490 gShapeBasemap.Reset();
491
492 // Create the default depth unit emboss maps
493 m_pEM_Feet = NULL;
494 m_pEM_Meters = NULL;
495 m_pEM_Fathoms = NULL;
496
497 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
498
499 m_pEM_OverZoom = NULL;
500 SetOverzoomFont();
501 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
502
503 // Build icons for tide/current points
504 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
505 m_bmTideDay =
506 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
507
508 // Dusk
509 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
510
511 // Night
512 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
513
514 // Build Dusk/Night ownship icons
515 double factor_dusk = 0.5;
516 double factor_night = 0.25;
517
518 // Red
519 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
520
521 int rimg_width = m_os_image_red_day.GetWidth();
522 int rimg_height = m_os_image_red_day.GetHeight();
523
524 m_os_image_red_dusk = m_os_image_red_day.Copy();
525 m_os_image_red_night = m_os_image_red_day.Copy();
526
527 for (int iy = 0; iy < rimg_height; iy++) {
528 for (int ix = 0; ix < rimg_width; ix++) {
529 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
530 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
531 m_os_image_red_day.GetGreen(ix, iy),
532 m_os_image_red_day.GetBlue(ix, iy));
533 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
534 hsv.value = hsv.value * factor_dusk;
535 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
536 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
537
538 hsv = wxImage::RGBtoHSV(rgb);
539 hsv.value = hsv.value * factor_night;
540 nrgb = wxImage::HSVtoRGB(hsv);
541 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
542 }
543 }
544 }
545
546 // Grey
547 m_os_image_grey_day =
548 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
549
550 int gimg_width = m_os_image_grey_day.GetWidth();
551 int gimg_height = m_os_image_grey_day.GetHeight();
552
553 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
554 m_os_image_grey_night = m_os_image_grey_day.Copy();
555
556 for (int iy = 0; iy < gimg_height; iy++) {
557 for (int ix = 0; ix < gimg_width; ix++) {
558 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
559 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
560 m_os_image_grey_day.GetGreen(ix, iy),
561 m_os_image_grey_day.GetBlue(ix, iy));
562 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
563 hsv.value = hsv.value * factor_dusk;
564 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
565 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
566
567 hsv = wxImage::RGBtoHSV(rgb);
568 hsv.value = hsv.value * factor_night;
569 nrgb = wxImage::HSVtoRGB(hsv);
570 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
571 }
572 }
573 }
574
575 // Yellow
576 m_os_image_yellow_day = m_os_image_red_day.Copy();
577
578 gimg_width = m_os_image_yellow_day.GetWidth();
579 gimg_height = m_os_image_yellow_day.GetHeight();
580
581 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
582 m_os_image_yellow_night = m_os_image_red_day.Copy();
583
584 for (int iy = 0; iy < gimg_height; iy++) {
585 for (int ix = 0; ix < gimg_width; ix++) {
586 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
587 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
588 m_os_image_yellow_day.GetGreen(ix, iy),
589 m_os_image_yellow_day.GetBlue(ix, iy));
590 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
591 hsv.hue += 60. / 360.; // shift to yellow
592 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
593 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
594
595 hsv = wxImage::RGBtoHSV(rgb);
596 hsv.value = hsv.value * factor_dusk;
597 hsv.hue += 60. / 360.; // shift to yellow
598 nrgb = wxImage::HSVtoRGB(hsv);
599 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
600
601 hsv = wxImage::RGBtoHSV(rgb);
602 hsv.hue += 60. / 360.; // shift to yellow
603 hsv.value = hsv.value * factor_night;
604 nrgb = wxImage::HSVtoRGB(hsv);
605 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
606 }
607 }
608 }
609
610 // Set initial pointers to ownship images
611 m_pos_image_red = &m_os_image_red_day;
612 m_pos_image_yellow = &m_os_image_yellow_day;
613 m_pos_image_grey = &m_os_image_grey_day;
614
615 SetUserOwnship();
616
617 m_pBrightPopup = NULL;
618
619#ifdef ocpnUSE_GL
620 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
621#endif
622
623 SetupGridFont();
624
625 m_Piano = new Piano(this);
626
627 m_bShowCompassWin = true;
628 m_Compass = new ocpnCompass(this);
629 m_Compass->SetScaleFactor(g_compass_scalefactor);
630 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
631
632 if (IsPrimaryCanvas()) {
633 m_notification_button = new NotificationButton(this);
634 m_notification_button->SetScaleFactor(g_compass_scalefactor);
635 m_notification_button->Show(true);
636 }
637
638 m_pianoFrozen = false;
639
640 SetMinSize(wxSize(200, 200));
641
642 m_displayScale = 1.0;
643#if defined(__WXOSX__) || defined(__WXGTK3__)
644 // Support scaled HDPI displays.
645 m_displayScale = GetContentScaleFactor();
646#endif
647 VPoint.SetPixelScale(m_displayScale);
648
649#ifdef HAVE_WX_GESTURE_EVENTS
650 // if (!m_glcc)
651 {
652 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
653 wxLogError("Failed to enable touch events");
654 }
655
656 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
657
658 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
659 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
660
661 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
662 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
663
664 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
665 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
666
667 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
668 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
669 }
670#endif
671
672 // Listen for notification events
673 auto &noteman = NotificationManager::GetInstance();
674
675 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
676 evt_notificationlist_change_listener.Listen(
677 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
678 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
679 if (m_NotificationsList && m_NotificationsList->IsShown()) {
680 m_NotificationsList->ReloadNotificationList();
681 }
682 Refresh();
683 });
684}
685
686ChartCanvas::~ChartCanvas() {
687 delete pThumbDIBShow;
688
689 // Delete Cursors
690 delete pCursorLeft;
691 delete pCursorRight;
692 delete pCursorUp;
693 delete pCursorDown;
694 delete pCursorArrow;
695 delete pCursorPencil;
696 delete pCursorCross;
697
698 delete pPanTimer;
699 delete pMovementTimer;
700 delete pMovementStopTimer;
701 delete pCurTrackTimer;
702 delete pRotDefTimer;
703 delete m_DoubleClickTimer;
704
705 delete m_pTrackRolloverWin;
706 delete m_pRouteRolloverWin;
707 delete m_pAISRolloverWin;
708 delete m_pBrightPopup;
709
710 delete m_pCIWin;
711
712 delete pscratch_bm;
713
714 m_dc_route.SelectObject(wxNullBitmap);
715 delete proute_bm;
716
717 delete pWorldBackgroundChart;
718 delete pss_overlay_bmp;
719
720 delete m_pEM_Feet;
721 delete m_pEM_Meters;
722 delete m_pEM_Fathoms;
723
724 delete m_pEM_OverZoom;
725 // delete m_pEM_CM93Offset;
726
727 delete m_prot_bm;
728
729 delete m_pos_image_user_day;
730 delete m_pos_image_user_dusk;
731 delete m_pos_image_user_night;
732 delete m_pos_image_user_grey_day;
733 delete m_pos_image_user_grey_dusk;
734 delete m_pos_image_user_grey_night;
735 delete m_pos_image_user_yellow_day;
736 delete m_pos_image_user_yellow_dusk;
737 delete m_pos_image_user_yellow_night;
738
739 delete undo;
740#ifdef ocpnUSE_GL
741 if (!g_bdisable_opengl) {
742 delete m_glcc;
743
744#if wxCHECK_VERSION(2, 9, 0)
745 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
746#endif
747 }
748#endif
749
750 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
751 // wx tries to deliver events to this canvas during destroy.
752 MUIBar *muiBar = m_muiBar;
753 m_muiBar = 0;
754 delete muiBar;
755 delete m_pQuilt;
756 delete m_pCurrentStack;
757 delete m_Compass;
758 delete m_Piano;
759 delete m_notification_button;
760}
761
762void ChartCanvas::SetupGridFont() {
763 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
764 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
765 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
766 m_pgridFont = FontMgr::Get().FindOrCreateFont(
767 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
768 FALSE, wxString("Arial"));
769}
770
771void ChartCanvas::RebuildCursors() {
772 delete pCursorLeft;
773 delete pCursorRight;
774 delete pCursorUp;
775 delete pCursorDown;
776 delete pCursorArrow;
777 delete pCursorPencil;
778 delete pCursorCross;
779
780 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
781 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
782
783 double pencilScale =
784 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
785
786 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
787 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
788 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
789 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
790 wxImage ICursorPencil =
791 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
792 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
793
794#if !defined(__WXMSW__) && !defined(__WXQT__)
795 ICursorLeft.ConvertAlphaToMask(128);
796 ICursorRight.ConvertAlphaToMask(128);
797 ICursorUp.ConvertAlphaToMask(128);
798 ICursorDown.ConvertAlphaToMask(128);
799 ICursorPencil.ConvertAlphaToMask(10);
800 ICursorCross.ConvertAlphaToMask(10);
801#endif
802
803 if (ICursorLeft.Ok()) {
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
805 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
806 pCursorLeft = new wxCursor(ICursorLeft);
807 } else
808 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
809
810 if (ICursorRight.Ok()) {
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
812 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
813 pCursorRight = new wxCursor(ICursorRight);
814 } else
815 pCursorRight = new wxCursor(wxCURSOR_ARROW);
816
817 if (ICursorUp.Ok()) {
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
819 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
820 pCursorUp = new wxCursor(ICursorUp);
821 } else
822 pCursorUp = new wxCursor(wxCURSOR_ARROW);
823
824 if (ICursorDown.Ok()) {
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
826 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
827 pCursorDown = new wxCursor(ICursorDown);
828 } else
829 pCursorDown = new wxCursor(wxCURSOR_ARROW);
830
831 if (ICursorPencil.Ok()) {
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
833 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
834 pCursorPencil = new wxCursor(ICursorPencil);
835 } else
836 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
837
838 if (ICursorCross.Ok()) {
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
840 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
841 pCursorCross = new wxCursor(ICursorCross);
842 } else
843 pCursorCross = new wxCursor(wxCURSOR_ARROW);
844
845 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
846 pPlugIn_Cursor = NULL;
847}
848
849void ChartCanvas::CanvasApplyLocale() {
850 CreateDepthUnitEmbossMaps(m_cs);
851 CreateOZEmbossMapData(m_cs);
852}
853
854void ChartCanvas::SetupGlCanvas() {
855#ifndef __ANDROID__
856#ifdef ocpnUSE_GL
857 if (!g_bdisable_opengl) {
858 if (g_bopengl) {
859 wxLogMessage("Creating glChartCanvas");
860 m_glcc = new glChartCanvas(this);
861
862 // We use one context for all GL windows, so that textures etc will be
863 // automatically shared
864 if (IsPrimaryCanvas()) {
865 // qDebug() << "Creating Primary Context";
866
867 // wxGLContextAttrs ctxAttr;
868 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
869 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
870 // NULL, &ctxAttr);
871 wxGLContext *pctx = new wxGLContext(m_glcc);
872 m_glcc->SetContext(pctx);
873 g_pGLcontext = pctx; // Save a copy of the common context
874 } else {
875#ifdef __WXOSX__
876 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
877#else
878 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
879 // saved common context
880#endif
881 }
882 }
883 }
884#endif
885#endif
886
887#ifdef __ANDROID__ // ocpnUSE_GL
888 if (!g_bdisable_opengl) {
889 if (g_bopengl) {
890 // qDebug() << "SetupGlCanvas";
891 wxLogMessage("Creating glChartCanvas");
892
893 // We use one context for all GL windows, so that textures etc will be
894 // automatically shared
895 if (IsPrimaryCanvas()) {
896 qDebug() << "Creating Primary glChartCanvas";
897
898 // wxGLContextAttrs ctxAttr;
899 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
900 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
901 // NULL, &ctxAttr);
902 m_glcc = new glChartCanvas(this);
903
904 wxGLContext *pctx = new wxGLContext(m_glcc);
905 m_glcc->SetContext(pctx);
906 g_pGLcontext = pctx; // Save a copy of the common context
907 m_glcc->m_pParentCanvas = this;
908 // m_glcc->Reparent(this);
909 } else {
910 qDebug() << "Creating Secondary glChartCanvas";
911 // QGLContext *pctx =
912 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
913 // << "pctx: " << pctx;
914
915 m_glcc =
916 new glChartCanvas(wxTheApp->GetTopWindow(),
917 top_frame::Get()->GetWxGlCanvas()); // Shared
918 // m_glcc = new glChartCanvas(this, pctx); //Shared
919 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
920 wxGLContext *pwxctx = new wxGLContext(m_glcc);
921 m_glcc->SetContext(pwxctx);
922 m_glcc->m_pParentCanvas = this;
923 // m_glcc->Reparent(this);
924 }
925 }
926 }
927#endif
928}
929
930void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
931 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
932
933 // On Android, we get a KillFocus on just about every keystroke.
934 // Why?
935#ifdef __ANDROID__
936 return;
937#endif
938
939 // Special logic:
940 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
941 // canvas focus. Why??? Who knows... So, we provide for this case by
942 // starting a timer if required to actually Finish() a route on a legitimate
943 // focus change, but not if the focus is quickly regained ( <20 msec.) on
944 // this canvas.
945#ifdef __WXOSX__
946 if (m_routeState && m_FinishRouteOnKillFocus)
947 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
948#else
949 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
950#endif
951}
952
953void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
954 m_routeFinishTimer.Stop();
955
956 // Try to keep the global top-line menubar selections up to date with the
957 // current "focus" canvas
958 top_frame::Get()->UpdateGlobalMenuItems(this);
959
960 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
961}
962
963void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
964 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
965}
966
967#ifdef HAVE_WX_GESTURE_EVENTS
968void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
969#ifdef __ANDROID__
970 /* we defer the popup menu call upon the leftup event
971 else the menu disappears immediately,
972 (see
973 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
974 */
975 m_popupWanted = true;
976#else
977 m_inLongPress = !g_bhide_context_menus;
978
979 // Send a synthetic mouse left-up event to sync the mouse pan logic.
980 m_menuPos = event.GetPosition();
981 wxMouseEvent ev(wxEVT_LEFT_UP);
982 ev.m_x = m_menuPos.x;
983 ev.m_y = m_menuPos.y;
984 wxPostEvent(this, ev);
985
986 // In touch mode, send a "RIGHT CLICK" event, for plugins
987 if (g_btouch) {
988 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
989 ev_right_click.m_x = m_menuPos.x;
990 ev_right_click.m_y = m_menuPos.y;
991 MouseEvent(ev_right_click);
992 }
993#endif
994}
995
996void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
997 // not implemented yet
998}
999
1000void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1001
1002void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1003
1004void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1005#ifdef __WXGTK__
1006 long dt = m_sw_left_up.Time() - m_sw_up_time;
1007 m_sw_up_time = m_sw_left_up.Time();
1008
1009 // printf(" dt %ld\n",dt);
1010 if (dt < 5) {
1011 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1012 // ignore it.
1013 return;
1014 }
1015#endif
1016 // printf("Left_UP\n");
1017
1018 wxPoint pos = event.GetPosition();
1019
1020 m_leftdown = false;
1021
1022 if (!m_popupWanted) {
1023 wxMouseEvent ev(wxEVT_LEFT_UP);
1024 ev.m_x = pos.x;
1025 ev.m_y = pos.y;
1026 MouseEvent(ev);
1027 return;
1028 }
1029
1030 m_popupWanted = false;
1031
1032 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1033 ev.m_x = pos.x;
1034 ev.m_y = pos.y;
1035
1036 MouseEvent(ev);
1037}
1038
1039void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1040 m_leftdown = true;
1041
1042 // Detect and manage multiple left-downs coming from GTK mouse emulation
1043#ifdef __WXGTK__
1044 long dt = m_sw_left_down.Time() - m_sw_down_time;
1045 m_sw_down_time = m_sw_left_down.Time();
1046
1047 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1048
1049 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1050 // The timing between the two events is dependent upon the wxWidgets
1051 // message queue status, and the processing time required for intervening
1052 // events.
1053 // We detect and remove the duplicate events by measuring the elapsed time
1054 // between arrival of events.
1055 // Choose a duplicate detection time long enough to catch worst case time lag
1056 // between duplicating events, but considerably shorter than the nominal
1057 // "intentional double-click" time interval defined generally as 350 msec.
1058 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1059 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1060 // ignore it.
1061 return;
1062 }
1063#endif
1064
1065 // printf("Left_DOWN\n");
1066
1067 // detect and manage double-tap
1068#ifdef __WXGTK__
1069 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1070 2; // Use system setting for distance
1071 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1072 m_lastTapPos.y - max_double_click_distance,
1073 max_double_click_distance * 2, max_double_click_distance * 2);
1074
1075 // A new tap has started, check if it's close enough and in time
1076 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1077 // printf(" TapBump 1\n");
1078 m_tap_count += 1;
1079 } else {
1080 // printf(" TapSet 1\n");
1081 m_tap_count = 1;
1082 m_lastTapPos = event.GetPosition();
1083 m_tap_timer.StartOnce(
1084 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1085 }
1086
1087 if (m_tap_count == 2) {
1088 // printf(" Doubletap detected\n");
1089 m_tap_count = 0; // Reset after a double-tap
1090
1091 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1092 ev.m_x = event.m_x;
1093 ev.m_y = event.m_y;
1094 // wxPostEvent(this, ev);
1095 MouseEvent(ev);
1096 return;
1097 }
1098
1099#endif
1100
1101 MouseEvent(event);
1102}
1103
1104void ChartCanvas::OnMotion(wxMouseEvent &event) {
1105 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1106 dragging, upon simple click, and without the OnLeftDown event before Thus,
1107 this consists in skiping it, and setting the leftdown bit according to a
1108 status that we trust */
1109 event.m_leftDown = m_leftdown;
1110 MouseEvent(event);
1111}
1112
1113void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1114 /* there are spurious end zoom events upon right-click */
1115 if (event.IsGestureEnd()) return;
1116
1117 double factor = event.GetZoomFactor();
1118
1119 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1120 m_oldVPSScale = GetVPScale();
1121 }
1122
1123 double current_vps = GetVPScale();
1124 double wanted_factor = m_oldVPSScale / current_vps * factor;
1125
1126 ZoomCanvas(wanted_factor, true, false);
1127
1128 // Allow combined zoom/pan operation
1129 if (event.IsGestureStart()) {
1130 m_zoomStartPoint = event.GetPosition();
1131 } else {
1132 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1133 PanCanvas(-delta.x, -delta.y);
1134 m_zoomStartPoint = event.GetPosition();
1135 }
1136}
1137
1138void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1139
1140void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1141 DoRotateCanvas(0.0);
1142}
1143#endif /* HAVE_WX_GESTURE_EVENTS */
1144
1145void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1146 // printf("tap timer %d\n", m_tap_count);
1147 m_tap_count = 0;
1148}
1149
1150void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1151 m_FinishRouteOnKillFocus = false;
1152 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1153 m_FinishRouteOnKillFocus = true;
1154}
1155
1156void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1157 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1158 m_vLat = pcc->iLat;
1159 m_vLon = pcc->iLon;
1160
1161 m_restore_dbindex = pcc->DBindex;
1162 m_bFollow = pcc->bFollow;
1163 if (pcc->GroupID < 0) pcc->GroupID = 0;
1164
1165 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1166 m_groupIndex = 0;
1167 else
1168 m_groupIndex = pcc->GroupID;
1169
1170 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1171
1172 ShowTides(pcc->bShowTides);
1173 ShowCurrents(pcc->bShowCurrents);
1174
1175 SetShowDepthUnits(pcc->bShowDepthUnits);
1176 SetShowGrid(pcc->bShowGrid);
1177 SetShowOutlines(pcc->bShowOutlines);
1178
1179 SetShowAIS(pcc->bShowAIS);
1180 SetAttenAIS(pcc->bAttenAIS);
1181
1182 // ENC options
1183 SetShowENCText(pcc->bShowENCText);
1184 m_encDisplayCategory = pcc->nENCDisplayCategory;
1185 m_encShowDepth = pcc->bShowENCDepths;
1186 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1187 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1188 m_encShowLights = pcc->bShowENCLights;
1189 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1190 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1191 m_encShowDataQual = pcc->bShowENCDataQuality;
1192
1193 bool courseUp = pcc->bCourseUp;
1194 bool headUp = pcc->bHeadUp;
1195 m_upMode = NORTH_UP_MODE;
1196 if (courseUp)
1197 m_upMode = COURSE_UP_MODE;
1198 else if (headUp)
1199 m_upMode = HEAD_UP_MODE;
1200
1201 m_bLookAhead = pcc->bLookahead;
1202
1203 m_singleChart = NULL;
1204}
1205
1206void ChartCanvas::ApplyGlobalSettings() {
1207 // GPS compas window
1208 if (m_Compass) {
1209 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1210 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1211 }
1212 if (m_notification_button) m_notification_button->UpdateStatus();
1213}
1214
1215void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1216 bool groupOK = CheckGroup(m_groupIndex);
1217
1218 if (!groupOK) {
1219 SetGroupIndex(m_groupIndex, true);
1220 }
1221}
1222
1223void ChartCanvas::SetShowGPS(bool bshow) {
1224 if (m_bShowGPS != bshow) {
1225 delete m_Compass;
1226 m_Compass = new ocpnCompass(this, bshow);
1227 m_Compass->SetScaleFactor(g_compass_scalefactor);
1228 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1229 }
1230 m_bShowGPS = bshow;
1231}
1232
1233void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1234 m_bShowCompassWin = bshow;
1235 if (m_Compass) {
1236 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1237 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1238 }
1239}
1240
1241int ChartCanvas::GetPianoHeight() {
1242 int height = 0;
1243 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1244
1245 return height;
1246}
1247
1248void ChartCanvas::ConfigureChartBar() {
1249 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1250
1251 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1252 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1253
1254 if (GetQuiltMode()) {
1255 m_Piano->SetRoundedRectangles(true);
1256 }
1257 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1258 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1259 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1260}
1261
1262void ChartCanvas::ShowTides(bool bShow) {
1263 top_frame::Get()->LoadHarmonics();
1264
1265 if (ptcmgr->IsReady()) {
1266 SetbShowTide(bShow);
1267
1268 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1269 } else {
1270 wxLogMessage("Chart1::Event...TCMgr Not Available");
1271 SetbShowTide(false);
1272 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1273 }
1274
1275 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1276 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1277
1278 // TODO
1279 // if( GetbShowTide() ) {
1280 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1281 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1282 // update
1283 // } else
1284 // FrameTCTimer.Stop();
1285}
1286
1287void ChartCanvas::ShowCurrents(bool bShow) {
1288 top_frame::Get()->LoadHarmonics();
1289
1290 if (ptcmgr->IsReady()) {
1291 SetbShowCurrent(bShow);
1292 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1293 } else {
1294 wxLogMessage("Chart1::Event...TCMgr Not Available");
1295 SetbShowCurrent(false);
1296 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1297 }
1298
1299 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1300 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1301
1302 // TODO
1303 // if( GetbShowCurrent() ) {
1304 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1305 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1306 // update
1307 // } else
1308 // FrameTCTimer.Stop();
1309}
1310
1311// TODO
1312static ChartDummy *pDummyChart;
1313
1316
1317void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1318
1319void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1320 SetAlertString("");
1321
1322 int new_index = index;
1323 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1324
1325 bool bgroup_override = false;
1326 int old_group_index = new_index;
1327
1328 if (!CheckGroup(new_index)) {
1329 new_index = 0;
1330 bgroup_override = true;
1331 }
1332
1333 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1334 new_index = index;
1335
1336 // Get the currently displayed chart native scale, and the current ViewPort
1337 int current_chart_native_scale = GetCanvasChartNativeScale();
1338 ViewPort vp = GetVP();
1339
1340 m_groupIndex = new_index;
1341
1342 // Are there ENCs in this group
1343 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1344
1345 // Update the MUIBar for ENC availability
1346 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1347
1348 // Allow the chart database to pre-calculate the MBTile inclusion test
1349 // boolean...
1350 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1351
1352 // Invalidate the "sticky" chart on group change, since it might not be in
1353 // the new group
1354 g_sticky_chart = -1;
1355
1356 // We need a chartstack and quilt to figure out which chart to open in the
1357 // new group
1358 UpdateCanvasOnGroupChange();
1359
1360 int dbi_now = -1;
1361 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1362
1363 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1364
1365 // If a new reference chart is indicated, set a good scale for it.
1366 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1367 double best_scale = GetBestStartScale(dbi_hint, vp);
1368 SetVPScale(best_scale);
1369 }
1370
1371 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1372
1373 // Refresh the canvas, selecting the "best" chart,
1374 // applying the prior ViewPort exactly
1375 canvasChartsRefresh(dbi_hint);
1376
1377 UpdateCanvasControlBar();
1378
1379 if (!autoSwitch && bgroup_override) {
1380 // show a short timed message box
1381 wxString msg(_("Group \""));
1382
1383 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1384 msg += pGroup->m_group_name;
1385
1386 msg += _("\" is empty.");
1387
1388 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1389
1390 return;
1391 }
1392
1393 // Message box is deferred so that canvas refresh occurs properly before
1394 // dialog
1395 if (bgroup_override) {
1396 wxString msg(_("Group \""));
1397
1398 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1399 msg += pGroup->m_group_name;
1400
1401 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1402
1403 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1404 }
1405}
1406
1407bool ChartCanvas::CheckGroup(int igroup) {
1408 if (!ChartData) return true; // Not known yet...
1409
1410 if (igroup == 0) return true; // "all charts" is always OK
1411
1412 if (igroup < 0) // negative group is an error
1413 return false;
1414
1415 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1416
1417 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1418 // and auto-shift to group 0
1419 return false;
1420
1421 for (const auto &elem : pGroup->m_element_array) {
1422 for (unsigned int ic = 0;
1423 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1424 auto &cte = ChartData->GetChartTableEntry(ic);
1425 wxString chart_full_path(cte.GetpFullPath(), wxConvUTF8);
1426
1427 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1428 }
1429 }
1430
1431 // If necessary, check for GSHHS
1432 for (const auto &elem : pGroup->m_element_array) {
1433 const wxString &element_root = elem.m_element_name;
1434 wxString test_string = "GSHH";
1435 if (element_root.Upper().Contains(test_string)) return true;
1436 }
1437
1438 return false;
1439}
1440
1441void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1442 if (!ChartData) return;
1443
1444 AbstractPlatform::ShowBusySpinner();
1445
1446 double old_scale = GetVPScale();
1447 InvalidateQuilt();
1448 SetQuiltRefChart(-1);
1449
1450 m_singleChart = NULL;
1451
1452 // delete m_pCurrentStack;
1453 // m_pCurrentStack = NULL;
1454
1455 // Build a new ChartStack
1456 if (!m_pCurrentStack) {
1457 m_pCurrentStack = new ChartStack;
1458 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1459 }
1460
1461 if (-1 != dbi_hint) {
1462 if (GetQuiltMode()) {
1463 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1464 SetQuiltRefChart(dbi_hint);
1465 } else {
1466 // Open the saved chart
1467 ChartBase *pTentative_Chart;
1468 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1469
1470 if (pTentative_Chart) {
1471 /* m_singleChart is always NULL here, (set above) should this go before
1472 * that? */
1473 if (m_singleChart) m_singleChart->Deactivate();
1474
1475 m_singleChart = pTentative_Chart;
1476 m_singleChart->Activate();
1477
1478 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1479 GetpCurrentStack(), m_singleChart->GetFullPath());
1480 }
1481 }
1482
1483 // refresh_Piano();
1484 } else {
1485 // Select reference chart from the stack, as though clicked by user
1486 // Make it the smallest scale chart on the stack
1487 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1488 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1489 SetQuiltRefChart(selected_index);
1490 }
1491
1492 // Validate the correct single chart, or set the quilt mode as appropriate
1493 SetupCanvasQuiltMode();
1494 if (!GetQuiltMode() && m_singleChart == 0) {
1495 // use a dummy like in DoChartUpdate
1496 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1497 m_singleChart = pDummyChart;
1498 SetVPScale(old_scale);
1499 }
1500
1501 ReloadVP();
1502
1503 UpdateCanvasControlBar();
1504 UpdateGPSCompassStatusBox(true);
1505
1506 SetCursor(wxCURSOR_ARROW);
1507
1508 AbstractPlatform::HideBusySpinner();
1509}
1510
1511bool ChartCanvas::DoCanvasUpdate() {
1512 double tLat, tLon; // Chart Stack location
1513 double vpLat, vpLon; // ViewPort location
1514 bool blong_jump = false;
1515 meters_to_shift = 0;
1516 dir_to_shift = 0;
1517
1518 bool bNewChart = false;
1519 bool bNewView = false;
1520 bool bCanvasChartAutoOpen = true; // debugging
1521
1522 bool bNewPiano = false;
1523 bool bOpenSpecified;
1524 ChartStack LastStack;
1525 ChartBase *pLast_Ch;
1526
1527 ChartStack WorkStack;
1528
1529 if (!GetVP().IsValid()) return false;
1530 if (bDBUpdateInProgress) return false;
1531 if (!ChartData) return false;
1532
1533 if (ChartData->IsBusy()) return false;
1534 // Do not disturb any existing animations
1535 if (m_chart_drag_inertia_active) return false;
1536 if (m_animationActive) return false;
1537
1538 // Startup case:
1539 // Quilting is enabled, but the last chart seen was not quiltable
1540 // In this case, drop to single chart mode, set persistence flag,
1541 // And open the specified chart
1542 // TODO implement this
1543 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1544 // if( GetQuiltMode() ) {
1545 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1546 // gFrame->ToggleQuiltMode();
1547 // m_bpersistent_quilt = true;
1548 // m_singleChart = NULL;
1549 // }
1550 // }
1551 // }
1552
1553 // If in auto-follow mode, use the current glat,glon to build chart
1554 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1555 // other means
1556
1557 if (m_bFollow) {
1558 tLat = gLat;
1559 tLon = gLon;
1560
1561 // Set the ViewPort center based on the OWNSHIP offset
1562 double dx = m_OSoffsetx;
1563 double dy = m_OSoffsety;
1564 double d_east = dx / GetVP().view_scale_ppm;
1565 double d_north = dy / GetVP().view_scale_ppm;
1566
1567 if (GetUpMode() == NORTH_UP_MODE) {
1568 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1569 } else {
1570 double offset_angle = atan2(d_north, d_east);
1571 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1572 double chart_angle = GetVPRotation();
1573 double target_angle = chart_angle + offset_angle;
1574 double d_east_mod = offset_distance * cos(target_angle);
1575 double d_north_mod = offset_distance * sin(target_angle);
1576 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1577 }
1578
1579 // on lookahead mode, adjust the vp center point
1580 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1581 double cog_to_use = gCog;
1582 if (g_btenhertz &&
1583 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1584 cog_to_use = gCog_gt;
1585 blong_jump = true;
1586 }
1587 if (!g_btenhertz) cog_to_use = g_COGAvg;
1588
1589 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1590
1591 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1592 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1593
1594 double pixel_delta_tent =
1595 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1596
1597 double pixel_delta = 0;
1598
1599 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1600 // avoid jumping of the vp center point during slow maneuvering, or at
1601 // anchor....
1602 if (!std::isnan(gSog)) {
1603 if (gSog < 2.0)
1604 pixel_delta = 0.;
1605 else
1606 pixel_delta = pixel_delta_tent;
1607 }
1608
1609 meters_to_shift = 0;
1610 dir_to_shift = 0;
1611 if (!std::isnan(gCog)) {
1612 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1613 dir_to_shift = cog_to_use;
1614 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1615 &vpLon);
1616 } else {
1617 vpLat = gLat;
1618 vpLon = gLon;
1619 }
1620 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1621 m_OSoffsetx = 0; // center ownship on loss of GPS
1622 m_OSoffsety = 0;
1623 vpLat = gLat;
1624 vpLon = gLon;
1625 }
1626
1627 } else {
1628 tLat = m_vLat;
1629 tLon = m_vLon;
1630 vpLat = m_vLat;
1631 vpLon = m_vLon;
1632 }
1633
1634 if (GetQuiltMode()) {
1635 int current_db_index = -1;
1636 if (m_pCurrentStack)
1637 current_db_index =
1638 m_pCurrentStack
1639 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1640 // chart dbIndex
1641 else
1642 m_pCurrentStack = new ChartStack;
1643
1644 // This logic added to enable opening a chart when there is no
1645 // previous chart indication, either from inital startup, or from adding
1646 // new chart directory
1647 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1648 m_pCurrentStack) {
1649 if (m_pCurrentStack->nEntry) {
1650 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1651 1); // smallest scale
1652 SelectQuiltRefdbChart(new_dbIndex, true);
1653 m_bautofind = false;
1654 }
1655 }
1656
1657 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1658 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1659
1660 if (m_bFirstAuto) {
1661 // Allow the chart database to pre-calculate the MBTile inclusion test
1662 // boolean...
1663 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1664
1665 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1666 // physical pixels. On standard DPI displays where logical = physical
1667 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1668 // logical pixels, this ratio would be 0.5.
1669 double proposed_scale_onscreen =
1670 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1671
1672 int initial_db_index = m_restore_dbindex;
1673 if (initial_db_index < 0) {
1674 if (m_pCurrentStack->nEntry) {
1675 initial_db_index =
1676 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1677 } else
1678 m_bautofind = true; // initial_db_index = 0;
1679 }
1680
1681 if (m_pCurrentStack->nEntry) {
1682 int initial_type = ChartData->GetDBChartType(initial_db_index);
1683
1684 // Check to see if the target new chart is quiltable as a reference
1685 // chart
1686
1687 if (!IsChartQuiltableRef(initial_db_index)) {
1688 // If it is not quiltable, then walk the stack up looking for a
1689 // satisfactory chart i.e. one that is quiltable and of the same type
1690 // XXX if there's none?
1691 int stack_index = 0;
1692
1693 if (stack_index >= 0) {
1694 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1695 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1696 if (IsChartQuiltableRef(test_db_index) &&
1697 (initial_type ==
1698 ChartData->GetDBChartType(initial_db_index))) {
1699 initial_db_index = test_db_index;
1700 break;
1701 }
1702 stack_index++;
1703 }
1704 }
1705 }
1706
1707 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1708 if (pc) {
1709 SetQuiltRefChart(initial_db_index);
1710 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1711 }
1712 }
1713 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1714 // just GetVPScale(), so I'm not sure why it's necessary to define the
1715 // proposed_scale_onscreen variable.
1716 bNewView |= SetViewPoint(vpLat, vpLon,
1717 GetCanvasScaleFactor() / proposed_scale_onscreen,
1718 0, GetVPRotation());
1719 }
1720 // Measure rough jump distance if in bfollow mode
1721 // No good reason to do smooth pan for
1722 // jump distance more than one screen width at scale.
1723 bool super_jump = false;
1724 if (m_bFollow) {
1725 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1726 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1727 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1728 }
1729 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1730 goto update_finish;
1731 }
1732
1733 // Single Chart Mode from here....
1734 pLast_Ch = m_singleChart;
1735 ChartTypeEnum new_open_type;
1736 ChartFamilyEnum new_open_family;
1737 if (pLast_Ch) {
1738 new_open_type = pLast_Ch->GetChartType();
1739 new_open_family = pLast_Ch->GetChartFamily();
1740 } else {
1741 new_open_type = CHART_TYPE_KAP;
1742 new_open_family = CHART_FAMILY_RASTER;
1743 }
1744
1745 bOpenSpecified = m_bFirstAuto;
1746
1747 // Make sure the target stack is valid
1748 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1749
1750 // Build a chart stack based on tLat, tLon
1751 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1752 m_groupIndex)) { // Bogus Lat, Lon?
1753 if (NULL == pDummyChart) {
1754 pDummyChart = new ChartDummy;
1755 bNewChart = true;
1756 }
1757
1758 if (m_singleChart)
1759 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1760
1761 m_singleChart = pDummyChart;
1762
1763 // If the current viewpoint is invalid, set the default scale to
1764 // something reasonable.
1765 double set_scale = GetVPScale();
1766 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1767
1768 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1769
1770 // If the chart stack has just changed, there is new status
1771 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1772 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1773 bNewPiano = true;
1774 bNewChart = true;
1775 }
1776 }
1777
1778 // Copy the new (by definition empty) stack into the target stack
1779 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1780
1781 goto update_finish;
1782 }
1783
1784 // Check to see if Chart Stack has changed
1785 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1786 // New chart stack, so...
1787 bNewPiano = true;
1788
1789 // Save a copy of the current stack
1790 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1791
1792 // Copy the new stack into the target stack
1793 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1794
1795 // Is Current Chart in new stack?
1796
1797 int tEntry = -1;
1798 if (NULL != m_singleChart) // this handles startup case
1799 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1800 m_singleChart->GetFullPath());
1801
1802 if (tEntry != -1) { // m_singleChart is in the new stack
1803 m_pCurrentStack->CurrentStackEntry = tEntry;
1804 bNewChart = false;
1805 }
1806
1807 else // m_singleChart is NOT in new stack, or m_singlechart not yet set
1808 { // So, need to open a new chart
1809 // Find the largest scale raster chart that opens OK
1810
1811 ChartBase *pProposed = NULL;
1812
1813 if (bCanvasChartAutoOpen) {
1814 bool search_direction =
1815 false; // default is to search from lowest to highest
1816 int start_index = 0;
1817
1818 // A special case: If panning at high scale, open largest scale
1819 // chart first
1820 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1821 (LastStack.nEntry == 0)) {
1822 search_direction = true;
1823 start_index = m_pCurrentStack->nEntry - 1;
1824 }
1825
1826 // Another special case, open specified db index on program start
1827 if (bOpenSpecified) {
1828 if (m_restore_dbindex >= 0) {
1829 pProposed =
1830 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
1831 std::vector<int> one_array;
1832 one_array.push_back(m_restore_dbindex);
1833 m_Piano->SetActiveKeyArray(one_array);
1834 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
1835 m_restore_dbindex = -1; // Mark as used...
1836 }
1837
1838 if (!pProposed) {
1839 search_direction = false;
1840 start_index = m_restore_dbindex;
1841 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1842 start_index = 0;
1843
1844 new_open_type = CHART_TYPE_DONTCARE;
1845 }
1846 }
1847
1848 if (!pProposed) {
1849 pProposed = ChartData->OpenStackChartConditional(
1850 m_pCurrentStack, start_index, search_direction, new_open_type,
1851 new_open_family);
1852
1853 // Try to open other types/families of chart in some priority
1854 if (NULL == pProposed)
1855 pProposed = ChartData->OpenStackChartConditional(
1856 m_pCurrentStack, start_index, search_direction,
1857 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1858
1859 if (NULL == pProposed)
1860 pProposed = ChartData->OpenStackChartConditional(
1861 m_pCurrentStack, start_index, search_direction,
1862 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1863
1864 bNewChart = true;
1865 }
1866 } // bCanvasChartAutoOpen
1867
1868 else
1869 pProposed = NULL;
1870
1871 // If no go, then
1872 // Open a Dummy Chart
1873 if (NULL == pProposed) {
1874 if (NULL == pDummyChart) {
1875 pDummyChart = new ChartDummy;
1876 bNewChart = true;
1877 }
1878
1879 if (pLast_Ch)
1880 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1881
1882 pProposed = pDummyChart;
1883 }
1884
1885 // Arriving here, pProposed points to an opened chart, or NULL.
1886 if (m_singleChart) m_singleChart->Deactivate();
1887 m_singleChart = pProposed;
1888
1889 if (m_singleChart) {
1890 m_singleChart->Activate();
1891 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1892 m_pCurrentStack, m_singleChart->GetFullPath());
1893 }
1894 } // need new chart
1895
1896 // Arriving here, m_singleChart is opened and OK, or NULL
1897 if (NULL != m_singleChart) {
1898 // Setup the view using the current scale
1899 double set_scale = GetVPScale();
1900
1901 double proposed_scale_onscreen;
1902
1903 if (m_bFollow) { // autoset the scale only if in autofollow
1904 double new_scale_ppm =
1905 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1906 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1907 } else
1908 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1909
1910 // This logic will bring a new chart onscreen at roughly twice the true
1911 // paper scale equivalent. Note that first chart opened on application
1912 // startup (bOpenSpecified = true) will open at the config saved scale
1913 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1914 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1915 double equivalent_vp_scale =
1916 GetCanvasScaleFactor() / proposed_scale_onscreen;
1917 double new_scale_ppm =
1918 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1919 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1920 }
1921
1922 if (m_bFollow) { // bounds-check the scale only if in autofollow
1923 proposed_scale_onscreen =
1924 wxMin(proposed_scale_onscreen,
1925 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1926 GetCanvasWidth()));
1927 proposed_scale_onscreen =
1928 wxMax(proposed_scale_onscreen,
1929 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1931 }
1932
1933 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1934
1935 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1936 m_singleChart->GetChartSkew() * PI / 180.,
1937 GetVPRotation());
1938 }
1939 } // new stack
1940
1941 else // No change in Chart Stack
1942 {
1943 double s = GetVPScale();
1944 if ((m_bFollow) && m_singleChart)
1945 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1946 m_singleChart->GetChartSkew() * PI / 180.,
1947 GetVPRotation());
1948 }
1949
1950update_finish:
1951
1952 // TODO
1953 // if( bNewPiano ) UpdateControlBar();
1954
1955 m_bFirstAuto = false; // Auto open on program start
1956
1957 // If we need a Refresh(), do it here...
1958 // But don't duplicate a Refresh() done by SetViewPoint()
1959 if (bNewChart && !bNewView) Refresh(false);
1960
1961#ifdef ocpnUSE_GL
1962 // If a new chart, need to invalidate gl viewport for refresh
1963 // so the fbo gets flushed
1964 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1965#endif
1966
1967 return bNewChart | bNewView;
1968}
1969
1970void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1971 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1972
1973 SetQuiltRefChart(db_index);
1974 if (ChartData) {
1975 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1976 if (pc) {
1977 if (b_autoscale) {
1978 double best_scale_ppm = GetBestVPScale(pc);
1979 SetVPScale(best_scale_ppm);
1980 }
1981 } else
1982 SetQuiltRefChart(-1);
1983 } else
1984 SetQuiltRefChart(-1);
1985}
1986
1987void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1988 std::vector<int> piano_chart_index_array =
1989 GetQuiltExtendedStackdbIndexArray();
1990 int current_db_index = piano_chart_index_array[selected_index];
1991
1992 SelectQuiltRefdbChart(current_db_index);
1993}
1994
1995double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1996 if (pchart) {
1997 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1998
1999 if ((g_bPreserveScaleOnX) ||
2000 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2001 double new_scale_ppm = GetVPScale();
2002 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2003 } else {
2004 // This logic will bring the new chart onscreen at roughly twice the true
2005 // paper scale equivalent.
2006 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2007 double equivalent_vp_scale =
2008 GetCanvasScaleFactor() / proposed_scale_onscreen;
2009 double new_scale_ppm =
2010 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2011 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2012 }
2013
2014 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2015 // set. Otherwise, we get severe performance problems on all platforms
2016
2017 double max_underzoom_multiplier = 2.0;
2018 if (GetVP().b_quilt) {
2019 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2020 pchart->GetChartType(),
2021 pchart->GetChartFamily());
2022 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2023 }
2024
2025 proposed_scale_onscreen = wxMin(
2026 proposed_scale_onscreen,
2027 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2028 max_underzoom_multiplier);
2029
2030 // And, do not allow excessive overzoom either
2031 proposed_scale_onscreen =
2032 wxMax(proposed_scale_onscreen,
2033 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2034
2035 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2036 } else
2037 return 1.0;
2038}
2039
2040void ChartCanvas::SetupCanvasQuiltMode() {
2041 if (GetQuiltMode()) // going to quilt mode
2042 {
2043 ChartData->LockCache();
2044
2045 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2046
2047 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2048
2049 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2050 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2051 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2052 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2053
2054 m_Piano->SetRoundedRectangles(true);
2055
2056 // Select the proper Ref chart
2057 int target_new_dbindex = -1;
2058 if (m_pCurrentStack) {
2059 target_new_dbindex =
2060 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2061
2062 if (-1 != target_new_dbindex) {
2063 if (!IsChartQuiltableRef(target_new_dbindex)) {
2064 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2065 int type = ChartData->GetDBChartType(target_new_dbindex);
2066
2067 // walk the stack up looking for a satisfactory chart
2068 int stack_index = m_pCurrentStack->CurrentStackEntry;
2069
2070 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2071 (stack_index >= 0)) {
2072 int proj_tent = ChartData->GetDBChartProj(
2073 m_pCurrentStack->GetDBIndex(stack_index));
2074 int type_tent = ChartData->GetDBChartType(
2075 m_pCurrentStack->GetDBIndex(stack_index));
2076
2077 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2078 if ((proj == proj_tent) && (type_tent == type)) {
2079 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2080 break;
2081 }
2082 }
2083 stack_index++;
2084 }
2085 }
2086 }
2087 }
2088
2089 if (IsChartQuiltableRef(target_new_dbindex))
2090 SelectQuiltRefdbChart(target_new_dbindex,
2091 false); // Try not to allow a scale change
2092 else { // fall back to last selected no-quilt chart as new reference
2093 int stack_index = m_pCurrentStack->CurrentStackEntry;
2094 SelectQuiltRefdbChart(m_pCurrentStack->GetDBIndex(stack_index), false);
2095 }
2096
2097 m_singleChart = NULL; // Bye....
2098
2099 // Re-qualify the quilt reference chart selection
2100 AdjustQuiltRefChart();
2101
2102 // Restore projection type saved on last quilt mode toggle
2103 // TODO
2104 // if(g_sticky_projection != -1)
2105 // GetVP().SetProjectionType(g_sticky_projection);
2106 // else
2107 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2108 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2109
2110 } else // going to SC Mode
2111 {
2112 std::vector<int> empty_array;
2113 m_Piano->SetActiveKeyArray(empty_array);
2114 m_Piano->SetNoshowIndexArray(empty_array);
2115 m_Piano->SetEclipsedIndexArray(empty_array);
2116
2117 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2118 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2119 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2120 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2121 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2122
2123 m_Piano->SetRoundedRectangles(false);
2124 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2125 }
2126
2127 // When shifting from quilt to single chart mode, select the "best" single
2128 // chart to show
2129 if (!GetQuiltMode()) {
2130 if (ChartData && ChartData->IsValid()) {
2131 UnlockQuilt();
2132
2133 double tLat, tLon;
2134 if (m_bFollow == true) {
2135 tLat = gLat;
2136 tLon = gLon;
2137 } else {
2138 tLat = m_vLat;
2139 tLon = m_vLon;
2140 }
2141
2142 if (!m_singleChart) {
2143 // First choice is to adopt the outgoing quilt reference chart
2144 if (GetQuiltReferenceChartIndex() >= 0) {
2145 m_singleChart = ChartData->OpenChartFromDB(
2146 GetQuiltReferenceChartIndex(), FULL_INIT);
2147 }
2148 // Second choice is to use any "no-quilt restore index", if available
2149 else if (m_restore_dbindex >= 0) {
2150 m_singleChart =
2151 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
2152 }
2153 // Final choice it to pick a suitable chart based on current stack.
2154 else {
2155 // Build a temporary chart stack based on tLat, tLon
2156 ChartStack TempStack;
2157 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2158 m_groupIndex);
2159
2160 // Iterate over the quilt charts actually shown, looking for the
2161 // largest scale chart that will be in the new chartstack.... This
2162 // will (almost?) always be the reference chart....
2163
2164 ChartBase *Candidate_Chart = NULL;
2165 int cur_max_scale = (int)1e8;
2166
2167 ChartBase *pChart = GetFirstQuiltChart();
2168 while (pChart) {
2169 // Is this pChart in new stack?
2170 int tEntry =
2171 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2172 if (tEntry != -1) {
2173 if (pChart->GetNativeScale() < cur_max_scale) {
2174 Candidate_Chart = pChart;
2175 cur_max_scale = pChart->GetNativeScale();
2176 }
2177 }
2178 pChart = GetNextQuiltChart();
2179 }
2180
2181 m_singleChart = Candidate_Chart;
2182
2183 // If the quilt is empty, there is no "best" chart.
2184 // So, open the smallest scale chart in the current stack
2185 if (NULL == m_singleChart) {
2186 m_singleChart = ChartData->OpenStackChartConditional(
2187 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2188 CHART_FAMILY_DONTCARE);
2189 }
2190 }
2191 }
2192 // Invalidate all the charts in the quilt,
2193 // as any cached data may be region based and not have fullscreen coverage
2194 InvalidateAllQuiltPatchs();
2195
2196 if (m_singleChart) {
2197 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2198 std::vector<int> one_array;
2199 one_array.push_back(dbi);
2200 m_Piano->SetActiveKeyArray(one_array);
2201 }
2202
2203 if (m_singleChart) {
2204 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2205 }
2206 }
2207 }
2208 // Invalidate the current stack so that it will be rebuilt on next tick
2209 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2210 SetVPScale(GetVPScale() * 1.0001);
2211}
2212
2213bool ChartCanvas::IsTempMenuBarEnabled() {
2214#ifdef __WXMSW__
2215 int major;
2216 wxGetOsVersion(&major);
2217 return (major >
2218 5); // For Windows, function is only available on Vista and above
2219#else
2220 return true;
2221#endif
2222}
2223
2224double ChartCanvas::GetCanvasRangeMeters() {
2225 int width, height;
2226 GetSize(&width, &height);
2227 int minDimension = wxMin(width, height);
2228
2229 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2230 range *= cos(GetVP().clat * PI / 180.);
2231 return range;
2232}
2233
2234void ChartCanvas::SetCanvasRangeMeters(double range) {
2235 int width, height;
2236 GetSize(&width, &height);
2237 int minDimension = wxMin(width, height);
2238
2239 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2240 SetVPScale(scale_ppm / 2);
2241}
2242
2243bool ChartCanvas::SetUserOwnship() {
2244 // Look for user defined ownship image
2245 // This may be found in the shared data location along with other user
2246 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2247 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2248 double factor_dusk = 0.5;
2249 double factor_night = 0.25;
2250
2251 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2252 m_pos_image_user_day = new wxImage;
2253 *m_pos_image_user_day = pbmp->ConvertToImage();
2254 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2255
2256 int gimg_width = m_pos_image_user_day->GetWidth();
2257 int gimg_height = m_pos_image_user_day->GetHeight();
2258
2259 // Make dusk and night images
2260 m_pos_image_user_dusk = new wxImage;
2261 m_pos_image_user_night = new wxImage;
2262
2263 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2264 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2265
2266 for (int iy = 0; iy < gimg_height; iy++) {
2267 for (int ix = 0; ix < gimg_width; ix++) {
2268 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2269 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2270 m_pos_image_user_day->GetGreen(ix, iy),
2271 m_pos_image_user_day->GetBlue(ix, iy));
2272 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2273 hsv.value = hsv.value * factor_dusk;
2274 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2275 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2276 nrgb.blue);
2277
2278 hsv = wxImage::RGBtoHSV(rgb);
2279 hsv.value = hsv.value * factor_night;
2280 nrgb = wxImage::HSVtoRGB(hsv);
2281 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2282 nrgb.blue);
2283 }
2284 }
2285 }
2286
2287 // Make some alternate greyed out day/dusk/night images
2288 m_pos_image_user_grey_day = new wxImage;
2289 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2290
2291 m_pos_image_user_grey_dusk = new wxImage;
2292 m_pos_image_user_grey_night = new wxImage;
2293
2294 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2295 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2296
2297 for (int iy = 0; iy < gimg_height; iy++) {
2298 for (int ix = 0; ix < gimg_width; ix++) {
2299 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2300 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2301 m_pos_image_user_grey_day->GetGreen(ix, iy),
2302 m_pos_image_user_grey_day->GetBlue(ix, iy));
2303 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2304 hsv.value = hsv.value * factor_dusk;
2305 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2306 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2307 nrgb.blue);
2308
2309 hsv = wxImage::RGBtoHSV(rgb);
2310 hsv.value = hsv.value * factor_night;
2311 nrgb = wxImage::HSVtoRGB(hsv);
2312 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2313 nrgb.blue);
2314 }
2315 }
2316 }
2317
2318 // Make a yellow image for rendering under low accuracy chart conditions
2319 m_pos_image_user_yellow_day = new wxImage;
2320 m_pos_image_user_yellow_dusk = new wxImage;
2321 m_pos_image_user_yellow_night = new wxImage;
2322
2323 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2324 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2325 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2326
2327 for (int iy = 0; iy < gimg_height; iy++) {
2328 for (int ix = 0; ix < gimg_width; ix++) {
2329 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2330 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2331 m_pos_image_user_grey_day->GetGreen(ix, iy),
2332 m_pos_image_user_grey_day->GetBlue(ix, iy));
2333
2334 // Simply remove all "blue" from the greyscaled image...
2335 // so, what is not black becomes yellow.
2336 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2337 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2338 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2339
2340 hsv = wxImage::RGBtoHSV(rgb);
2341 hsv.value = hsv.value * factor_dusk;
2342 nrgb = wxImage::HSVtoRGB(hsv);
2343 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2344
2345 hsv = wxImage::RGBtoHSV(rgb);
2346 hsv.value = hsv.value * factor_night;
2347 nrgb = wxImage::HSVtoRGB(hsv);
2348 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2349 0);
2350 }
2351 }
2352 }
2353
2354 return true;
2355 } else
2356 return false;
2357}
2358
2360 m_display_size_mm = size;
2361
2362 // int sx, sy;
2363 // wxDisplaySize( &sx, &sy );
2364
2365 // Calculate logical pixels per mm for later reference.
2366 wxSize sd = g_Platform->getDisplaySize();
2367 double horizontal = sd.x;
2368 // Set DPI (Win) scale factor
2369 g_scaler = g_Platform->GetDisplayDIPMult(this);
2370
2371 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2372 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2373
2374 if (ps52plib) {
2375 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2376 ps52plib->SetPPMM(m_pix_per_mm);
2377 }
2378
2379 wxString msg;
2380 msg.Printf(
2381 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2382 "%d:%d ",
2383 m_display_size_mm, sd.x, sd.y);
2384 wxLogDebug(msg);
2385
2386 int ssx, ssy;
2387 ssx = g_monitor_info[g_current_monitor].width;
2388 ssy = g_monitor_info[g_current_monitor].height;
2389 msg.Printf("monitor size: %d %d", ssx, ssy);
2390 wxLogDebug(msg);
2391
2392 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2393}
2394#if 0
2395void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2396{
2397 wxString msg(event.m_string.c_str(), wxConvUTF8);
2398 // if cpus are removed between runs
2399 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2400 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2401 }
2402
2403 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2404 {
2405 compress_msg_array.RemoveAt(event.thread);
2406 compress_msg_array.Insert( msg, event.thread);
2407 }
2408 else
2409 compress_msg_array.Add(msg);
2410
2411
2412 wxString combined_msg;
2413 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2414 combined_msg += compress_msg_array[i];
2415 combined_msg += "\n";
2416 }
2417
2418 bool skip = false;
2419 pprog->Update(pprog_count, combined_msg, &skip );
2420 pprog->SetSize(pprog_size);
2421 if(skip)
2422 b_skipout = skip;
2423}
2424#endif
2425void ChartCanvas::InvalidateGL() {
2426 if (!m_glcc) return;
2427#ifdef ocpnUSE_GL
2428 if (g_bopengl) m_glcc->Invalidate();
2429#endif
2430 if (m_Compass) m_Compass->UpdateStatus(true);
2431}
2432
2433int ChartCanvas::GetCanvasChartNativeScale() {
2434 int ret = 1;
2435 if (!VPoint.b_quilt) {
2436 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2437 } else
2438 ret = (int)m_pQuilt->GetRefNativeScale();
2439
2440 return ret;
2441}
2442
2443ChartBase *ChartCanvas::GetChartAtCursor() {
2444 ChartBase *target_chart;
2445 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2446 target_chart = m_singleChart;
2447 else if (VPoint.b_quilt)
2448 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2449 else
2450 target_chart = NULL;
2451 return target_chart;
2452}
2453
2454ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2455 ChartBase *target_chart;
2456 if (VPoint.b_quilt)
2457 target_chart =
2458 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2459 else
2460 target_chart = NULL;
2461 return target_chart;
2462}
2463
2464int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2465 int new_dbIndex = -1;
2466 if (!VPoint.b_quilt) {
2467 if (m_pCurrentStack) {
2468 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2469 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2470 if (sc >= scale) {
2471 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2472 break;
2473 }
2474 }
2475 }
2476 } else {
2477 // Using the current quilt, select a useable reference chart
2478 // Said chart will be in the extended (possibly full-screen) stack,
2479 // And will have a scale equal to or just greater than the stipulated
2480 // value
2481 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2482 if (im > 0) {
2483 for (unsigned int is = 0; is < im; is++) {
2484 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2485 m_pQuilt->GetExtendedStackIndexArray()[is]);
2486 if ((m.Scale_ge(
2487 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2488 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2489 break;
2490 }
2491 }
2492 }
2493 }
2494
2495 return new_dbIndex;
2496}
2497
2498void ChartCanvas::EnablePaint(bool b_enable) {
2499 m_b_paint_enable = b_enable;
2500#ifdef ocpnUSE_GL
2501 if (m_glcc) m_glcc->EnablePaint(b_enable);
2502#endif
2503}
2504
2505bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2506
2507void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2508
2509std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2510 return m_pQuilt->GetQuiltIndexArray();
2511 ;
2512}
2513
2514void ChartCanvas::SetQuiltMode(bool b_quilt) {
2515 VPoint.b_quilt = b_quilt;
2516 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2517}
2518
2519bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2520
2521int ChartCanvas::GetQuiltReferenceChartIndex() {
2522 return m_pQuilt->GetRefChartdbIndex();
2523}
2524
2525void ChartCanvas::InvalidateAllQuiltPatchs() {
2526 m_pQuilt->InvalidateAllQuiltPatchs();
2527}
2528
2529ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2530 return m_pQuilt->GetLargestScaleChart();
2531}
2532
2533ChartBase *ChartCanvas::GetFirstQuiltChart() {
2534 return m_pQuilt->GetFirstChart();
2535}
2536
2537ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2538
2539int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2540
2541void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2542 m_pQuilt->SetHiliteIndex(dbIndex);
2543}
2544
2545void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2546 m_pQuilt->SetHiliteIndexArray(hilite_array);
2547}
2548
2549void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2550 m_pQuilt->ClearHiliteIndexArray();
2551}
2552
2553std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2554 bool flag2) {
2555 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2556}
2557
2558int ChartCanvas::GetQuiltRefChartdbIndex() {
2559 return m_pQuilt->GetRefChartdbIndex();
2560}
2561
2562std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2563 return m_pQuilt->GetExtendedStackIndexArray();
2564}
2565
2566std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2567 return m_pQuilt->GetFullscreenIndexArray();
2568}
2569
2570std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2571 return m_pQuilt->GetEclipsedStackIndexArray();
2572}
2573
2574void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2575
2576double ChartCanvas::GetQuiltMaxErrorFactor() {
2577 return m_pQuilt->GetMaxErrorFactor();
2578}
2579
2580bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2581 return m_pQuilt->IsChartQuiltableRef(db_index);
2582}
2583
2584bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2585 double chartMaxScale =
2586 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2587 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2588}
2589
2590void ChartCanvas::StartMeasureRoute() {
2591 if (!m_routeState) { // no measure tool if currently creating route
2592 if (m_bMeasure_Active) {
2593 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2594 m_pMeasureRoute = NULL;
2595 }
2596
2597 m_bMeasure_Active = true;
2598 m_nMeasureState = 1;
2599 m_bDrawingRoute = false;
2600
2601 SetCursor(*pCursorPencil);
2602 Refresh();
2603 }
2604}
2605
2606void ChartCanvas::CancelMeasureRoute() {
2607 m_bMeasure_Active = false;
2608 m_nMeasureState = 0;
2609 m_bDrawingRoute = false;
2610
2611 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2612 m_pMeasureRoute = NULL;
2613
2614 SetCursor(*pCursorArrow);
2615}
2616
2617ViewPort &ChartCanvas::GetVP() { return VPoint; }
2618
2619void ChartCanvas::SetVP(ViewPort &vp) {
2620 VPoint = vp;
2621 VPoint.SetPixelScale(m_displayScale);
2622}
2623
2624// void ChartCanvas::SetFocus()
2625// {
2626// printf("set %d\n", m_canvasIndex);
2627// //wxWindow:SetFocus();
2628// }
2629
2630void ChartCanvas::TriggerDeferredFocus() {
2631 // #if defined(__WXGTK__) || defined(__WXOSX__)
2632
2633 m_deferredFocusTimer.Start(20, true);
2634
2635#if defined(__WXGTK__) || defined(__WXOSX__)
2636 top_frame::Get()->Raise();
2637#endif
2638
2639 // top_frame::Get()->Raise();
2640 // #else
2641 // SetFocus();
2642 // Refresh(true);
2643 // #endif
2644}
2645
2646void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2647 SetFocus();
2648 Refresh(true);
2649}
2650
2651void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2652 if (SendKeyEventToPlugins(event))
2653 return; // PlugIn did something, and does not want the canvas to do
2654 // anything else
2655
2656 int key_char = event.GetKeyCode();
2657 switch (key_char) {
2658 case '?':
2659 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2660 break;
2661 case '+':
2662 ZoomCanvas(g_plus_minus_zoom_factor, false);
2663 break;
2664 case '-':
2665 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2666 break;
2667 default:
2668 break;
2669 }
2670 if (g_benable_rotate) {
2671 switch (key_char) {
2672 case ']':
2673 RotateCanvas(1);
2674 Refresh();
2675 break;
2676
2677 case '[':
2678 RotateCanvas(-1);
2679 Refresh();
2680 break;
2681
2682 case '\\':
2683 DoRotateCanvas(0);
2684 break;
2685 }
2686 }
2687
2688 event.Skip();
2689}
2690
2691void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2692 if (SendKeyEventToPlugins(event))
2693 return; // PlugIn did something, and does not want the canvas to do
2694 // anything else
2695
2696 bool b_handled = false;
2697
2698 m_modkeys = event.GetModifiers();
2699
2700 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2701
2702#ifdef OCPN_ALT_MENUBAR
2703#ifndef __WXOSX__
2704 // If the permanent menubar is disabled, we show it temporarily when Alt is
2705 // pressed or when Alt + a letter is presssed (for the top-menu-level
2706 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2707 // some special cases.
2708 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2709 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2710 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2711 if (!g_bTempShowMenuBar) {
2712 g_bTempShowMenuBar = true;
2713 top_frame::Get()->ApplyGlobalSettings(false);
2714 }
2715 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2716 event.Skip();
2717 return;
2718 }
2719 // If another key is pressed while Alt is down, do NOT toggle the menus when
2720 // Alt is released
2721 if (event.GetKeyCode() != WXK_ALT) {
2722 m_bMayToggleMenuBar = false;
2723 }
2724 }
2725#endif
2726#endif
2727
2728 // HOTKEYS
2729 switch (event.GetKeyCode()) {
2730 case WXK_TAB:
2731 // parent_frame->SwitchKBFocus( this );
2732 break;
2733
2734 case WXK_MENU:
2735 int x, y;
2736 event.GetPosition(&x, &y);
2737 m_FinishRouteOnKillFocus = false;
2738 CallPopupMenu(x, y);
2739 m_FinishRouteOnKillFocus = true;
2740 break;
2741
2742 case WXK_ALT:
2743 m_modkeys |= wxMOD_ALT;
2744 break;
2745
2746 case WXK_CONTROL:
2747 m_modkeys |= wxMOD_CONTROL;
2748 break;
2749
2750#ifdef __WXOSX__
2751 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2752 case WXK_RAW_CONTROL:
2753 m_modkeys |= wxMOD_RAW_CONTROL;
2754 break;
2755#endif
2756
2757 case WXK_LEFT:
2758 if (m_modkeys == wxMOD_CONTROL)
2759 top_frame::Get()->DoStackDown(this);
2760 else if (g_bsmoothpanzoom) {
2761 StartTimedMovement();
2762 m_panx = -1;
2763 } else {
2764 PanCanvas(-panspeed, 0);
2765 }
2766 b_handled = true;
2767 break;
2768
2769 case WXK_UP:
2770 if (g_bsmoothpanzoom) {
2771 StartTimedMovement();
2772 m_pany = -1;
2773 } else
2774 PanCanvas(0, -panspeed);
2775 b_handled = true;
2776 break;
2777
2778 case WXK_RIGHT:
2779 if (m_modkeys == wxMOD_CONTROL)
2780 top_frame::Get()->DoStackUp(this);
2781 else if (g_bsmoothpanzoom) {
2782 StartTimedMovement();
2783 m_panx = 1;
2784 } else
2785 PanCanvas(panspeed, 0);
2786 b_handled = true;
2787
2788 break;
2789
2790 case WXK_DOWN:
2791 if (g_bsmoothpanzoom) {
2792 StartTimedMovement();
2793 m_pany = 1;
2794 } else
2795 PanCanvas(0, panspeed);
2796 b_handled = true;
2797 break;
2798
2799 case WXK_F2: {
2800 if (event.ShiftDown()) {
2801 double scale = GetVP().view_scale_ppm;
2802 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2803
2804 std::shared_ptr<HostApi> host_api;
2805 host_api = GetHostApi();
2806 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2807
2808 if (api_121)
2809 api_121->SelectChartFamily(m_canvasIndex,
2810 (ChartFamilyEnumPI)target_family);
2811 } else
2812 TogglebFollow();
2813 break;
2814 }
2815 case WXK_F3: {
2816 if (event.ShiftDown()) {
2817 double scale = GetVP().view_scale_ppm;
2818 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2819
2820 std::shared_ptr<HostApi> host_api;
2821 host_api = GetHostApi();
2822 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2823
2824 if (api_121)
2825 api_121->SelectChartFamily(m_canvasIndex,
2826 (ChartFamilyEnumPI)target_family);
2827 } else {
2828 SetShowENCText(!GetShowENCText());
2829 Refresh(true);
2830 InvalidateGL();
2831 }
2832 break;
2833 }
2834 case WXK_F4:
2835 if (!m_bMeasure_Active) {
2836 if (event.ShiftDown())
2837 m_bMeasure_DistCircle = true;
2838 else
2839 m_bMeasure_DistCircle = false;
2840
2841 StartMeasureRoute();
2842 } else {
2843 CancelMeasureRoute();
2844
2845 SetCursor(*pCursorArrow);
2846
2847 // SurfaceToolbar();
2848 InvalidateGL();
2849 Refresh(false);
2850 }
2851
2852 break;
2853
2854 case WXK_F5:
2855 top_frame::Get()->ToggleColorScheme();
2856 top_frame::Get()->Raise();
2857 TriggerDeferredFocus();
2858 break;
2859
2860 case WXK_F6: {
2861 int mod = m_modkeys & wxMOD_SHIFT;
2862 if (mod != m_brightmod) {
2863 m_brightmod = mod;
2864 m_bbrightdir = !m_bbrightdir;
2865 }
2866
2867 if (!m_bbrightdir) {
2868 g_nbrightness -= 10;
2869 if (g_nbrightness <= MIN_BRIGHT) {
2870 g_nbrightness = MIN_BRIGHT;
2871 m_bbrightdir = true;
2872 }
2873 } else {
2874 g_nbrightness += 10;
2875 if (g_nbrightness >= MAX_BRIGHT) {
2876 g_nbrightness = MAX_BRIGHT;
2877 m_bbrightdir = false;
2878 }
2879 }
2880
2881 SetScreenBrightness(g_nbrightness);
2882 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2883
2884 SetFocus(); // just in case the external program steals it....
2885 top_frame::Get()->Raise(); // And reactivate the application main
2886
2887 break;
2888 }
2889
2890 case WXK_F7:
2891 top_frame::Get()->DoStackDown(this);
2892 break;
2893
2894 case WXK_F8:
2895 top_frame::Get()->DoStackUp(this);
2896 break;
2897
2898#ifndef __WXOSX__
2899 case WXK_F9: {
2900 ToggleCanvasQuiltMode();
2901 break;
2902 }
2903#endif
2904
2905 case WXK_F11:
2906 top_frame::Get()->ToggleFullScreen();
2907 b_handled = true;
2908 break;
2909
2910 case WXK_F12: {
2911 if (m_modkeys == wxMOD_ALT) {
2912 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2913 } else {
2914 ToggleChartOutlines();
2915 }
2916 break;
2917 }
2918
2919 case WXK_PAUSE: // Drop MOB
2920 top_frame::Get()->ActivateMOB();
2921 break;
2922
2923 // NUMERIC PAD
2924 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2925 case WXK_PAGEUP: {
2926 ZoomCanvas(g_plus_minus_zoom_factor, false);
2927 break;
2928 }
2929 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2930 case WXK_PAGEDOWN: {
2931 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2932 break;
2933 }
2934 case WXK_DELETE:
2935 case WXK_BACK:
2936 if (m_bMeasure_Active) {
2937 if (m_nMeasureState > 2) {
2938 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2939 m_pMeasureRoute->m_lastMousePointIndex =
2940 m_pMeasureRoute->GetnPoints();
2941 m_nMeasureState--;
2942 top_frame::Get()->RefreshAllCanvas();
2943 } else {
2944 CancelMeasureRoute();
2945 StartMeasureRoute();
2946 }
2947 }
2948 break;
2949 default:
2950 break;
2951 }
2952
2953 if (event.GetKeyCode() < 128) // ascii
2954 {
2955 int key_char = event.GetKeyCode();
2956
2957 // Handle both QWERTY and AZERTY keyboard separately for a few control
2958 // codes
2959 if (!g_b_assume_azerty) {
2960#ifdef __WXMAC__
2961 if (g_benable_rotate) {
2962 switch (key_char) {
2963 // On other platforms these are handled in OnKeyChar, which
2964 // (apparently) works better in some locales. On OS X it is better
2965 // to handle them here, since pressing Alt (which should change the
2966 // rotation speed) changes the key char and so prevents the keys
2967 // from working.
2968 case ']':
2969 RotateCanvas(1);
2970 b_handled = true;
2971 break;
2972
2973 case '[':
2974 RotateCanvas(-1);
2975 b_handled = true;
2976 break;
2977
2978 case '\\':
2979 DoRotateCanvas(0);
2980 b_handled = true;
2981 break;
2982 }
2983 }
2984#endif
2985 } else { // AZERTY
2986 switch (key_char) {
2987 case 43:
2988 ZoomCanvas(g_plus_minus_zoom_factor, false);
2989 break;
2990
2991 case 54: // '-' alpha/num pad
2992 // case 56: // '_' alpha/num pad
2993 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2994 break;
2995 }
2996 }
2997
2998#ifdef __WXOSX__
2999 // Ctrl+Cmd+F toggles fullscreen on macOS
3000 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3001 m_modkeys & wxMOD_RAW_CONTROL) {
3002 top_frame::Get()->ToggleFullScreen();
3003 return;
3004 }
3005#endif
3006
3007 if (event.ControlDown()) key_char -= 64;
3008
3009 if (key_char >= '0' && key_char <= '9')
3010 SetGroupIndex(key_char - '0');
3011 else
3012
3013 switch (key_char) {
3014 case 'A':
3015 SetShowENCAnchor(!GetShowENCAnchor());
3016 ReloadVP();
3017
3018 break;
3019
3020 case 'C':
3021 top_frame::Get()->ToggleColorScheme();
3022 break;
3023
3024 case 'D': {
3025 int x, y;
3026 event.GetPosition(&x, &y);
3027 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3028 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3029 // First find out what kind of chart is being used
3030 if (!pPopupDetailSlider) {
3031 if (VPoint.b_quilt) {
3032 if (m_pQuilt) {
3033 if (m_pQuilt->GetChartAtPix(
3034 VPoint,
3035 wxPoint(
3036 x, y))) // = null if no chart loaded for this point
3037 {
3038 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3039 ->GetChartType();
3040 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3041 ->GetChartFamily();
3042 }
3043 }
3044 } else {
3045 if (m_singleChart) {
3046 ChartType = m_singleChart->GetChartType();
3047 ChartFam = m_singleChart->GetChartFamily();
3048 }
3049 }
3050 // If a charttype is found show the popupslider
3051 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3052 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3054 this, -1, ChartType, ChartFam,
3055 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3056 wxDefaultSize, wxSIMPLE_BORDER, "");
3058 }
3059 } else //( !pPopupDetailSlider ) close popupslider
3060 {
3062 pPopupDetailSlider = NULL;
3063 }
3064 break;
3065 }
3066
3067 case 'E':
3068 m_nmea_log->Show();
3069 m_nmea_log->Raise();
3070 break;
3071
3072 case 'L':
3073 SetShowENCLights(!GetShowENCLights());
3074 ReloadVP();
3075
3076 break;
3077
3078 case 'M':
3079 if (event.ShiftDown())
3080 m_bMeasure_DistCircle = true;
3081 else
3082 m_bMeasure_DistCircle = false;
3083
3084 StartMeasureRoute();
3085 break;
3086
3087 case 'N':
3088 if (g_bInlandEcdis && ps52plib) {
3089 SetENCDisplayCategory((_DisCat)STANDARD);
3090 }
3091 break;
3092
3093 case 'O':
3094 ToggleChartOutlines();
3095 break;
3096
3097 case 'Q':
3098 ToggleCanvasQuiltMode();
3099 break;
3100
3101 case 'P':
3102 top_frame::Get()->ToggleTestPause();
3103 break;
3104 case 'R':
3105 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3106 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3107 g_iNavAidRadarRingsNumberVisible = 1;
3108 else if (!g_bNavAidRadarRingsShown &&
3109 g_iNavAidRadarRingsNumberVisible == 1)
3110 g_iNavAidRadarRingsNumberVisible = 0;
3111 break;
3112 case 'S':
3113 SetShowENCDepth(!m_encShowDepth);
3114 ReloadVP();
3115 break;
3116
3117 case 'T':
3118 SetShowENCText(!GetShowENCText());
3119 ReloadVP();
3120 break;
3121
3122 case 'U':
3123 SetShowENCDataQual(!GetShowENCDataQual());
3124 ReloadVP();
3125 break;
3126
3127 case 'V':
3128 m_bShowNavobjects = !m_bShowNavobjects;
3129 Refresh(true);
3130 break;
3131
3132 case 'W': // W Toggle CPA alarm
3133 ToggleCPAWarn();
3134
3135 break;
3136
3137 case 1: // Ctrl A
3138 TogglebFollow();
3139
3140 break;
3141
3142 case 2: // Ctrl B
3143 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3144 break;
3145
3146 case 13: // Ctrl M // Drop Marker at cursor
3147 {
3148 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3149 break;
3150 }
3151
3152 case 14: // Ctrl N - Activate next waypoint in a route
3153 {
3154 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3155 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3156 if ((indexActive + 1) <= r->GetnPoints()) {
3158 InvalidateGL();
3159 Refresh(false);
3160 }
3161 }
3162 break;
3163 }
3164
3165 case 15: // Ctrl O - Drop Marker at boat's position
3166 {
3167 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3168 break;
3169 }
3170
3171 case 32: // Special needs use space bar
3172 {
3173 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3174 break;
3175 }
3176
3177 case -32: // Ctrl Space // Drop MOB
3178 {
3179 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3180
3181 break;
3182 }
3183
3184 case -20: // Ctrl ,
3185 {
3186 top_frame::Get()->DoSettings();
3187 break;
3188 }
3189 case 17: // Ctrl Q
3190 parent_frame->Close();
3191 return;
3192
3193 case 18: // Ctrl R
3194 StartRoute();
3195 return;
3196
3197 case 20: // Ctrl T
3198 if (NULL == pGoToPositionDialog) // There is one global instance of
3199 // the Go To Position Dialog
3201 pGoToPositionDialog->SetCanvas(this);
3202 pGoToPositionDialog->Show();
3203 break;
3204
3205 case 25: // Ctrl Y
3206 if (undo->AnythingToRedo()) {
3207 undo->RedoNextAction();
3208 InvalidateGL();
3209 Refresh(false);
3210 }
3211 break;
3212
3213 case 26:
3214 if (event.ShiftDown()) { // Shift-Ctrl-Z
3215 if (undo->AnythingToRedo()) {
3216 undo->RedoNextAction();
3217 InvalidateGL();
3218 Refresh(false);
3219 }
3220 } else { // Ctrl Z
3221 if (undo->AnythingToUndo()) {
3222 undo->UndoLastAction();
3223 InvalidateGL();
3224 Refresh(false);
3225 }
3226 }
3227 break;
3228
3229 case 27:
3230 // Generic break
3231 if (m_bMeasure_Active) {
3232 CancelMeasureRoute();
3233
3234 SetCursor(*pCursorArrow);
3235
3236 // SurfaceToolbar();
3237 top_frame::Get()->RefreshAllCanvas();
3238 }
3239
3240 if (m_routeState) // creating route?
3241 {
3242 FinishRoute();
3243 // SurfaceToolbar();
3244 InvalidateGL();
3245 Refresh(false);
3246 }
3247
3248 break;
3249
3250 case 7: // Ctrl G
3251 switch (gamma_state) {
3252 case (0):
3253 r_gamma_mult = 0;
3254 g_gamma_mult = 1;
3255 b_gamma_mult = 0;
3256 gamma_state = 1;
3257 break;
3258 case (1):
3259 r_gamma_mult = 1;
3260 g_gamma_mult = 0;
3261 b_gamma_mult = 0;
3262 gamma_state = 2;
3263 break;
3264 case (2):
3265 r_gamma_mult = 1;
3266 g_gamma_mult = 1;
3267 b_gamma_mult = 1;
3268 gamma_state = 0;
3269 break;
3270 }
3271 SetScreenBrightness(g_nbrightness);
3272
3273 break;
3274
3275 case 9: // Ctrl I
3276 if (event.ControlDown()) {
3277 m_bShowCompassWin = !m_bShowCompassWin;
3278 SetShowGPSCompassWindow(m_bShowCompassWin);
3279 Refresh(false);
3280 }
3281 break;
3282
3283 default:
3284 break;
3285
3286 } // switch
3287 }
3288
3289 // Allow OnKeyChar to catch the key events too.
3290 if (!b_handled) {
3291 event.Skip();
3292 }
3293}
3294
3295void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3296 if (SendKeyEventToPlugins(event))
3297 return; // PlugIn did something, and does not want the canvas to do
3298 // anything else
3299
3300 switch (event.GetKeyCode()) {
3301 case WXK_TAB:
3302 top_frame::Get()->SwitchKBFocus(this);
3303 break;
3304
3305 case WXK_LEFT:
3306 case WXK_RIGHT:
3307 m_panx = 0;
3308 if (!m_pany) m_panspeed = 0;
3309 break;
3310
3311 case WXK_UP:
3312 case WXK_DOWN:
3313 m_pany = 0;
3314 if (!m_panx) m_panspeed = 0;
3315 break;
3316
3317 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3318 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3319 case WXK_PAGEUP:
3320 case WXK_PAGEDOWN:
3321 if (m_mustmove) DoMovement(m_mustmove);
3322
3323 m_zoom_factor = 1;
3324 break;
3325
3326 case WXK_ALT:
3327 m_modkeys &= ~wxMOD_ALT;
3328#ifdef OCPN_ALT_MENUBAR
3329#ifndef __WXOSX__
3330 // If the permanent menu bar is disabled, and we are not in the middle of
3331 // another key combo, then show the menu bar temporarily when Alt is
3332 // released (or hide it if already visible).
3333 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3334 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3335 top_frame::Get()->ApplyGlobalSettings(false);
3336 }
3337 m_bMayToggleMenuBar = true;
3338#endif
3339#endif
3340 break;
3341
3342 case WXK_CONTROL:
3343 m_modkeys &= ~wxMOD_CONTROL;
3344 break;
3345 }
3346
3347 if (event.GetKeyCode() < 128) // ascii
3348 {
3349 int key_char = event.GetKeyCode();
3350
3351 // Handle both QWERTY and AZERTY keyboard separately for a few control
3352 // codes
3353 if (!g_b_assume_azerty) {
3354 switch (key_char) {
3355 case '+':
3356 case '=':
3357 case '-':
3358 case '_':
3359 case 54:
3360 case 56: // '_' alpha/num pad
3361 DoMovement(m_mustmove);
3362
3363 // m_zoom_factor = 1;
3364 break;
3365 case '[':
3366 case ']':
3367 DoMovement(m_mustmove);
3368 m_rotation_speed = 0;
3369 break;
3370 }
3371 } else {
3372 switch (key_char) {
3373 case 43:
3374 case 54: // '-' alpha/num pad
3375 case 56: // '_' alpha/num pad
3376 DoMovement(m_mustmove);
3377
3378 m_zoom_factor = 1;
3379 break;
3380 }
3381 }
3382 }
3383 event.Skip();
3384}
3385
3386void ChartCanvas::ToggleChartOutlines() {
3387 m_bShowOutlines = !m_bShowOutlines;
3388
3389 Refresh(false);
3390
3391#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3392 // needs a full refresh
3393 if (g_bopengl) InvalidateGL();
3394#endif
3395}
3396
3397void ChartCanvas::ToggleLookahead() {
3398 m_bLookAhead = !m_bLookAhead;
3399 m_OSoffsetx = 0; // center ownship
3400 m_OSoffsety = 0;
3401}
3402
3403void ChartCanvas::SetUpMode(int mode) {
3404 m_upMode = mode;
3405
3406 if (mode != NORTH_UP_MODE) {
3407 // Stuff the COGAvg table in case COGUp is selected
3408 double stuff = 0;
3409 if (!std::isnan(gCog)) stuff = gCog;
3410
3411 if (g_COGAvgSec > 0) {
3412 auto cog_table = top_frame::Get()->GetCOGTable();
3413 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3414 }
3415 g_COGAvg = stuff;
3416 top_frame::Get()->StartCogTimer();
3417 } else {
3418 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3419 SetVPRotation(GetVPSkew());
3420 else
3421 SetVPRotation(0); /* reset to north up */
3422 }
3423
3424 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3425 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3426
3427 UpdateGPSCompassStatusBox(true);
3428 top_frame::Get()->DoChartUpdate();
3429}
3430
3431bool ChartCanvas::DoCanvasCOGSet() {
3432 if (GetUpMode() == NORTH_UP_MODE) return false;
3433 double cog_use = g_COGAvg;
3434 if (g_btenhertz) cog_use = gCog;
3435
3436 double rotation = 0;
3437 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3438 rotation = -gHdt * PI / 180.;
3439 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3440 rotation = -cog_use * PI / 180.;
3441
3442 SetVPRotation(rotation);
3443 return true;
3444}
3445
3446double easeOutCubic(double t) {
3447 // Starts quickly and slows down toward the end
3448 return 1.0 - pow(1.0 - t, 3.0);
3449}
3450
3451void ChartCanvas::StartChartDragInertia() {
3452 m_bChartDragging = false;
3453
3454 // Set some parameters
3455 m_chart_drag_inertia_time = 750; // msec
3456 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3457 m_last_elapsed = 0;
3458
3459 // Calculate ending drag velocity
3460 size_t n_vel = 10;
3461 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3462 int xacc = 0;
3463 int yacc = 0;
3464 double tacc = 0;
3465 size_t length = m_drag_vec_t.size();
3466 for (size_t i = 0; i < n_vel; i++) {
3467 xacc += m_drag_vec_x.at(length - 1 - i);
3468 yacc += m_drag_vec_y.at(length - 1 - i);
3469 tacc += m_drag_vec_t.at(length - 1 - i);
3470 }
3471
3472 if (tacc == 0) return;
3473
3474 double drag_velocity_x = xacc / tacc;
3475 double drag_velocity_y = yacc / tacc;
3476 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3477 // drag_velocity_y);
3478
3479 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3480 // touch tap.
3481 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3482
3483 m_chart_drag_velocity_x = drag_velocity_x;
3484 m_chart_drag_velocity_y = drag_velocity_y;
3485
3486 m_chart_drag_inertia_active = true;
3487 // First callback as fast as possible.
3488 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3489}
3490
3491void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3492 if (!m_chart_drag_inertia_active) return;
3493 // Calculate time fraction from 0..1
3494 wxLongLong now = wxGetLocalTimeMillis();
3495 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3496 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3497 if (t > 1.0) t = 1.0;
3498 double e = 1.0 - easeOutCubic(t); // 0..1
3499
3500 double dx =
3501 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3502 double dy =
3503 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3504
3505 m_last_elapsed = elapsed;
3506
3507 // Ensure that target destination lies on whole-pixel boundary
3508 // This allows the render engine to use a faster FBO copy method for drawing
3509 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3510 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3511 double inertia_lat, inertia_lon;
3512 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3513 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3514 // Check if ownship has moved off-screen
3515 if (!IsOwnshipOnScreen()) {
3516 m_bFollow = false; // update the follow flag
3517 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3518 UpdateFollowButtonState();
3519 m_OSoffsetx = 0;
3520 m_OSoffsety = 0;
3521 } else {
3522 m_OSoffsetx += dx;
3523 m_OSoffsety -= dy;
3524 }
3525
3526 Refresh(false);
3527
3528 // Stop condition
3529 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3530 m_chart_drag_inertia_timer.Stop();
3531
3532 // Disable chart pan movement logic
3533 m_target_lat = GetVP().clat;
3534 m_target_lon = GetVP().clon;
3535 m_pan_drag.x = m_pan_drag.y = 0;
3536 m_panx = m_pany = 0;
3537 m_chart_drag_inertia_active = false;
3538 DoCanvasUpdate();
3539
3540 } else {
3541 int target_redraw_interval = 40; // msec
3542 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3543 }
3544}
3545
3546void ChartCanvas::StopMovement() {
3547 m_panx = m_pany = 0;
3548 m_panspeed = 0;
3549 m_zoom_factor = 1;
3550 m_rotation_speed = 0;
3551 m_mustmove = 0;
3552#if 0
3553#if !defined(__WXGTK__) && !defined(__WXQT__)
3554 SetFocus();
3555 top_frame::Get()->Raise();
3556#endif
3557#endif
3558}
3559
3560/* instead of integrating in timer callbacks
3561 (which do not always get called fast enough)
3562 we can perform the integration of movement
3563 at each render frame based on the time change */
3564bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3565 // Start/restart the stop movement timer
3566 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3567
3568 if (!pMovementTimer->IsRunning()) {
3569 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3570 }
3571
3572 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3573 // already moving, gets called again because of key-repeat event
3574 return false;
3575 }
3576
3577 m_last_movement_time = wxDateTime::UNow();
3578
3579 return true;
3580}
3581void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3582 int nstep) {
3583 // Save the target
3584 m_target_lat = target_lat;
3585 m_target_lon = target_lon;
3586
3587 // Save the start point
3588 m_start_lat = GetVP().clat;
3589 m_start_lon = GetVP().clon;
3590
3591 m_VPMovementTimer.Start(1, true); // oneshot
3592 m_timed_move_vp_active = true;
3593 m_stvpc = 0;
3594 m_timedVP_step = nstep;
3595}
3596
3597void ChartCanvas::DoTimedMovementVP() {
3598 if (!m_timed_move_vp_active) return; // not active
3599 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3600 StopMovement();
3601 return;
3602 }
3603 // Stop condition
3604 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3605 double d2 =
3606 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3607 d2 = pow(d2, 0.5);
3608
3609 if (d2 < one_pix) {
3610 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3611 StopMovementVP();
3612 return;
3613 }
3614
3615 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3616 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3617 // StopMovementVP();
3618 // return;
3619 // }
3620
3621 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3622 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3623
3624 m_run_lat = new_lat;
3625 m_run_lon = new_lon;
3626
3627 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3628}
3629
3630void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3631
3632void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3633
3634void ChartCanvas::StartTimedMovementTarget() {}
3635
3636void ChartCanvas::DoTimedMovementTarget() {}
3637
3638void ChartCanvas::StopMovementTarget() {}
3639int ntm;
3640
3641void ChartCanvas::DoTimedMovement() {
3642 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3643 !m_rotation_speed)
3644 return; /* not moving */
3645
3646 wxDateTime now = wxDateTime::UNow();
3647 long dt = 0;
3648 if (m_last_movement_time.IsValid())
3649 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3650
3651 m_last_movement_time = now;
3652
3653 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3654 dt = 500;
3655
3656 DoMovement(dt);
3657}
3658
3660 /* if we get here quickly assume 1ms so that some movement occurs */
3661 if (dt == 0) dt = 1;
3662
3663 m_mustmove -= dt;
3664 if (m_mustmove < 0) m_mustmove = 0;
3665
3666 if (!m_inPinch) { // this stops compound zoom/pan
3667 if (m_pan_drag.x || m_pan_drag.y) {
3668 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3669 m_pan_drag.x = m_pan_drag.y = 0;
3670 }
3671
3672 if (m_panx || m_pany) {
3673 const double slowpan = .1, maxpan = 2;
3674 if (m_modkeys == wxMOD_ALT)
3675 m_panspeed = slowpan;
3676 else {
3677 m_panspeed += (double)dt / 500; /* apply acceleration */
3678 m_panspeed = wxMin(maxpan, m_panspeed);
3679 }
3680 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3681 }
3682 }
3683 if (m_zoom_factor != 1) {
3684 double alpha = 400, beta = 1.5;
3685 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3686
3687 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3688
3689 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3690
3691 // Try to hit the zoom target exactly.
3692 // if(m_wheelzoom_stop_oneshot > 0)
3693 {
3694 if (zoom_factor > 1) {
3695 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3696 zoom_factor = VPoint.chart_scale / m_zoom_target;
3697 }
3698
3699 else if (zoom_factor < 1) {
3700 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3701 zoom_factor = VPoint.chart_scale / m_zoom_target;
3702 }
3703 }
3704
3705 if (fabs(zoom_factor - 1) > 1e-4) {
3706 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3707 } else {
3708 StopMovement();
3709 }
3710
3711 if (m_wheelzoom_stop_oneshot > 0) {
3712 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3713 m_wheelzoom_stop_oneshot = 0;
3714 StopMovement();
3715 }
3716
3717 // Don't overshoot the zoom target.
3718 if (zoom_factor > 1) {
3719 if (VPoint.chart_scale <= m_zoom_target) {
3720 m_wheelzoom_stop_oneshot = 0;
3721 StopMovement();
3722 }
3723 } else if (zoom_factor < 1) {
3724 if (VPoint.chart_scale >= m_zoom_target) {
3725 m_wheelzoom_stop_oneshot = 0;
3726 StopMovement();
3727 }
3728 }
3729 }
3730 }
3731
3732 if (m_rotation_speed) { /* in degrees per second */
3733 double speed = m_rotation_speed;
3734 if (m_modkeys == wxMOD_ALT) speed /= 10;
3735 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3736 }
3737}
3738
3739void ChartCanvas::SetColorScheme(ColorScheme cs) {
3740 SetAlertString("");
3741
3742 // Setup ownship image pointers
3743 switch (cs) {
3744 case GLOBAL_COLOR_SCHEME_DAY:
3745 m_pos_image_red = &m_os_image_red_day;
3746 m_pos_image_grey = &m_os_image_grey_day;
3747 m_pos_image_yellow = &m_os_image_yellow_day;
3748 m_pos_image_user = m_pos_image_user_day;
3749 m_pos_image_user_grey = m_pos_image_user_grey_day;
3750 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3751 m_cTideBitmap = m_bmTideDay;
3752 m_cCurrentBitmap = m_bmCurrentDay;
3753
3754 break;
3755 case GLOBAL_COLOR_SCHEME_DUSK:
3756 m_pos_image_red = &m_os_image_red_dusk;
3757 m_pos_image_grey = &m_os_image_grey_dusk;
3758 m_pos_image_yellow = &m_os_image_yellow_dusk;
3759 m_pos_image_user = m_pos_image_user_dusk;
3760 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3761 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3762 m_cTideBitmap = m_bmTideDusk;
3763 m_cCurrentBitmap = m_bmCurrentDusk;
3764 break;
3765 case GLOBAL_COLOR_SCHEME_NIGHT:
3766 m_pos_image_red = &m_os_image_red_night;
3767 m_pos_image_grey = &m_os_image_grey_night;
3768 m_pos_image_yellow = &m_os_image_yellow_night;
3769 m_pos_image_user = m_pos_image_user_night;
3770 m_pos_image_user_grey = m_pos_image_user_grey_night;
3771 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3772 m_cTideBitmap = m_bmTideNight;
3773 m_cCurrentBitmap = m_bmCurrentNight;
3774 break;
3775 default:
3776 m_pos_image_red = &m_os_image_red_day;
3777 m_pos_image_grey = &m_os_image_grey_day;
3778 m_pos_image_yellow = &m_os_image_yellow_day;
3779 m_pos_image_user = m_pos_image_user_day;
3780 m_pos_image_user_grey = m_pos_image_user_grey_day;
3781 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3782 m_cTideBitmap = m_bmTideDay;
3783 m_cCurrentBitmap = m_bmCurrentDay;
3784 break;
3785 }
3786
3787 CreateDepthUnitEmbossMaps(cs);
3788 CreateOZEmbossMapData(cs);
3789
3790 // Set up fog effect base color
3791 m_fog_color = wxColor(
3792 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3793 float dim = 1.0;
3794 switch (cs) {
3795 case GLOBAL_COLOR_SCHEME_DUSK:
3796 dim = 0.5;
3797 break;
3798 case GLOBAL_COLOR_SCHEME_NIGHT:
3799 dim = 0.25;
3800 break;
3801 default:
3802 break;
3803 }
3804 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3805 m_fog_color.Blue() * dim);
3806
3807 // Really dark
3808#if 0
3809 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3810 SetBackgroundColour( wxColour(0,0,0) );
3811
3812 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3813 }
3814 else{
3815 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3816#ifndef __WXMAC__
3817 SetBackgroundColour( wxNullColour );
3818#endif
3819 }
3820#endif
3821
3822 // UpdateToolbarColorScheme(cs);
3823
3824 m_Piano->SetColorScheme(cs);
3825
3826 m_Compass->SetColorScheme(cs);
3827
3828 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3829
3830 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3831
3832 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3833 if (m_notification_button) {
3834 m_notification_button->SetColorScheme(cs);
3835 }
3836
3837#ifdef ocpnUSE_GL
3838 if (g_bopengl && m_glcc) {
3839 m_glcc->SetColorScheme(cs);
3840 g_glTextureManager->ClearAllRasterTextures();
3841 // m_glcc->FlushFBO();
3842 }
3843#endif
3844 SetbTCUpdate(true); // force re-render of tide/current locators
3845 m_brepaint_piano = true;
3846
3847 ReloadVP();
3848
3849 m_cs = cs;
3850}
3851
3852wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3853 wxImage img = Bitmap.ConvertToImage();
3854 int sx = img.GetWidth();
3855 int sy = img.GetHeight();
3856
3857 wxImage new_img(img);
3858
3859 for (int i = 0; i < sx; i++) {
3860 for (int j = 0; j < sy; j++) {
3861 if (!img.IsTransparent(i, j)) {
3862 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3863 (unsigned char)(img.GetGreen(i, j) * factor),
3864 (unsigned char)(img.GetBlue(i, j) * factor));
3865 }
3866 }
3867 }
3868
3869 wxBitmap ret = wxBitmap(new_img);
3870
3871 return ret;
3872}
3873
3874void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3875 int max) {
3876 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3877 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3878
3879 if (!m_pBrightPopup) {
3880 // Calculate size
3881 int x, y;
3882 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3883
3884 m_pBrightPopup = new TimedPopupWin(this, 3);
3885
3886 m_pBrightPopup->SetSize(x, y);
3887 m_pBrightPopup->Move(120, 120);
3888 }
3889
3890 int bmpsx = m_pBrightPopup->GetSize().x;
3891 int bmpsy = m_pBrightPopup->GetSize().y;
3892
3893 wxBitmap bmp(bmpsx, bmpsx);
3894 wxMemoryDC mdc(bmp);
3895
3896 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3897 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3898 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3899 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3900 mdc.Clear();
3901
3902 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3903
3904 mdc.SetFont(*pfont);
3905 wxString val;
3906
3907 if (brightness == max)
3908 val = "MAX";
3909 else if (brightness == min)
3910 val = "MIN";
3911 else
3912 val.Printf("%3d", brightness);
3913
3914 mdc.DrawText(val, 0, 0);
3915
3916 mdc.SelectObject(wxNullBitmap);
3917
3918 m_pBrightPopup->SetBitmap(bmp);
3919 m_pBrightPopup->Show();
3920 m_pBrightPopup->Refresh();
3921}
3922
3923void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3924 m_b_rot_hidef = true;
3925 ReloadVP();
3926}
3927
3928void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3929 if (!g_bRollover) return;
3930
3931 bool b_need_refresh = false;
3932
3933 wxSize win_size = GetSize() * m_displayScale;
3934 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3935
3936 // Handle the AIS Rollover Window first
3937 bool showAISRollover = false;
3938 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3939 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3940 SelectItem *pFind = pSelectAIS->FindSelection(
3941 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3942 if (pFind) {
3943 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3944 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3945
3946 if (ptarget) {
3947 showAISRollover = true;
3948
3949 if (NULL == m_pAISRolloverWin) {
3950 m_pAISRolloverWin = new RolloverWin(this);
3951 m_pAISRolloverWin->IsActive(false);
3952 b_need_refresh = true;
3953 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3954 m_AISRollover_MMSI != FoundAIS_MMSI) {
3955 // Sometimes the mouse moves fast enough to get over a new AIS
3956 // target before the one-shot has fired to remove the old target.
3957 // Result: wrong target data is shown.
3958 // Detect this case,close the existing rollover ASAP, and restart
3959 // the timer.
3960 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3961 m_pAISRolloverWin->IsActive(false);
3962 m_AISRollover_MMSI = 0;
3963 Refresh();
3964 return;
3965 }
3966
3967 m_AISRollover_MMSI = FoundAIS_MMSI;
3968
3969 if (!m_pAISRolloverWin->IsActive()) {
3970 wxString s = ptarget->GetRolloverString();
3971 m_pAISRolloverWin->SetString(s);
3972
3973 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3974 AIS_ROLLOVER, win_size);
3975 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3976 m_pAISRolloverWin->IsActive(true);
3977 b_need_refresh = true;
3978 }
3979 }
3980 } else {
3981 m_AISRollover_MMSI = 0;
3982 showAISRollover = false;
3983 }
3984 }
3985
3986 // Maybe turn the rollover off
3987 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3988 m_pAISRolloverWin->IsActive(false);
3989 m_AISRollover_MMSI = 0;
3990 b_need_refresh = true;
3991 }
3992
3993 // Now the Route info rollover
3994 // Show the route segment info
3995 bool showRouteRollover = false;
3996
3997 if (NULL == m_pRolloverRouteSeg) {
3998 // Get a list of all selectable sgements, and search for the first
3999 // visible segment as the rollover target.
4000
4001 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4002 SelectableItemList SelList = pSelect->FindSelectionList(
4003 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4004 auto node = SelList.begin();
4005 while (node != SelList.end()) {
4006 SelectItem *pFindSel = *node;
4007
4008 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4009
4010 if (pr && pr->IsVisible()) {
4011 m_pRolloverRouteSeg = pFindSel;
4012 showRouteRollover = true;
4013
4014 if (NULL == m_pRouteRolloverWin) {
4015 m_pRouteRolloverWin = new RolloverWin(this, 10);
4016 m_pRouteRolloverWin->IsActive(false);
4017 }
4018
4019 if (!m_pRouteRolloverWin->IsActive()) {
4020 wxString s;
4021 RoutePoint *segShow_point_a =
4022 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4023 RoutePoint *segShow_point_b =
4024 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4025
4026 double brg, dist;
4027 DistanceBearingMercator(
4028 segShow_point_b->m_lat, segShow_point_b->m_lon,
4029 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4030
4031 if (!pr->m_bIsInLayer)
4032 s.Append(_("Route") + ": ");
4033 else
4034 s.Append(_("Layer Route: "));
4035
4036 if (pr->m_RouteNameString.IsEmpty())
4037 s.Append(_("(unnamed)"));
4038 else
4039 s.Append(pr->m_RouteNameString);
4040
4041 s << "\n"
4042 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4043 << "\n"
4044 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4045 << segShow_point_b->GetName() << "\n";
4046
4047 if (g_bShowTrue)
4048 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4049 (int)floor(brg + 0.5), 0x00B0);
4050 if (g_bShowMag) {
4051 double latAverage =
4052 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4053 double lonAverage =
4054 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4055 double varBrg =
4056 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4057
4058 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4059 (int)floor(varBrg + 0.5), 0x00B0);
4060 }
4061
4062 s << FormatDistanceAdaptive(dist);
4063
4064 // Compute and display cumulative distance from route start point to
4065 // current leg end point and RNG,TTG,ETA from ship to current leg end
4066 // point for active route
4067 double shiptoEndLeg = 0.;
4068 bool validActive = false;
4069 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4070 validActive = true;
4071
4072 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4073 auto node = pr->pRoutePointList->begin();
4074 RoutePoint *prp;
4075 float dist_to_endleg = 0;
4076 wxString t;
4077
4078 for (++node; node != pr->pRoutePointList->end(); ++node) {
4079 prp = *node;
4080 if (validActive)
4081 shiptoEndLeg += prp->m_seg_len;
4082 else if (prp->m_bIsActive)
4083 validActive = true;
4084 dist_to_endleg += prp->m_seg_len;
4085 if (prp->IsSame(segShow_point_a)) break;
4086 }
4087 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4088 }
4089 // write from ship to end selected leg point data if the route is
4090 // active
4091 if (validActive) {
4092 s << "\n"
4093 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4094 shiptoEndLeg +=
4096 ->GetCurrentRngToActivePoint(); // add distance from ship
4097 // to active point
4098 shiptoEndLeg +=
4099 segShow_point_b
4100 ->m_seg_len; // add the lenght of the selected leg
4101 s << FormatDistanceAdaptive(shiptoEndLeg);
4102 // ensure sog/cog are valid and vmg is positive to keep data
4103 // coherent
4104 double vmg = 0.;
4105 if (!std::isnan(gCog) && !std::isnan(gSog))
4106 vmg = gSog *
4107 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4108 PI / 180.);
4109 if (vmg > 0.) {
4110 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4111 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4112 s << " - "
4113 << wxString(ttg_sec > SECONDS_PER_DAY
4114 ? ttg_span.Format(_("%Dd %H:%M"))
4115 : ttg_span.Format(_("%H:%M")));
4116 wxDateTime dtnow, eta;
4117 eta = dtnow.SetToCurrent().Add(ttg_span);
4118 s << " - " << eta.Format("%b").Mid(0, 4)
4119 << eta.Format(" %d %H:%M");
4120 } else
4121 s << " ---- ----";
4122 }
4123 m_pRouteRolloverWin->SetString(s);
4124
4125 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4126 LEG_ROLLOVER, win_size);
4127 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4128 m_pRouteRolloverWin->IsActive(true);
4129 b_need_refresh = true;
4130 showRouteRollover = true;
4131 break;
4132 }
4133 } else {
4134 ++node;
4135 }
4136 }
4137 } else {
4138 // Is the cursor still in select radius, and not timed out?
4139 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4140 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4141 m_pRolloverRouteSeg))
4142 showRouteRollover = false;
4143 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4144 showRouteRollover = false;
4145 else
4146 showRouteRollover = true;
4147 }
4148
4149 // If currently creating a route, do not show this rollover window
4150 if (m_routeState) showRouteRollover = false;
4151
4152 // Similar for AIS target rollover window
4153 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4154 showRouteRollover = false;
4155
4156 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4157 !showRouteRollover) {
4158 m_pRouteRolloverWin->IsActive(false);
4159 m_pRolloverRouteSeg = NULL;
4160 m_pRouteRolloverWin->Destroy();
4161 m_pRouteRolloverWin = NULL;
4162 b_need_refresh = true;
4163 } else if (m_pRouteRolloverWin && showRouteRollover) {
4164 m_pRouteRolloverWin->IsActive(true);
4165 b_need_refresh = true;
4166 }
4167
4168 // Now the Track info rollover
4169 // Show the track segment info
4170 bool showTrackRollover = false;
4171
4172 if (NULL == m_pRolloverTrackSeg) {
4173 // Get a list of all selectable sgements, and search for the first
4174 // visible segment as the rollover target.
4175
4176 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4177 SelectableItemList SelList = pSelect->FindSelectionList(
4178 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4179
4180 auto node = SelList.begin();
4181 while (node != SelList.end()) {
4182 SelectItem *pFindSel = *node;
4183
4184 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4185
4186 if (pt && pt->IsVisible()) {
4187 m_pRolloverTrackSeg = pFindSel;
4188 showTrackRollover = true;
4189
4190 if (NULL == m_pTrackRolloverWin) {
4191 m_pTrackRolloverWin = new RolloverWin(this, 10);
4192 m_pTrackRolloverWin->IsActive(false);
4193 }
4194
4195 if (!m_pTrackRolloverWin->IsActive()) {
4196 wxString s;
4197 TrackPoint *segShow_point_a =
4198 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4199 TrackPoint *segShow_point_b =
4200 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4201
4202 double brg, dist;
4203 DistanceBearingMercator(
4204 segShow_point_b->m_lat, segShow_point_b->m_lon,
4205 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4206
4207 if (!pt->m_bIsInLayer)
4208 s.Append(_("Track") + ": ");
4209 else
4210 s.Append(_("Layer Track: "));
4211
4212 if (pt->GetName().IsEmpty())
4213 s.Append(_("(unnamed)"));
4214 else
4215 s.Append(pt->GetName());
4216 double tlenght = pt->Length();
4217 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4218 if (pt->GetLastPoint()->GetTimeString() &&
4219 pt->GetPoint(0)->GetTimeString()) {
4220 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4221 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4222 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4223 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4224 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4225 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4226 << getUsrSpeedUnit();
4227 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4228 : ttime.Format(" %H:%M"));
4229 }
4230 }
4231
4232 if (g_bShowTrackPointTime &&
4233 strlen(segShow_point_b->GetTimeString())) {
4234 wxString stamp = segShow_point_b->GetTimeString();
4235 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4236 if (timestamp.IsValid()) {
4237 // Format track rollover timestamp to OCPN global TZ setting
4240 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4241 }
4242 s << "\n" << _("Segment Created: ") << stamp;
4243 }
4244
4245 s << "\n";
4246 if (g_bShowTrue)
4247 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4248 0x00B0);
4249
4250 if (g_bShowMag) {
4251 double latAverage =
4252 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4253 double lonAverage =
4254 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4255 double varBrg =
4256 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4257
4258 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4259 0x00B0);
4260 }
4261
4262 s << FormatDistanceAdaptive(dist);
4263
4264 if (segShow_point_a->GetTimeString() &&
4265 segShow_point_b->GetTimeString()) {
4266 wxDateTime apoint = segShow_point_a->GetCreateTime();
4267 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4268 if (apoint.IsValid() && bpoint.IsValid()) {
4269 double segmentSpeed = toUsrSpeed(
4270 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4271 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4272 << getUsrSpeedUnit();
4273 }
4274 }
4275
4276 m_pTrackRolloverWin->SetString(s);
4277
4278 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4279 LEG_ROLLOVER, win_size);
4280 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4281 m_pTrackRolloverWin->IsActive(true);
4282 b_need_refresh = true;
4283 showTrackRollover = true;
4284 break;
4285 }
4286 } else {
4287 ++node;
4288 }
4289 }
4290 } else {
4291 // Is the cursor still in select radius, and not timed out?
4292 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4293 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4294 m_pRolloverTrackSeg))
4295 showTrackRollover = false;
4296 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4297 showTrackRollover = false;
4298 else
4299 showTrackRollover = true;
4300 }
4301
4302 // Similar for AIS target rollover window
4303 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4304 showTrackRollover = false;
4305
4306 // Similar for route rollover window
4307 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4308 showTrackRollover = false;
4309
4310 // TODO We onlt show tracks on primary canvas....
4311 // if(!IsPrimaryCanvas())
4312 // showTrackRollover = false;
4313
4314 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4315 !showTrackRollover) {
4316 m_pTrackRolloverWin->IsActive(false);
4317 m_pRolloverTrackSeg = NULL;
4318 m_pTrackRolloverWin->Destroy();
4319 m_pTrackRolloverWin = NULL;
4320 b_need_refresh = true;
4321 } else if (m_pTrackRolloverWin && showTrackRollover) {
4322 m_pTrackRolloverWin->IsActive(true);
4323 b_need_refresh = true;
4324 }
4325
4326 if (b_need_refresh) Refresh();
4327}
4328
4329void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4330 if ((GetShowENCLights() || m_bsectors_shown) &&
4331 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4332 extendedSectorLegs)) {
4333 if (!m_bsectors_shown) {
4334 ReloadVP(false);
4335 m_bsectors_shown = true;
4336 }
4337 } else {
4338 if (m_bsectors_shown) {
4339 ReloadVP(false);
4340 m_bsectors_shown = false;
4341 }
4342 }
4343
4344// This is here because GTK status window update is expensive..
4345// cairo using pango rebuilds the font every time so is very
4346// inefficient
4347// Anyway, only update the status bar when this timer expires
4348#if defined(__WXGTK__) || defined(__WXQT__)
4349 {
4350 // Check the absolute range of the cursor position
4351 // There could be a window wherein the chart geoereferencing is not
4352 // valid....
4353 double cursor_lat, cursor_lon;
4354 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4355
4356 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4357 while (cursor_lon < -180.) cursor_lon += 360.;
4358
4359 while (cursor_lon > 180.) cursor_lon -= 360.;
4360
4361 SetCursorStatus(cursor_lat, cursor_lon);
4362 }
4363 }
4364#endif
4365}
4366
4367void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4368 if (!top_frame::Get()->GetFrameStatusBar()) return;
4369
4370 wxString s1;
4371 s1 += " ";
4372 s1 += toSDMM(1, cursor_lat);
4373 s1 += " ";
4374 s1 += toSDMM(2, cursor_lon);
4375
4376 if (STAT_FIELD_CURSOR_LL >= 0)
4377 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4378
4379 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4380
4381 double brg, dist;
4382 wxString sm;
4383 wxString st;
4384 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4385 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4386 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4387
4388 wxString s = st + sm;
4389 s << FormatDistanceAdaptive(dist);
4390
4391 // CUSTOMIZATION - LIVE ETA OPTION
4392 // -------------------------------------------------------
4393 // Calculate an "live" ETA based on route starting from the current
4394 // position of the boat and goes to the cursor of the mouse.
4395 // In any case, an standard ETA will be calculated with a default speed
4396 // of the boat to give an estimation of the route (in particular if GPS
4397 // is off).
4398
4399 // Display only if option "live ETA" is selected in Settings > Display >
4400 // General.
4401 if (g_bShowLiveETA) {
4402 float realTimeETA;
4403 float boatSpeed;
4404 float boatSpeedDefault = g_defaultBoatSpeed;
4405
4406 // Calculate Estimate Time to Arrival (ETA) in minutes
4407 // Check before is value not closed to zero (it will make an very big
4408 // number...)
4409 if (!std::isnan(gSog)) {
4410 boatSpeed = gSog;
4411 if (boatSpeed < 0.5) {
4412 realTimeETA = 0;
4413 } else {
4414 realTimeETA = dist / boatSpeed * 60;
4415 }
4416 } else {
4417 realTimeETA = 0;
4418 }
4419
4420 // Add space after distance display
4421 s << " ";
4422 // Display ETA
4423 s << minutesToHoursDays(realTimeETA);
4424
4425 // In any case, display also an ETA with default speed at 6knts
4426
4427 s << " [@";
4428 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4429 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4430 s << " ";
4431 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4432 s << "]";
4433 }
4434 // END OF - LIVE ETA OPTION
4435
4436 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4437}
4438
4439// CUSTOMIZATION - FORMAT MINUTES
4440// -------------------------------------------------------
4441// New function to format minutes into a more readable format:
4442// * Hours + minutes, or
4443// * Days + hours.
4444wxString minutesToHoursDays(float timeInMinutes) {
4445 wxString s;
4446
4447 if (timeInMinutes == 0) {
4448 s << "--min";
4449 }
4450
4451 // Less than 60min, keep time in minutes
4452 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4453 s << wxString::Format("%d", (int)timeInMinutes);
4454 s << "min";
4455 }
4456
4457 // Between 1h and less than 24h, display time in hours, minutes
4458 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4459 int hours;
4460 int min;
4461 hours = (int)timeInMinutes / 60;
4462 min = (int)timeInMinutes % 60;
4463
4464 if (min == 0) {
4465 s << wxString::Format("%d", hours);
4466 s << "h";
4467 } else {
4468 s << wxString::Format("%d", hours);
4469 s << "h";
4470 s << wxString::Format("%d", min);
4471 s << "min";
4472 }
4473
4474 }
4475
4476 // More than 24h, display time in days, hours
4477 else if (timeInMinutes > 24 * 60) {
4478 int days;
4479 int hours;
4480 days = (int)(timeInMinutes / 60) / 24;
4481 hours = (int)(timeInMinutes / 60) % 24;
4482
4483 if (hours == 0) {
4484 s << wxString::Format("%d", days);
4485 s << "d";
4486 } else {
4487 s << wxString::Format("%d", days);
4488 s << "d";
4489 s << wxString::Format("%d", hours);
4490 s << "h";
4491 }
4492 }
4493
4494 return s;
4495}
4496
4497// END OF CUSTOMIZATION - FORMAT MINUTES
4498// Thanks open source code ;-)
4499// -------------------------------------------------------
4500
4501void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4502 double clat, clon;
4503 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4504 *lat = clat;
4505 *lon = clon;
4506}
4507
4508void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4509 wxPoint2DDouble *r) {
4510 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4511}
4512
4514 double rlon, wxPoint2DDouble *r) {
4515 // If the Current Chart is a raster chart, and the
4516 // requested lat/long is within the boundaries of the chart,
4517 // and the VP is not rotated,
4518 // then use the embedded BSB chart georeferencing algorithm
4519 // for greater accuracy
4520 // Additionally, use chart embedded georef if the projection is TMERC
4521 // i.e. NOT MERCATOR and NOT POLYCONIC
4522
4523 // If for some reason the chart rejects the request by returning an error,
4524 // then fall back to Viewport Projection estimate from canvas parameters
4525 if (!g_bopengl && m_singleChart &&
4526 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4527 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4528 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4529 (m_singleChart->GetChartProjectionType() !=
4530 PROJECTION_TRANSVERSE_MERCATOR) &&
4531 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4532 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4533 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4534 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4535 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4536 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4537 // Cur_BSB_Ch->GetCOVRTablenPoints
4538 // ( 0 ), rlon,
4539 // rlat );
4540 // bInside = true;
4541 // if ( bInside )
4542 if (Cur_BSB_Ch) {
4543 // This is a Raster chart....
4544 // If the VP is changing, the raster chart parameters may not yet be
4545 // setup So do that before accessing the chart's embedded
4546 // georeferencing
4547 Cur_BSB_Ch->SetVPRasterParms(vp);
4548 double rpixxd, rpixyd;
4549 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4550 r->m_x = rpixxd;
4551 r->m_y = rpixyd;
4552 return;
4553 }
4554 }
4555 }
4556
4557 // if needed, use the VPoint scaling estimator,
4558 *r = vp.GetDoublePixFromLL(rlat, rlon);
4559}
4560
4561// This routine might be deleted and all of the rendering improved
4562// to have floating point accuracy
4563bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4564 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4565}
4566
4567bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4568 wxPoint *r) {
4569 wxPoint2DDouble p;
4570 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4571
4572 // some projections give nan values when invisible values (other side of
4573 // world) are requested we should stop using integer coordinates or return
4574 // false here (and test it everywhere)
4575 if (std::isnan(p.m_x)) {
4576 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4577 return false;
4578 }
4579
4580 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4581 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4582 else
4583 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4584
4585 return true;
4586}
4587
4588void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4589 double &lon) {
4590 // If the Current Chart is a raster chart, and the
4591 // requested x,y is within the boundaries of the chart,
4592 // and the VP is not rotated,
4593 // then use the embedded BSB chart georeferencing algorithm
4594 // for greater accuracy
4595 // Additionally, use chart embedded georef if the projection is TMERC
4596 // i.e. NOT MERCATOR and NOT POLYCONIC
4597
4598 // If for some reason the chart rejects the request by returning an error,
4599 // then fall back to Viewport Projection estimate from canvas parameters
4600 bool bUseVP = true;
4601
4602 if (!g_bopengl && m_singleChart &&
4603 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4604 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4605 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4606 (m_singleChart->GetChartProjectionType() !=
4607 PROJECTION_TRANSVERSE_MERCATOR) &&
4608 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4609 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4610 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4611 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4612
4613 // TODO maybe need iterative process to validate bInside
4614 // first pass is mercator, then check chart boundaries
4615
4616 if (Cur_BSB_Ch) {
4617 // This is a Raster chart....
4618 // If the VP is changing, the raster chart parameters may not yet be
4619 // setup So do that before accessing the chart's embedded
4620 // georeferencing
4621 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4622
4623 double slat, slon;
4624 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4625 lat = slat;
4626
4627 if (slon < -180.)
4628 slon += 360.;
4629 else if (slon > 180.)
4630 slon -= 360.;
4631
4632 lon = slon;
4633 bUseVP = false;
4634 }
4635 }
4636 }
4637
4638 // if needed, use the VPoint scaling estimator
4639 if (bUseVP) {
4640 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4641 }
4642}
4643
4645 StopMovement();
4646 DoZoomCanvas(factor, false);
4647 extendedSectorLegs.clear();
4648}
4649
4650void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4651 bool stoptimer) {
4652 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4653
4654 if (g_bsmoothpanzoom) {
4655 if (StartTimedMovement(stoptimer)) {
4656 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4657 m_zoom_factor = factor;
4658 }
4659
4660 m_zoom_target = VPoint.chart_scale / factor;
4661 } else {
4662 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4663
4664 DoZoomCanvas(factor, can_zoom_to_cursor);
4665 }
4666
4667 extendedSectorLegs.clear();
4668}
4669
4670void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4671 // possible on startup
4672 if (!ChartData) return;
4673 if (!m_pCurrentStack) return;
4674
4675 /* TODO: queue the quilted loading code to a background thread
4676 so yield is never called from here, and also rendering is not delayed */
4677
4678 // Cannot allow Yield() re-entrancy here
4679 if (m_bzooming) return;
4680 m_bzooming = true;
4681
4682 double old_ppm = GetVP().view_scale_ppm;
4683
4684 // Capture current cursor position for zoom to cursor
4685 double zlat = m_cursor_lat;
4686 double zlon = m_cursor_lon;
4687
4688 double proposed_scale_onscreen =
4689 GetVP().chart_scale /
4690 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4691 bool b_do_zoom = false;
4692
4693 if (factor > 1) {
4694 b_do_zoom = true;
4695
4696 // double zoom_factor = factor;
4697
4698 ChartBase *pc = NULL;
4699
4700 if (!VPoint.b_quilt) {
4701 pc = m_singleChart;
4702 } else {
4703 if (!m_disable_adjust_on_zoom) {
4704 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4705 if (new_db_index >= 0)
4706 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4707 else { // for whatever reason, no reference chart is known
4708 // Choose the smallest scale chart on the current stack
4709 // and then adjust for scale range
4710 int current_ref_stack_index = -1;
4711 if (m_pCurrentStack->nEntry) {
4712 int trial_index =
4713 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4714 m_pQuilt->SetReferenceChart(trial_index);
4715 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4716 if (new_db_index >= 0)
4717 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4718 }
4719 }
4720
4721 if (m_pCurrentStack)
4722 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4723 new_db_index); // highlite the correct bar entry
4724 }
4725 }
4726
4727 if (pc) {
4728 // double target_scale_ppm = GetVPScale() * zoom_factor;
4729 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4730 // target_scale_ppm;
4731
4732 // Query the chart to determine the appropriate zoom range
4733 double min_allowed_scale =
4734 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4735
4736 if (proposed_scale_onscreen < min_allowed_scale) {
4737 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4738 m_zoom_factor = 1; /* stop zooming */
4739 b_do_zoom = false;
4740 } else
4741 proposed_scale_onscreen = min_allowed_scale;
4742 }
4743
4744 } else {
4745 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4746 }
4747
4748 } else if (factor < 1) {
4749 b_do_zoom = true;
4750
4751 ChartBase *pc = NULL;
4752
4753 bool b_smallest = false;
4754
4755 if (!VPoint.b_quilt) { // not quilted
4756 pc = m_singleChart;
4757
4758 if (pc) {
4759 // If m_singleChart is not on the screen, unbound the zoomout
4760 LLBBox viewbox = VPoint.GetBBox();
4761 // BoundingBox chart_box;
4762 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4763 double max_allowed_scale;
4764
4765 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4766
4767 // We can allow essentially unbounded zoomout in single chart mode
4768 // if( ChartData->GetDBBoundingBox( current_index,
4769 // &chart_box ) &&
4770 // !viewbox.IntersectOut( chart_box ) )
4771 // // Clamp the minimum scale zoom-out to the value
4772 // specified by the chart max_allowed_scale =
4773 // wxMin(max_allowed_scale, 4.0 *
4774 // pc->GetNormalScaleMax(
4775 // GetCanvasScaleFactor(),
4776 // GetCanvasWidth() ) );
4777 if (proposed_scale_onscreen > max_allowed_scale) {
4778 m_zoom_factor = 1; /* stop zooming */
4779 proposed_scale_onscreen = max_allowed_scale;
4780 }
4781 }
4782
4783 } else {
4784 if (!m_disable_adjust_on_zoom) {
4785 int new_db_index =
4786 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4787 if (new_db_index >= 0)
4788 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4789
4790 if (m_pCurrentStack)
4791 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4792 new_db_index); // highlite the correct bar entry
4793
4794 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4795
4796 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4797 proposed_scale_onscreen =
4798 wxMin(proposed_scale_onscreen,
4799 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4800 }
4801
4802 // set a minimum scale
4803 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4804 m_absolute_min_scale_ppm)
4805 proposed_scale_onscreen =
4806 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4807 }
4808 }
4809 double new_scale =
4810 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4811
4812 if (b_do_zoom) {
4813 // Disable ZTC if lookahead is ON, and currently b_follow is active
4814 bool b_allow_ztc = true;
4815 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4816 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4817 if (m_bLookAhead) {
4818 double brg, distance;
4819 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4820 &distance);
4821 dir_to_shift = brg;
4822 meters_to_shift = distance * 1852;
4823 }
4824 // Arrange to combine the zoom and pan into one operation for smoother
4825 // appearance
4826 SetVPScale(new_scale, false); // adjust, but deferred refresh
4827 wxPoint r;
4828 GetCanvasPointPix(zlat, zlon, &r);
4829 // this will emit the Refresh()
4830 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4831 } else {
4832 SetVPScale(new_scale);
4833 if (m_bFollow) DoCanvasUpdate();
4834 }
4835 }
4836
4837 m_bzooming = false;
4838}
4839
4840void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4841 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4842 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4843}
4844
4845int rot;
4846void ChartCanvas::RotateCanvas(double dir) {
4847 // SetUpMode(NORTH_UP_MODE);
4848
4849 if (g_bsmoothpanzoom) {
4850 if (StartTimedMovement()) {
4851 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4852 m_rotation_speed = dir * 60;
4853 }
4854 } else {
4855 double speed = dir * 10;
4856 if (m_modkeys == wxMOD_ALT) speed /= 20;
4857 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4858 }
4859}
4860
4861void ChartCanvas::DoRotateCanvas(double rotation) {
4862 while (rotation < 0) rotation += 2 * PI;
4863 while (rotation > 2 * PI) rotation -= 2 * PI;
4864
4865 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4866
4867 SetVPRotation(rotation);
4868 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4869}
4870
4871void ChartCanvas::DoTiltCanvas(double tilt) {
4872 while (tilt < 0) tilt = 0;
4873 while (tilt > .95) tilt = .95;
4874
4875 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4876
4877 VPoint.tilt = tilt;
4878 Refresh(false);
4879}
4880
4881void ChartCanvas::TogglebFollow() {
4882 if (!m_bFollow)
4883 SetbFollow();
4884 else
4885 ClearbFollow();
4886}
4887
4888void ChartCanvas::ClearbFollow() {
4889 m_bFollow = false; // update the follow flag
4890
4891 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4892
4893 UpdateFollowButtonState();
4894
4895 DoCanvasUpdate();
4896 ReloadVP();
4897 top_frame::Get()->SetChartUpdatePeriod();
4898}
4899
4900void ChartCanvas::SetbFollow() {
4901 // Is the OWNSHIP on-screen?
4902 // If not, then reset the OWNSHIP offset to 0 (center screen)
4903 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4904 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4905 m_OSoffsetx = 0;
4906 m_OSoffsety = 0;
4907 }
4908
4909 // Apply the present b_follow offset values to ship position
4910 wxPoint2DDouble p;
4912 p.m_x += m_OSoffsetx;
4913 p.m_y -= m_OSoffsety;
4914
4915 // compute the target center screen lat/lon
4916 double dlat, dlon;
4917 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4918
4919 JumpToPosition(dlat, dlon, GetVPScale());
4920 m_bFollow = true;
4921
4922 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4923 UpdateFollowButtonState();
4924
4925 if (!g_bSmoothRecenter) {
4926 DoCanvasUpdate();
4927 ReloadVP();
4928 }
4929 top_frame::Get()->SetChartUpdatePeriod();
4930}
4931
4932void ChartCanvas::UpdateFollowButtonState() {
4933 if (m_muiBar) {
4934 if (!m_bFollow)
4935 m_muiBar->SetFollowButtonState(0);
4936 else {
4937 if (m_bLookAhead)
4938 m_muiBar->SetFollowButtonState(2);
4939 else
4940 m_muiBar->SetFollowButtonState(1);
4941 }
4942 }
4943
4944#ifdef __ANDROID__
4945 if (!m_bFollow)
4946 androidSetFollowTool(0);
4947 else {
4948 if (m_bLookAhead)
4949 androidSetFollowTool(2);
4950 else
4951 androidSetFollowTool(1);
4952 }
4953#endif
4954
4955 // Look for plugin using API-121 or later
4956 // If found, make the follow state callback.
4957 if (g_pi_manager) {
4958 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4959 if (pic->m_enabled && pic->m_init_state) {
4960 switch (pic->m_api_version) {
4961 case 121: {
4962 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4963 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4964 break;
4965 }
4966 default:
4967 break;
4968 }
4969 }
4970 }
4971 }
4972}
4973
4974void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4975 if (g_bSmoothRecenter && !m_routeState) {
4976 if (StartSmoothJump(lat, lon, scale_ppm))
4977 return;
4978 else {
4979 // move closer to the target destination, and try again
4980 double gcDist, gcBearingEnd;
4981 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4982 &gcBearingEnd);
4983 gcBearingEnd += 180;
4984 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4985 GetCanvasWidth() / GetVPScale(); // meters
4986 double lon_offset =
4987 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4988 double new_lat = lat + (lat_offset / (1852 * 60));
4989 double new_lon = lon + (lon_offset / (1852 * 60));
4990 SetViewPoint(new_lat, new_lon);
4991 ReloadVP();
4992 StartSmoothJump(lat, lon, scale_ppm);
4993 return;
4994 }
4995 }
4996
4997 if (lon > 180.0) lon -= 360.0;
4998 m_vLat = lat;
4999 m_vLon = lon;
5000 StopMovement();
5001 m_bFollow = false;
5002
5003 if (!GetQuiltMode()) {
5004 double skew = 0;
5005 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5006 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5007 } else {
5008 if (scale_ppm != GetVPScale()) {
5009 // XXX should be done in SetViewPoint
5010 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5011 AdjustQuiltRefChart();
5012 }
5013 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5014 }
5015
5016 ReloadVP();
5017
5018 UpdateFollowButtonState();
5019
5020 // TODO
5021 // if( g_pi_manager ) {
5022 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5023 // }
5024}
5025
5026bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5027 // Check distance to jump, in pixels at current chart scale
5028 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5029 // width.
5030 double gcDist;
5031 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5032 double distance_pixels = gcDist * GetVPScale();
5033 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5034 // Jump is too far, try again
5035 return false;
5036 }
5037
5038 // Save where we're coming from
5039 m_startLat = m_vLat;
5040 m_startLon = m_vLon;
5041 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5042
5043 // Save where we want to end up
5044 m_endLat = lat;
5045 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5046 m_endScale = scale_ppm;
5047
5048 // Setup timing
5049 m_animationDuration = 600; // ms
5050 m_animationStart = wxGetLocalTimeMillis();
5051
5052 // Stop any previous movement, ensure no conflicts
5053 StopMovement();
5054 m_bFollow = false;
5055
5056 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5057 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5058 m_animationActive = true;
5059
5060 return true;
5061}
5062
5063void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5064 // Calculate time fraction from 0..1
5065 wxLongLong now = wxGetLocalTimeMillis();
5066 double elapsed = (now - m_animationStart).ToDouble();
5067 double t = elapsed / m_animationDuration.ToDouble();
5068 if (t > 1.0) t = 1.0;
5069
5070 // Ease function for smoother movement
5071 double e = easeOutCubic(t);
5072
5073 // Interpolate lat/lon/scale
5074 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5075 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5076 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5077
5078 // Update viewpoint
5079 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5080 // portion)
5081 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5082 ReloadVP();
5083
5084 // If we reached the end, stop the timer and finalize
5085 if (t >= 1.0) {
5086 m_easeTimer.Stop();
5087 m_animationActive = false;
5088 UpdateFollowButtonState();
5089 ZoomCanvasSimple(1.0001);
5090 DoCanvasUpdate();
5091 ReloadVP();
5092 }
5093}
5094
5095bool ChartCanvas::PanCanvas(double dx, double dy) {
5096 if (!ChartData) return false;
5097 extendedSectorLegs.clear();
5098
5099 double dlat, dlon;
5100 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5101
5102 int iters = 0;
5103 for (;;) {
5104 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5105
5106 if (iters++ > 5) return false;
5107 if (!std::isnan(dlat)) break;
5108
5109 dx *= .5, dy *= .5;
5110 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5111 }
5112
5113 // avoid overshooting the poles
5114 if (dlat > 90)
5115 dlat = 90;
5116 else if (dlat < -90)
5117 dlat = -90;
5118
5119 if (dlon > 360.) dlon -= 360.;
5120 if (dlon < -360.) dlon += 360.;
5121
5122 // This should not really be necessary, but round-trip georef on some
5123 // charts is not perfect, So we can get creep on repeated unidimensional
5124 // pans, and corrupt chart cacheing.......
5125
5126 // But this only works on north-up projections
5127 // TODO: can we remove this now?
5128 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5129 // .001 ) ) {
5130 //
5131 // if( dx == 0 ) dlon = clon;
5132 // if( dy == 0 ) dlat = clat;
5133 // }
5134
5135 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5136
5137 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5138
5139 if (VPoint.b_quilt) {
5140 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5141 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5142 // Tweak the scale slightly for a new ref chart
5143 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5144 if (pc) {
5145 double tweak_scale_ppm =
5146 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5147 SetVPScale(tweak_scale_ppm);
5148 }
5149 }
5150
5151 if (new_ref_dbIndex == -1) {
5152#pragma GCC diagnostic push
5153#pragma GCC diagnostic ignored "-Warray-bounds"
5154 // The compiler sees a -1 index being used. Does not happen, though.
5155
5156 // for whatever reason, no reference chart is known
5157 // Probably panned out of the coverage region
5158 // If any charts are anywhere on-screen, choose the smallest
5159 // scale chart on the screen to be a new reference chart.
5160 int trial_index = -1;
5161 if (m_pCurrentStack->nEntry) {
5162 int trial_index =
5163 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5164 }
5165
5166 if (trial_index < 0) {
5167 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5168 if (full_screen_array.size())
5169 trial_index = full_screen_array[full_screen_array.size() - 1];
5170 }
5171
5172 if (trial_index >= 0) {
5173 m_pQuilt->SetReferenceChart(trial_index);
5174 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5175 VPoint.rotation);
5176 ReloadVP();
5177 }
5178#pragma GCC diagnostic pop
5179 }
5180 }
5181
5182 // Turn off bFollow only if the ownship has left the screen
5183 if (m_bFollow) {
5184 double offx, offy;
5185 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5186
5187 double offset_angle = atan2(offy, offx);
5188 double offset_distance = sqrt((offy * offy) + (offx * offx));
5189 double chart_angle = GetVPRotation();
5190 double target_angle = chart_angle - offset_angle;
5191 double d_east_mod = offset_distance * cos(target_angle);
5192 double d_north_mod = offset_distance * sin(target_angle);
5193
5194 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5195 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5196
5197 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5198 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5199 m_bFollow = false; // update the follow flag
5200 UpdateFollowButtonState();
5201 }
5202 }
5203
5204 Refresh(false);
5205
5206 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5207
5208 return true;
5209}
5210
5211bool ChartCanvas::IsOwnshipOnScreen() {
5212 wxPoint r;
5214 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5215 ((r.y > 0) && r.y < GetCanvasHeight()))
5216 return true;
5217 else
5218 return false;
5219}
5220
5221void ChartCanvas::ReloadVP(bool b_adjust) {
5222 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5223
5224 LoadVP(VPoint, b_adjust);
5225}
5226
5227void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5228#ifdef ocpnUSE_GL
5229 if (g_bopengl && m_glcc) {
5230 m_glcc->Invalidate();
5231 if (m_glcc->GetSize() != GetSize()) {
5232 m_glcc->SetSize(GetSize());
5233 }
5234 } else
5235#endif
5236 {
5237 m_cache_vp.Invalidate();
5238 m_bm_cache_vp.Invalidate();
5239 }
5240
5241 VPoint.Invalidate();
5242
5243 if (m_pQuilt) m_pQuilt->Invalidate();
5244
5245 // Make sure that the Selected Group is sensible...
5246 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5247 // m_groupIndex = 0;
5248 // if( !CheckGroup( m_groupIndex ) )
5249 // m_groupIndex = 0;
5250
5251 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5252 vp.m_projection_type, b_adjust);
5253}
5254
5255void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5256 m_pQuilt->SetReferenceChart(dbIndex);
5257 VPoint.Invalidate();
5258 m_pQuilt->Invalidate();
5259}
5260
5261double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5262 if (m_pQuilt)
5263 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5264 else
5265 return vp.view_scale_ppm;
5266}
5267
5268// Verify and adjust the current reference chart,
5269// so that it will not lead to excessive overzoom or underzoom onscreen
5270int ChartCanvas::AdjustQuiltRefChart() {
5271 int ret = -1;
5272 if (m_pQuilt) {
5273 wxASSERT(ChartData);
5274 ChartBase *pc =
5275 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5276 if (pc) {
5277 double min_ref_scale =
5278 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5279 double max_ref_scale =
5280 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5281
5282 if (VPoint.chart_scale < min_ref_scale) {
5283 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5284 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5285 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5286 } else {
5287 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5288
5289 if (!brender_ok) {
5290 int target_stack_index = wxNOT_FOUND;
5291 int il = 0;
5292 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5293 if (index == m_pQuilt->GetRefChartdbIndex()) {
5294 target_stack_index = il;
5295 break;
5296 }
5297 il++;
5298 }
5299 if (wxNOT_FOUND == target_stack_index) // should never happen...
5300 target_stack_index = 0;
5301
5302 int ref_family = pc->GetChartFamily();
5303 int extended_array_count =
5304 m_pQuilt->GetExtendedStackIndexArray().size();
5305 while ((!brender_ok) &&
5306 ((int)target_stack_index < (extended_array_count - 1))) {
5307 target_stack_index++;
5308 int test_db_index =
5309 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5310
5311 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5312 IsChartQuiltableRef(test_db_index)) {
5313 // open the target, and check the min_scale
5314 ChartBase *ptest_chart =
5315 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5316 if (ptest_chart) {
5317 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5318 }
5319 }
5320 }
5321
5322 if (brender_ok) { // found a better reference chart
5323 int new_db_index =
5324 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5325 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5326 IsChartQuiltableRef(new_db_index)) {
5327 m_pQuilt->SetReferenceChart(new_db_index);
5328 ret = new_db_index;
5329 } else
5330 ret = m_pQuilt->GetRefChartdbIndex();
5331 } else
5332 ret = m_pQuilt->GetRefChartdbIndex();
5333
5334 } else
5335 ret = m_pQuilt->GetRefChartdbIndex();
5336 }
5337 } else
5338 ret = -1;
5339 }
5340
5341 return ret;
5342}
5343
5344void ChartCanvas::UpdateCanvasOnGroupChange() {
5345 delete m_pCurrentStack;
5346 m_pCurrentStack = new ChartStack;
5347 wxASSERT(ChartData);
5348 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5349 m_groupIndex);
5350
5351 if (m_pQuilt) {
5352 m_pQuilt->Compose(VPoint);
5353 SetFocus();
5354 }
5355}
5356
5357bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5358 double latNE, double lonNE) {
5359 // Center Point
5360 double latc = (latSW + latNE) / 2.0;
5361 double lonc = (lonSW + lonNE) / 2.0;
5362
5363 // Get scale in ppm (latitude)
5364 double ne_easting, ne_northing;
5365 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5366
5367 double sw_easting, sw_northing;
5368 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5369
5370 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5371
5372 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5373}
5374
5375bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5376 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5377 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5378}
5379
5380bool ChartCanvas::SetVPProjection(int projection) {
5381 if (!g_bopengl) // alternative projections require opengl
5382 return false;
5383
5384 // the view scale varies depending on geographic location and projection
5385 // rescale to keep the relative scale on the screen the same
5386 double prev_true_scale_ppm = m_true_scale_ppm;
5387 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5388 VPoint.skew, VPoint.rotation, projection) &&
5389 SetVPScale(wxMax(
5390 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5391 m_absolute_min_scale_ppm));
5392}
5393
5394bool ChartCanvas::SetViewPoint(double lat, double lon) {
5395 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5396 VPoint.rotation);
5397}
5398
5399bool ChartCanvas::SetVPRotation(double angle) {
5400 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5401 VPoint.skew, angle);
5402}
5403bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5404 double skew, double rotation, int projection,
5405 bool b_adjust, bool b_refresh) {
5406 if (ChartData->IsBusy()) return false;
5407 bool b_ret = false;
5408 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5409 skew -= 2 * PI;
5410 // Any sensible change?
5411 if (VPoint.IsValid()) {
5412 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5413 (fabs(VPoint.skew - skew) < 1e-9) &&
5414 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5415 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5416 (VPoint.m_projection_type == projection ||
5417 projection == PROJECTION_UNKNOWN))
5418 return false;
5419 }
5420 if (VPoint.m_projection_type != projection)
5421 VPoint.InvalidateTransformCache(); // invalidate
5422
5423 // Take a local copy of the last viewport
5424 ViewPort last_vp = VPoint;
5425
5426 VPoint.skew = skew;
5427 VPoint.clat = lat;
5428 VPoint.clon = lon;
5429 VPoint.rotation = rotation;
5430 VPoint.view_scale_ppm = scale_ppm;
5431 if (projection != PROJECTION_UNKNOWN)
5432 VPoint.SetProjectionType(projection);
5433 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5434 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5435
5436 // don't allow latitude above 88 for mercator (90 is infinity)
5437 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5438 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5439 if (VPoint.clat > 89.5)
5440 VPoint.clat = 89.5;
5441 else if (VPoint.clat < -89.5)
5442 VPoint.clat = -89.5;
5443 }
5444
5445 // don't zoom out too far for transverse mercator polyconic until we resolve
5446 // issues
5447 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5448 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5449 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5450
5451 // SetVPRotation(rotation);
5452
5453 if (!g_bopengl) // tilt is not possible without opengl
5454 VPoint.tilt = 0;
5455
5456 if ((VPoint.pix_width <= 0) ||
5457 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5458 return false;
5459
5460 bool bwasValid = VPoint.IsValid();
5461 VPoint.Validate(); // Mark this ViewPoint as OK
5462
5463 // Has the Viewport scale changed? If so, invalidate the vp
5464 if (last_vp.view_scale_ppm != scale_ppm) {
5465 m_cache_vp.Invalidate();
5466 InvalidateGL();
5467 }
5468
5469 // A preliminary value, may be tweaked below
5470 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5471
5472 // recompute cursor position
5473 // and send to interested plugins if the mouse is actually in this window
5474 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5475 int mouseX = mouse_x;
5476 int mouseY = mouse_y;
5477 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5478 (mouseY < VPoint.pix_height)) {
5479 double lat_mouse, lon_mouse;
5480 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5481 m_cursor_lat = lat_mouse;
5482 m_cursor_lon = lon_mouse;
5483 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5484 }
5485 }
5486
5487 if (!VPoint.b_quilt && m_singleChart) {
5488 VPoint.SetBoxes();
5489
5490 // Allow the chart to adjust the new ViewPort for performance optimization
5491 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5492 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5493
5494 // If there is a sensible change in the chart render, refresh the whole
5495 // screen
5496 if ((!m_cache_vp.IsValid()) ||
5497 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5498 Refresh(false);
5499 b_ret = true;
5500 } else {
5501 wxPoint cp_last, cp_this;
5502 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5503 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5504
5505 if (cp_last != cp_this) {
5506 Refresh(false);
5507 b_ret = true;
5508 }
5509 }
5510 // Create the stack
5511 if (m_pCurrentStack) {
5512 assert(ChartData != 0);
5513 int current_db_index;
5514 current_db_index =
5515 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5516
5517 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5518 m_groupIndex);
5519 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5520 }
5521
5522 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5523 }
5524
5525 // Handle the quilted case
5526 if (VPoint.b_quilt) {
5527 VPoint.SetBoxes();
5528
5529 if (last_vp.view_scale_ppm != scale_ppm)
5530 m_pQuilt->InvalidateAllQuiltPatchs();
5531
5532 // Create the quilt
5533 if (ChartData /*&& ChartData->IsValid()*/) {
5534 if (!m_pCurrentStack) return false;
5535
5536 int current_db_index;
5537 current_db_index =
5538 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5539
5540 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5541 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5542
5543 // Check to see if the current quilt reference chart is in the new stack
5544 int current_ref_stack_index = -1;
5545 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5546 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5547 current_ref_stack_index = i;
5548 }
5549
5550 if (g_bFullScreenQuilt) {
5551 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5552 }
5553
5554 // We might need a new Reference Chart
5555 bool b_needNewRef = false;
5556
5557 // If the new stack does not contain the current ref chart....
5558 if ((-1 == current_ref_stack_index) &&
5559 (m_pQuilt->GetRefChartdbIndex() >= 0))
5560 b_needNewRef = true;
5561
5562 // Would the current Ref Chart be excessively underzoomed?
5563 // We need to check this here to be sure, since we cannot know where the
5564 // reference chart was assigned. For instance, the reference chart may
5565 // have been selected from the config file, or from a long jump with a
5566 // chart family switch implicit. Anyway, we check to be sure....
5567 bool renderable = true;
5568 ChartBase *referenceChart =
5569 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5570 if (referenceChart) {
5571 double chartMaxScale = referenceChart->GetNormalScaleMax(
5572 GetCanvasScaleFactor(), GetCanvasWidth());
5573 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5574 }
5575 if (!renderable) b_needNewRef = true;
5576
5577 // Need new refchart?
5578 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5579 const ChartTableEntry &cte_ref =
5580 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5581 int target_scale = cte_ref.GetScale();
5582 int target_type = cte_ref.GetChartType();
5583 int candidate_stack_index;
5584
5585 // reset the ref chart in a way that does not lead to excessive
5586 // underzoom, for performance reasons Try to find a chart that is the
5587 // same type, and has a scale of just smaller than the current ref
5588 // chart
5589
5590 candidate_stack_index = 0;
5591 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5592 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5593 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5594 int candidate_scale = cte_candidate.GetScale();
5595 int candidate_type = cte_candidate.GetChartType();
5596
5597 if ((candidate_scale >= target_scale) &&
5598 (candidate_type == target_type)) {
5599 bool renderable = true;
5600 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5601 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5602 if (tentative_referenceChart) {
5603 double chartMaxScale =
5604 tentative_referenceChart->GetNormalScaleMax(
5605 GetCanvasScaleFactor(), GetCanvasWidth());
5606 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5607 }
5608
5609 if (renderable) break;
5610 }
5611
5612 candidate_stack_index++;
5613 }
5614
5615 // If that did not work, look for a chart of just larger scale and
5616 // same type
5617 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5618 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5619 while (candidate_stack_index >= 0) {
5620 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5621 if (idx >= 0) {
5622 const ChartTableEntry &cte_candidate =
5623 ChartData->GetChartTableEntry(idx);
5624 int candidate_scale = cte_candidate.GetScale();
5625 int candidate_type = cte_candidate.GetChartType();
5626
5627 if ((candidate_scale <= target_scale) &&
5628 (candidate_type == target_type))
5629 break;
5630 }
5631 candidate_stack_index--;
5632 }
5633 }
5634
5635 // and if that did not work, chose stack entry 0
5636 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5637 (candidate_stack_index < 0))
5638 candidate_stack_index = 0;
5639
5640 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5641
5642 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5643 }
5644
5645 if (!g_bopengl) {
5646 // Preset the VPoint projection type to match what the quilt projection
5647 // type will be
5648 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5649
5650 // Always keep the default Mercator projection if the reference chart is
5651 // not in the PatchList or the scale is too small for it to render.
5652
5653 bool renderable = true;
5654 ChartBase *referenceChart =
5655 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5656 if (referenceChart) {
5657 double chartMaxScale = referenceChart->GetNormalScaleMax(
5658 GetCanvasScaleFactor(), GetCanvasWidth());
5659 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5660 proj = ChartData->GetDBChartProj(ref_db_index);
5661 } else
5662 proj = PROJECTION_MERCATOR;
5663
5664 VPoint.b_MercatorProjectionOverride =
5665 (m_pQuilt->GetnCharts() == 0 || !renderable);
5666
5667 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5668
5669 VPoint.SetProjectionType(proj);
5670 }
5671
5672 // If this quilt will be a perceptible delta from the existing quilt,
5673 // then refresh the entire screen
5674 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5675 // Allow the quilt to adjust the new ViewPort for performance
5676 // optimization This will normally be only a fractional (i.e.
5677 // sub-pixel) adjustment...
5678 if (b_adjust) {
5679 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5680 }
5681
5682 // ChartData->ClearCacheInUseFlags();
5683 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5684
5685 // wxStopWatch sw;
5686
5687#ifdef __ANDROID__
5688 // This is an optimization for panning on touch screen systems.
5689 // The quilt composition is deferred until the OnPaint() message gets
5690 // finally removed and processed from the message queue.
5691 // Takes advantage of the fact that touch-screen pan gestures are
5692 // usually short in distance,
5693 // so not requiring a full quilt rebuild until the pan gesture is
5694 // complete.
5695 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5696 // qDebug() << "Force compose";
5697 m_pQuilt->Compose(VPoint);
5698 } else {
5699 m_pQuilt->Invalidate();
5700 }
5701#else
5702 m_pQuilt->Compose(VPoint);
5703#endif
5704
5705 // printf("comp time %ld\n", sw.Time());
5706
5707 // If the extended chart stack has changed, invalidate any cached
5708 // render bitmap
5709 // if(m_pQuilt->GetXStackHash() != hash1) {
5710 // m_bm_cache_vp.Invalidate();
5711 // InvalidateGL();
5712 // }
5713
5714 ChartData->PurgeCacheUnusedCharts(0.7);
5715
5716 if (b_refresh) Refresh(false);
5717
5718 b_ret = true;
5719 }
5720 }
5721
5722 VPoint.skew = 0.; // Quilting supports 0 Skew
5723 } else if (!g_bopengl) {
5724 OcpnProjType projection = PROJECTION_UNKNOWN;
5725 if (m_singleChart) // viewport projection must match chart projection
5726 // without opengl
5727 projection = m_singleChart->GetChartProjectionType();
5728 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5729 VPoint.SetProjectionType(projection);
5730 }
5731
5732 // Has the Viewport projection changed? If so, invalidate the vp
5733 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5734 m_cache_vp.Invalidate();
5735 InvalidateGL();
5736 }
5737
5738 UpdateCanvasControlBar(); // Refresh the Piano
5739
5740 VPoint.chart_scale = 1.0; // fallback default value
5741
5742 if (VPoint.GetBBox().GetValid()) {
5743 // Update the viewpoint reference scale
5744 if (m_singleChart)
5745 VPoint.ref_scale = m_singleChart->GetNativeScale();
5746 else {
5747#ifdef __ANDROID__
5748 // This is an optimization for panning on touch screen systems.
5749 // See above.
5750 // Quilt might not be fully composed at this point, so for cm93
5751 // the reference scale may not be known.
5752 // In this case, do not update the VP ref_scale.
5753 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5754 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5755 }
5756#else
5757 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5758#endif
5759 }
5760
5761 // Calculate the on-screen displayed actual scale
5762 // by a simple traverse northward from the center point
5763 // of roughly one eighth of the canvas height
5764 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5765
5766 double delta_check =
5767 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5768 delta_check /= 8.;
5769
5770 double check_point = wxMin(89., VPoint.clat);
5771
5772 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5773
5774 double rhumbDist;
5775 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5776 VPoint.clon, 0, &rhumbDist);
5777
5778 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5779 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5780 // Calculate the distance between r1 and r in physical pixels.
5781 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5782 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5783
5784 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5785
5786 // A fall back in case of very high zoom-out, giving delta_y == 0
5787 // which can probably only happen with vector charts
5788 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5789
5790 // Another fallback, for highly zoomed out charts
5791 // This adjustment makes the displayed TrueScale correspond to the
5792 // same algorithm used to calculate the chart zoom-out limit for
5793 // ChartDummy.
5794 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5795
5796 if (m_true_scale_ppm)
5797 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5798 else
5799 VPoint.chart_scale = 1.0;
5800
5801 // Create a nice renderable string
5802 double round_factor = 1000.;
5803 if (VPoint.chart_scale <= 1000.)
5804 round_factor = 10.;
5805 else if (VPoint.chart_scale <= 10000.)
5806 round_factor = 100.;
5807 else if (VPoint.chart_scale <= 100000.)
5808 round_factor = 1000.;
5809
5810 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5811 double retina_coef = 1;
5812#ifdef ocpnUSE_GL
5813#ifdef __WXOSX__
5814 if (g_bopengl) {
5815 retina_coef = GetContentScaleFactor();
5816 }
5817#endif
5818#endif
5819
5820 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5821 // rounded to the nearest 10, 100 or 1000.
5822 //
5823 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5824 // true_scale_display. That does not make sense. The chart scale should be
5825 // the same as the true scale within the limits of the rounding factor.
5826 double true_scale_display =
5827 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5828 wxString text;
5829
5830 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5831
5832 if (m_displayed_scale_factor > 10.0)
5833 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5834 m_displayed_scale_factor);
5835 else if (m_displayed_scale_factor > 1.0)
5836 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5837 m_displayed_scale_factor);
5838 else if (m_displayed_scale_factor > 0.1) {
5839 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5840 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5841 } else if (m_displayed_scale_factor > 0.01) {
5842 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5843 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5844 } else {
5845 text.Printf(
5846 "%s %4.0f (---)", _("Scale"),
5847 true_scale_display); // Generally, no chart, so no chart scale factor
5848 }
5849
5850 m_scaleValue = true_scale_display;
5851 m_scaleText = text;
5852 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5853
5854 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5855 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5856 STAT_FIELD_SCALE)) {
5857 // Check to see if the text will fit in the StatusBar field...
5858 bool b_noshow = false;
5859 {
5860 int w = 0;
5861 int h;
5862 wxClientDC dc(top_frame::Get()->GetStatusBar());
5863 if (dc.IsOk()) {
5864 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5865 dc.SetFont(*templateFont);
5866 dc.GetTextExtent(text, &w, &h);
5867
5868 // If text is too long for the allocated field, try to reduce the text
5869 // string a bit.
5870 wxRect rect;
5871 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5872 rect);
5873 if (w && w > rect.width) {
5874 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5875 }
5876
5877 // Test again...if too big still, then give it up.
5878 dc.GetTextExtent(text, &w, &h);
5879
5880 if (w && w > rect.width) {
5881 b_noshow = true;
5882 }
5883 }
5884 }
5885
5886 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5887 }
5888 }
5889
5890 // Maintain member vLat/vLon
5891 m_vLat = VPoint.clat;
5892 m_vLon = VPoint.clon;
5893
5894 return b_ret;
5895}
5896
5897// Static Icon definitions for some symbols requiring
5898// scaling/rotation/translation Very specific wxDC draw commands are
5899// necessary to properly render these icons...See the code in
5900// ShipDraw()
5901
5902// This icon was adapted and scaled from the S52 Presentation Library
5903// version 3_03.
5904// Symbol VECGND02
5905
5906static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5907
5908// This ownship icon was adapted and scaled from the S52 Presentation
5909// Library version 3_03 Symbol OWNSHP05
5910static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5911 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5912
5913wxColour ChartCanvas::PredColor() {
5914 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5915 // visibility.
5916 if (SHIP_NORMAL == m_ownship_state)
5917 return GetGlobalColor("URED");
5918
5919 else if (SHIP_LOWACCURACY == m_ownship_state)
5920 return GetGlobalColor("YELO1");
5921
5922 return GetGlobalColor("NODTA");
5923}
5924
5925wxColour ChartCanvas::ShipColor() {
5926 // Establish ship color
5927 // It changes color based on GPS and Chart accuracy/availability
5928
5929 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5930
5931 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5932
5933 return GetGlobalColor("URED"); // default is OK
5934}
5935
5936void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5937 wxPoint2DDouble lShipMidPoint) {
5938 dc.SetPen(wxPen(PredColor(), 2));
5939
5940 if (SHIP_NORMAL == m_ownship_state)
5941 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5942 else
5943 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5944
5945 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5946 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5947
5948 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5949 lShipMidPoint.m_y);
5950 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5951 lShipMidPoint.m_y + 12);
5952}
5953
5954void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5955 wxPoint GPSOffsetPixels,
5956 wxPoint2DDouble lGPSPoint) {
5957 // if (m_animationActive) return;
5958 // Develop a uniform length for course predictor line dash length, based on
5959 // physical display size Use this reference length to size all other graphics
5960 // elements
5961 float ref_dim = m_display_size_mm / 24;
5962 ref_dim = wxMin(ref_dim, 12);
5963 ref_dim = wxMax(ref_dim, 6);
5964
5965 wxColour cPred;
5966 cPred.Set(g_cog_predictor_color);
5967 if (cPred == wxNullColour) cPred = PredColor();
5968
5969 // Establish some graphic element line widths dependent on the platform
5970 // display resolution
5971 // double nominal_line_width_pix = wxMax(1.0,
5972 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5973 // not less than 1 pixel
5974 double nominal_line_width_pix = wxMax(
5975 1.0,
5976 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5977
5978 // If the calculated value is greater than the config file spec value, then
5979 // use it.
5980 if (nominal_line_width_pix > g_cog_predictor_width)
5981 g_cog_predictor_width = nominal_line_width_pix;
5982
5983 // Calculate ownship Position Predictor
5984 wxPoint lPredPoint, lHeadPoint;
5985
5986 float pCog = std::isnan(gCog) ? 0 : gCog;
5987 float pSog = std::isnan(gSog) ? 0 : gSog;
5988
5989 double pred_lat, pred_lon;
5990 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5991 &pred_lat, &pred_lon);
5992 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5993
5994 // test to catch the case where COG/HDG line crosses the screen
5995 LLBBox box;
5996
5997 // Should we draw the Head vector?
5998 // Compare the points lHeadPoint and lPredPoint
5999 // If they differ by more than n pixels, and the head vector is valid, then
6000 // render the head vector
6001
6002 float ndelta_pix = 10.;
6003 double hdg_pred_lat, hdg_pred_lon;
6004 bool b_render_hdt = false;
6005 if (!std::isnan(gHdt)) {
6006 // Calculate ownship Heading pointer as a predictor
6007 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6008 &hdg_pred_lon);
6009 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6010 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6011 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6012 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6013 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6014 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6015 }
6016 }
6017
6018 // draw course over ground if they are longer than the ship
6019 wxPoint lShipMidPoint;
6020 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6021 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6022 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6023 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6024
6025 if (lpp >= img_height / 2) {
6026 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6027 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6028 !std::isnan(gSog)) {
6029 // COG Predictor
6030 float dash_length = ref_dim;
6031 wxDash dash_long[2];
6032 dash_long[0] =
6033 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6034 g_cog_predictor_width); // Long dash , in mm <---------+
6035 dash_long[1] = dash_long[0] / 2.0; // Short gap
6036
6037 // On ultra-hi-res displays, do not allow the dashes to be greater than
6038 // 250, since it is defined as (char)
6039 if (dash_length > 250.) {
6040 dash_long[0] = 250. / g_cog_predictor_width;
6041 dash_long[1] = dash_long[0] / 2;
6042 }
6043
6044 wxPen ppPen2(cPred, g_cog_predictor_width,
6045 (wxPenStyle)g_cog_predictor_style);
6046 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6047 ppPen2.SetDashes(2, dash_long);
6048 dc.SetPen(ppPen2);
6049 dc.StrokeLine(
6050 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6051 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6052
6053 if (g_cog_predictor_width > 1) {
6054 float line_width = g_cog_predictor_width / 3.;
6055
6056 wxDash dash_long3[2];
6057 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6058 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6059
6060 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6061 (wxPenStyle)g_cog_predictor_style);
6062 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6063 ppPen3.SetDashes(2, dash_long3);
6064 dc.SetPen(ppPen3);
6065 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6066 lGPSPoint.m_y + GPSOffsetPixels.y,
6067 lPredPoint.x + GPSOffsetPixels.x,
6068 lPredPoint.y + GPSOffsetPixels.y);
6069 }
6070
6071 if (g_cog_predictor_endmarker) {
6072 // Prepare COG predictor endpoint icon
6073 double png_pred_icon_scale_factor = .4;
6074 if (g_ShipScaleFactorExp > 1.0)
6075 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6076 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6077
6078 wxPoint icon[4];
6079
6080 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6081 (float)(lPredPoint.x - lShipMidPoint.x));
6082 cog_rad += (float)PI;
6083
6084 for (int i = 0; i < 4; i++) {
6085 int j = i * 2;
6086 double pxa = (double)(s_png_pred_icon[j]);
6087 double pya = (double)(s_png_pred_icon[j + 1]);
6088
6089 pya *= png_pred_icon_scale_factor;
6090 pxa *= png_pred_icon_scale_factor;
6091
6092 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6093 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6094
6095 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6096 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6097 }
6098
6099 // Render COG endpoint icon
6100 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6101 wxPENSTYLE_SOLID);
6102 dc.SetPen(ppPen1);
6103 dc.SetBrush(wxBrush(cPred));
6104
6105 dc.StrokePolygon(4, icon);
6106 }
6107 }
6108 }
6109
6110 // HDT Predictor
6111 if (b_render_hdt) {
6112 float hdt_dash_length = ref_dim * 0.4;
6113
6114 cPred.Set(g_ownship_HDTpredictor_color);
6115 if (cPred == wxNullColour) cPred = PredColor();
6116 float hdt_width =
6117 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6118 : g_cog_predictor_width * 0.8);
6119 wxDash dash_short[2];
6120 dash_short[0] =
6121 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6122 hdt_width); // Short dash , in mm <---------+
6123 dash_short[1] =
6124 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6125 hdt_width); // Short gap |
6126
6127 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6128 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6129 ppPen2.SetDashes(2, dash_short);
6130
6131 dc.SetPen(ppPen2);
6132 dc.StrokeLine(
6133 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6134 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6135
6136 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6137 dc.SetPen(ppPen1);
6138 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6139
6140 if (g_ownship_HDTpredictor_endmarker) {
6141 double nominal_circle_size_pixels = wxMax(
6142 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6143
6144 // Scale the circle to ChartScaleFactor, slightly softened....
6145 if (g_ShipScaleFactorExp > 1.0)
6146 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6147
6148 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6149 lHeadPoint.y + GPSOffsetPixels.y,
6150 nominal_circle_size_pixels / 2);
6151 }
6152 }
6153
6154 // Draw radar rings if activated
6155 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6156 double factor = 1.00;
6157 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6158 factor = 1 / 1.852;
6159 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6160 if (std::isnan(gSog))
6161 factor = 0.0;
6162 else
6163 factor = gSog / 60;
6164 }
6165 factor *= g_fNavAidRadarRingsStep;
6166
6167 double tlat, tlon;
6168 wxPoint r;
6169 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6170 GetCanvasPointPix(tlat, tlon, &r);
6171
6172 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6173 pow((double)(lGPSPoint.m_y - r.y), 2));
6174 int pix_radius = (int)lpp;
6175
6176 wxColor rangeringcolour =
6177 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6178
6179 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6180
6181 dc.SetPen(ppPen1);
6182 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6183
6184 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6185 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6186 }
6187}
6188
6189#if ocpnUSE_GL
6190void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6191bool ChartCanvas::CanAccelerateGlPanning() {
6192 return GetglCanvas()->CanAcceleratePanning();
6193}
6194void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6195
6196#else
6197void ChartCanvas::ResetGlGridFont() {}
6198bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6199void ChartCanvas::SetupGlCompression() {}
6200#endif
6201
6202void ChartCanvas::ComputeShipScaleFactor(
6203 float icon_hdt, int ownShipWidth, int ownShipLength,
6204 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6205 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6206 float screenResolution = m_pix_per_mm;
6207
6208 // Calculate the true ship length in exact pixels
6209 double ship_bow_lat, ship_bow_lon;
6210 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6211 &ship_bow_lat, &ship_bow_lon);
6212 wxPoint lShipBowPoint;
6213 wxPoint2DDouble b_point =
6214 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6215 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6216
6217 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6218 powf((float)(b_point.m_y - a_point.m_y), 2));
6219
6220 // And in mm
6221 float shipLength_mm = shipLength_px / screenResolution;
6222
6223 // Set minimum ownship drawing size
6224 float ownship_min_mm = g_n_ownship_min_mm;
6225 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6226
6227 // Calculate Nautical Miles distance from midships to gps antenna
6228 float hdt_ant = icon_hdt + 180.;
6229 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6230 float dx = g_n_gps_antenna_offset_x / 1852.;
6231 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6232 {
6233 hdt_ant = icon_hdt;
6234 dy = -dy;
6235 }
6236
6237 // If the drawn ship size is going to be clamped, adjust the gps antenna
6238 // offsets
6239 if (shipLength_mm < ownship_min_mm) {
6240 dy /= shipLength_mm / ownship_min_mm;
6241 dx /= shipLength_mm / ownship_min_mm;
6242 }
6243
6244 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6245
6246 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6247 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6248 &ship_mid_lon1);
6249
6250 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6251 &lShipMidPoint);
6252
6253 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6254 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6255
6256 float scale_factor = shipLength_px / ownShipLength;
6257
6258 // Calculate a scale factor that would produce a reasonably sized icon
6259 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6260
6261 // And choose the correct one
6262 scale_factor = wxMax(scale_factor, scale_factor_min);
6263
6264 scale_factor_y = scale_factor;
6265 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6266 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6267}
6268
6269void ChartCanvas::ShipDraw(ocpnDC &dc) {
6270 if (!GetVP().IsValid()) return;
6271
6272 wxPoint GPSOffsetPixels(0, 0);
6273 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6274
6275 // COG/SOG may be undefined in NMEA data stream
6276 float pCog = std::isnan(gCog) ? 0 : gCog;
6277 float pSog = std::isnan(gSog) ? 0 : gSog;
6278
6279 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6280
6281 lShipMidPoint = lGPSPoint;
6282
6283 // Draw the icon rotated to the COG
6284 // or to the Hdt if available
6285 float icon_hdt = pCog;
6286 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6287
6288 // COG may be undefined in NMEA data stream
6289 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6290
6291 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6292 // predictor
6293 double osd_head_lat, osd_head_lon;
6294 wxPoint osd_head_point;
6295
6296 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6297 &osd_head_lon);
6298
6299 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6300
6301 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6302 (float)(osd_head_point.x - lShipMidPoint.m_x));
6303 icon_rad += (float)PI;
6304
6305 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6306
6307 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6308 // nominal size and is just barely outside the viewport ....
6309 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6310
6311 // TODO: fix to include actual size of boat that will be rendered
6312 int img_height = 0;
6313 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6314 if (GetVP().chart_scale >
6315 300000) // According to S52, this should be 50,000
6316 {
6317 ShipDrawLargeScale(dc, lShipMidPoint);
6318 img_height = 20;
6319 } else {
6320 wxImage pos_image;
6321
6322 // Substitute user ownship image if found
6323 if (m_pos_image_user)
6324 pos_image = m_pos_image_user->Copy();
6325 else if (SHIP_NORMAL == m_ownship_state)
6326 pos_image = m_pos_image_red->Copy();
6327 if (SHIP_LOWACCURACY == m_ownship_state)
6328 pos_image = m_pos_image_yellow->Copy();
6329 else if (SHIP_NORMAL != m_ownship_state)
6330 pos_image = m_pos_image_grey->Copy();
6331
6332 // Substitute user ownship image if found
6333 if (m_pos_image_user) {
6334 pos_image = m_pos_image_user->Copy();
6335
6336 if (SHIP_LOWACCURACY == m_ownship_state)
6337 pos_image = m_pos_image_user_yellow->Copy();
6338 else if (SHIP_NORMAL != m_ownship_state)
6339 pos_image = m_pos_image_user_grey->Copy();
6340 }
6341
6342 img_height = pos_image.GetHeight();
6343
6344 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6345 g_OwnShipIconType > 0) // use large ship
6346 {
6347 int ownShipWidth = 22; // Default values from s_ownship_icon
6348 int ownShipLength = 84;
6349 if (g_OwnShipIconType == 1) {
6350 ownShipWidth = pos_image.GetWidth();
6351 ownShipLength = pos_image.GetHeight();
6352 }
6353
6354 float scale_factor_x, scale_factor_y;
6355 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6356 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6357 scale_factor_x, scale_factor_y);
6358
6359 if (g_OwnShipIconType == 1) { // Scaled bitmap
6360 pos_image.Rescale(ownShipWidth * scale_factor_x,
6361 ownShipLength * scale_factor_y,
6362 wxIMAGE_QUALITY_HIGH);
6363 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6364 wxImage rot_image =
6365 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6366
6367 // Simple sharpening algorithm.....
6368 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6369 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6370 if (rot_image.GetAlpha(ip, jp) > 64)
6371 rot_image.SetAlpha(ip, jp, 255);
6372
6373 wxBitmap os_bm(rot_image);
6374
6375 int w = os_bm.GetWidth();
6376 int h = os_bm.GetHeight();
6377 img_height = h;
6378
6379 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6380 lShipMidPoint.m_y - h / 2, true);
6381
6382 // Maintain dirty box,, missing in __WXMSW__ library
6383 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6384 lShipMidPoint.m_y - h / 2);
6385 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6386 lShipMidPoint.m_y - h / 2 + h);
6387 }
6388
6389 else if (g_OwnShipIconType == 2) { // Scaled Vector
6390 wxPoint ownship_icon[10];
6391
6392 for (int i = 0; i < 10; i++) {
6393 int j = i * 2;
6394 float pxa = (float)(s_ownship_icon[j]);
6395 float pya = (float)(s_ownship_icon[j + 1]);
6396 pya *= scale_factor_y;
6397 pxa *= scale_factor_x;
6398
6399 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6400 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6401
6402 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6403 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6404 }
6405
6406 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6407 dc.SetPen(ppPen1);
6408 dc.SetBrush(wxBrush(ShipColor()));
6409
6410 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6411
6412 // draw reference point (midships) cross
6413 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6414 ownship_icon[7].y);
6415 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6416 ownship_icon[9].y);
6417 }
6418
6419 img_height = ownShipLength * scale_factor_y;
6420
6421 // Reference point, where the GPS antenna is
6422 int circle_rad = 3;
6423 if (m_pos_image_user) circle_rad = 1;
6424
6425 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6426 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6427 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6428 } else { // Fixed bitmap icon.
6429 /* non opengl, or suboptimal opengl via ocpndc: */
6430 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6431 wxImage rot_image =
6432 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6433
6434 // Simple sharpening algorithm.....
6435 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6436 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6437 if (rot_image.GetAlpha(ip, jp) > 64)
6438 rot_image.SetAlpha(ip, jp, 255);
6439
6440 wxBitmap os_bm(rot_image);
6441
6442 if (g_ShipScaleFactorExp > 1) {
6443 wxImage scaled_image = os_bm.ConvertToImage();
6444 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6445 1.0; // soften the scale factor a bit
6446 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6447 scaled_image.GetHeight() * factor,
6448 wxIMAGE_QUALITY_HIGH));
6449 }
6450 int w = os_bm.GetWidth();
6451 int h = os_bm.GetHeight();
6452 img_height = h;
6453
6454 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6455 lShipMidPoint.m_y - h / 2, true);
6456
6457 // Reference point, where the GPS antenna is
6458 int circle_rad = 3;
6459 if (m_pos_image_user) circle_rad = 1;
6460
6461 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6462 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6463 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6464
6465 // Maintain dirty box,, missing in __WXMSW__ library
6466 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6467 lShipMidPoint.m_y - h / 2);
6468 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6469 lShipMidPoint.m_y - h / 2 + h);
6470 }
6471 } // ownship draw
6472 }
6473
6474 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6475}
6476
6477/* @ChartCanvas::CalcGridSpacing
6478 **
6479 ** Calculate the major and minor spacing between the lat/lon grid
6480 **
6481 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6482 *window
6483 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6484 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6485 ** @return [void]
6486 */
6487void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6488 float &MinorSpacing) {
6489 // table for calculating the distance between the grids
6490 // [0] view_scale ppm
6491 // [1] spacing between major grid lines in degrees
6492 // [2] spacing between minor grid lines in degrees
6493 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6494 {.000001f, 45.0f, 15.0f},
6495 {.0002f, 30.0f, 10.0f},
6496 {.0003f, 10.0f, 2.0f},
6497 {.0008f, 5.0f, 1.0f},
6498 {.001f, 2.0f, 30.0f / 60.0f},
6499 {.003f, 1.0f, 20.0f / 60.0f},
6500 {.006f, 0.5f, 10.0f / 60.0f},
6501 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6502 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6503 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6504 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6505 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6506 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6507 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6508 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6509
6510 unsigned int tabi;
6511 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6512 if (view_scale_ppm < lltab[tabi][0]) break;
6513 MajorSpacing = lltab[tabi][1]; // major latitude distance
6514 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6515 return;
6516}
6517/* @ChartCanvas::CalcGridText *************************************
6518 **
6519 ** Calculates text to display at the major grid lines
6520 **
6521 ** @param [r] latlon [float] latitude or longitude of grid line
6522 ** @param [r] spacing [float] distance between two major grid lines
6523 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6524 **
6525 ** @return
6526 */
6527
6528wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6529 int deg = (int)fabs(latlon); // degrees
6530 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6531 char postfix;
6532
6533 // calculate postfix letter (NSEW)
6534 if (latlon > 0.0) {
6535 if (bPostfix) {
6536 postfix = 'N';
6537 } else {
6538 postfix = 'E';
6539 }
6540 } else if (latlon < 0.0) {
6541 if (bPostfix) {
6542 postfix = 'S';
6543 } else {
6544 postfix = 'W';
6545 }
6546 } else {
6547 postfix = ' '; // no postfix for equator and greenwich
6548 }
6549 // calculate text, display minutes only if spacing is smaller than one degree
6550
6551 wxString ret;
6552 if (spacing >= 1.0) {
6553 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6554 } else if (spacing >= (1.0 / 60.0)) {
6555 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6556 } else {
6557 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6558 }
6559
6560 return ret;
6561}
6562
6563/* @ChartCanvas::GridDraw *****************************************
6564 **
6565 ** Draws major and minor Lat/Lon Grid on the chart
6566 ** - distance between Grid-lm ines are calculated automatic
6567 ** - major grid lines will be across the whole chart window
6568 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6569 **
6570 ** @param [w] dc [wxDC&] the wx drawing context
6571 **
6572 ** @return [void]
6573 ************************************************************************/
6574void ChartCanvas::GridDraw(ocpnDC &dc) {
6575 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6576
6577 double nlat, elon, slat, wlon;
6578 float lat, lon;
6579 float dlon;
6580 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6581 wxCoord w, h;
6582 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6583 dc.SetPen(GridPen);
6584 if (!m_pgridFont) SetupGridFont();
6585 dc.SetFont(*m_pgridFont);
6586 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6587
6588 w = m_canvas_width;
6589 h = m_canvas_height;
6590
6591 GetCanvasPixPoint(0, 0, nlat,
6592 wlon); // get lat/lon of upper left point of the window
6593 GetCanvasPixPoint(w, h, slat,
6594 elon); // get lat/lon of lower right point of the window
6595 dlon =
6596 elon -
6597 wlon; // calculate how many degrees of longitude are shown in the window
6598 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6599 {
6600 dlon = dlon + 360.0;
6601 }
6602 // calculate distance between latitude grid lines
6603 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6604
6605 // calculate position of first major latitude grid line
6606 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6607
6608 // Draw Major latitude grid lines and text
6609 while (lat < nlat) {
6610 wxPoint r;
6611 wxString st =
6612 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6613 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6614 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6615 dc.DrawText(st, 0, r.y); // draw text
6616 lat = lat + gridlatMajor;
6617
6618 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6619 }
6620
6621 // calculate position of first minor latitude grid line
6622 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6623
6624 // Draw minor latitude grid lines
6625 while (lat < nlat) {
6626 wxPoint r;
6627 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6628 dc.DrawLine(0, r.y, 10, r.y, false);
6629 dc.DrawLine(w - 10, r.y, w, r.y, false);
6630 lat = lat + gridlatMinor;
6631 }
6632
6633 // calculate distance between grid lines
6634 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6635
6636 // calculate position of first major latitude grid line
6637 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6638
6639 // draw major longitude grid lines
6640 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6641 wxPoint r;
6642 wxString st = CalcGridText(lon, gridlonMajor, false);
6643 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6644 dc.DrawLine(r.x, 0, r.x, h, false);
6645 dc.DrawText(st, r.x, 0);
6646 lon = lon + gridlonMajor;
6647 if (lon > 180.0) {
6648 lon = lon - 360.0;
6649 }
6650
6651 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6652 }
6653
6654 // calculate position of first minor longitude grid line
6655 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6656 // draw minor longitude grid lines
6657 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6658 wxPoint r;
6659 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6660 dc.DrawLine(r.x, 0, r.x, 10, false);
6661 dc.DrawLine(r.x, h - 10, r.x, h, false);
6662 lon = lon + gridlonMinor;
6663 if (lon > 180.0) {
6664 lon = lon - 360.0;
6665 }
6666 }
6667}
6668
6669void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6670 if (0 ) {
6671 double blat, blon, tlat, tlon;
6672 wxPoint r;
6673
6674 int x_origin = m_bDisplayGrid ? 60 : 20;
6675 int y_origin = m_canvas_height - 50;
6676
6677 float dist;
6678 int count;
6679 wxPen pen1, pen2;
6680
6681 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6682 {
6683 dist = 10.0;
6684 count = 5;
6685 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6686 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6687 } else // Draw 1 mile scale as SCALEB10
6688 {
6689 dist = 1.0;
6690 count = 10;
6691 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6692 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6693 }
6694
6695 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6696 double rotation = -VPoint.rotation;
6697
6698 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6699 GetCanvasPointPix(tlat, tlon, &r);
6700 int l1 = (y_origin - r.y) / count;
6701
6702 for (int i = 0; i < count; i++) {
6703 int y = l1 * i;
6704 if (i & 1)
6705 dc.SetPen(pen1);
6706 else
6707 dc.SetPen(pen2);
6708
6709 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6710 }
6711 } else {
6712 double blat, blon, tlat, tlon;
6713
6714 int x_origin = 5.0 * GetPixPerMM();
6715 int chartbar_height = GetChartbarHeight();
6716 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6717 // if (style->chartStatusWindowTransparent)
6718 // chartbar_height = 0;
6719 int y_origin = m_canvas_height - chartbar_height - 5;
6720#ifdef __WXOSX__
6721 if (!g_bopengl)
6722 y_origin =
6723 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6724#endif
6725
6726 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6727 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6728
6729 double d;
6730 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6731 d /= 2;
6732
6733 int unit = g_iDistanceFormat;
6734 if (d < .5 &&
6735 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6736 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6737
6738 // nice number
6739 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6740 float places = floor(logdist), rem = logdist - places;
6741 dist = pow(10, places);
6742
6743 if (rem < .2)
6744 dist /= 5;
6745 else if (rem < .5)
6746 dist /= 2;
6747
6748 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6749 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6750 double rotation = -VPoint.rotation;
6751
6752 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6753 &tlat, &tlon);
6754 wxPoint r;
6755 GetCanvasPointPix(tlat, tlon, &r);
6756 int l1 = r.x - x_origin;
6757
6758 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6759 12); // Store this for later reference
6760
6761 dc.SetPen(pen1);
6762
6763 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6764 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6765 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6766
6767 if (!m_pgridFont) SetupGridFont();
6768 dc.SetFont(*m_pgridFont);
6769 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6770 int w, h;
6771 dc.GetTextExtent(s, &w, &h);
6772 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6773 if (g_bopengl) {
6774 w /= dpi_factor;
6775 h /= dpi_factor;
6776 }
6777 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6778 }
6779}
6780
6781void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6782 // Constants?
6783 double da_min = 2.;
6784 double da_max = 6.;
6785 double ra_min = 0.;
6786 double ra_max = 40.;
6787
6788 wxPen pen_save = dc.GetPen();
6789
6790 wxDateTime now = wxDateTime::Now();
6791
6792 dc.SetPen(pen);
6793
6794 int x0, y0, x1, y1;
6795
6796 x0 = x1 = x + radius; // Start point
6797 y0 = y1 = y;
6798 double angle = 0.;
6799 int i = 0;
6800
6801 while (angle < 360.) {
6802 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6803 angle += da;
6804
6805 if (angle > 360.) angle = 360.;
6806
6807 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6808
6809 double r;
6810 if (i & 1)
6811 r = radius + ra;
6812 else
6813 r = radius - ra;
6814
6815 x1 = (int)(x + cos(angle * PI / 180.) * r);
6816 y1 = (int)(y + sin(angle * PI / 180.) * r);
6817
6818 dc.DrawLine(x0, y0, x1, y1);
6819
6820 x0 = x1;
6821 y0 = y1;
6822
6823 i++;
6824 }
6825
6826 dc.DrawLine(x + radius, y, x1, y1); // closure
6827
6828 dc.SetPen(pen_save);
6829}
6830
6831static bool bAnchorSoundPlaying = false;
6832
6833static void onAnchorSoundFinished(void *ptr) {
6834 o_sound::g_anchorwatch_sound->UnLoad();
6835 bAnchorSoundPlaying = false;
6836}
6837
6838void ChartCanvas::AlertDraw(ocpnDC &dc) {
6839 using namespace o_sound;
6840 // Visual and audio alert for anchorwatch goes here
6841 bool play_sound = false;
6842 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6843 if (AnchorAlertOn1) {
6844 wxPoint TargetPoint;
6846 &TargetPoint);
6847 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6848 TargetPoint.y, 100);
6849 play_sound = true;
6850 }
6851 } else
6852 AnchorAlertOn1 = false;
6853
6854 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6855 if (AnchorAlertOn2) {
6856 wxPoint TargetPoint;
6858 &TargetPoint);
6859 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6860 TargetPoint.y, 100);
6861 play_sound = true;
6862 }
6863 } else
6864 AnchorAlertOn2 = false;
6865
6866 if (play_sound) {
6867 if (!bAnchorSoundPlaying) {
6868 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6869 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6870 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6871 if (g_anchorwatch_sound->IsOk()) {
6872 bAnchorSoundPlaying = true;
6873 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6874 g_anchorwatch_sound->Play();
6875 }
6876 }
6877 }
6878}
6879
6880void ChartCanvas::UpdateShips() {
6881 // Get the rectangle in the current dc which bounds the "ownship" symbol
6882
6883 wxClientDC dc(this);
6884 if (!dc.IsOk()) return;
6885
6886 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6887 if (!test_bitmap.IsOk()) return;
6888
6889 wxMemoryDC temp_dc(test_bitmap);
6890
6891 temp_dc.ResetBoundingBox();
6892 temp_dc.DestroyClippingRegion();
6893 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6894
6895 // Draw the ownship on the temp_dc
6896 ocpnDC ocpndc = ocpnDC(temp_dc);
6897 ShipDraw(ocpndc);
6898
6899 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6900 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6901 if (p) {
6902 wxPoint px;
6903 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6904 ocpndc.CalcBoundingBox(px.x, px.y);
6905 }
6906 }
6907
6908 ship_draw_rect =
6909 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6910 temp_dc.MaxY() - temp_dc.MinY());
6911
6912 wxRect own_ship_update_rect = ship_draw_rect;
6913
6914 if (!own_ship_update_rect.IsEmpty()) {
6915 // The required invalidate rectangle is the union of the last drawn
6916 // rectangle and this drawn rectangle
6917 own_ship_update_rect.Union(ship_draw_last_rect);
6918 own_ship_update_rect.Inflate(2);
6919 }
6920
6921 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6922
6923 ship_draw_last_rect = ship_draw_rect;
6924
6925 temp_dc.SelectObject(wxNullBitmap);
6926}
6927
6928void ChartCanvas::UpdateAlerts() {
6929 // Get the rectangle in the current dc which bounds the detected Alert
6930 // targets
6931
6932 // Use this dc
6933 wxClientDC dc(this);
6934
6935 // Get dc boundary
6936 int sx, sy;
6937 dc.GetSize(&sx, &sy);
6938
6939 // Need a bitmap
6940 wxBitmap test_bitmap(sx, sy, -1);
6941
6942 // Create a memory DC
6943 wxMemoryDC temp_dc;
6944 temp_dc.SelectObject(test_bitmap);
6945
6946 temp_dc.ResetBoundingBox();
6947 temp_dc.DestroyClippingRegion();
6948 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6949
6950 // Draw the Alert Targets on the temp_dc
6951 ocpnDC ocpndc = ocpnDC(temp_dc);
6952 AlertDraw(ocpndc);
6953
6954 // Retrieve the drawing extents
6955 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6956 temp_dc.MaxX() - temp_dc.MinX(),
6957 temp_dc.MaxY() - temp_dc.MinY());
6958
6959 if (!alert_rect.IsEmpty())
6960 alert_rect.Inflate(2); // clear all drawing artifacts
6961
6962 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6963 // The required invalidate rectangle is the union of the last drawn
6964 // rectangle and this drawn rectangle
6965 wxRect alert_update_rect = alert_draw_rect;
6966 alert_update_rect.Union(alert_rect);
6967
6968 // Invalidate the rectangular region
6969 RefreshRect(alert_update_rect, false);
6970 }
6971
6972 // Save this rectangle for next time
6973 alert_draw_rect = alert_rect;
6974
6975 temp_dc.SelectObject(wxNullBitmap); // clean up
6976}
6977
6978void ChartCanvas::UpdateAIS() {
6979 if (!g_pAIS) return;
6980
6981 // Get the rectangle in the current dc which bounds the detected AIS targets
6982
6983 // Use this dc
6984 wxClientDC dc(this);
6985
6986 // Get dc boundary
6987 int sx, sy;
6988 dc.GetSize(&sx, &sy);
6989
6990 wxRect ais_rect;
6991
6992 // How many targets are there?
6993
6994 // If more than "some number", it will be cheaper to refresh the entire
6995 // screen than to build update rectangles for each target.
6996 if (g_pAIS->GetTargetList().size() > 10) {
6997 ais_rect = wxRect(0, 0, sx, sy); // full screen
6998 } else {
6999 // Need a bitmap
7000 wxBitmap test_bitmap(sx, sy, -1);
7001
7002 // Create a memory DC
7003 wxMemoryDC temp_dc;
7004 temp_dc.SelectObject(test_bitmap);
7005
7006 temp_dc.ResetBoundingBox();
7007 temp_dc.DestroyClippingRegion();
7008 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7009
7010 // Draw the AIS Targets on the temp_dc
7011 ocpnDC ocpndc = ocpnDC(temp_dc);
7012 AISDraw(ocpndc, GetVP(), this);
7013 AISDrawAreaNotices(ocpndc, GetVP(), this);
7014
7015 // Retrieve the drawing extents
7016 ais_rect =
7017 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7018 temp_dc.MaxY() - temp_dc.MinY());
7019
7020 if (!ais_rect.IsEmpty())
7021 ais_rect.Inflate(2); // clear all drawing artifacts
7022
7023 temp_dc.SelectObject(wxNullBitmap); // clean up
7024 }
7025
7026 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7027 // The required invalidate rectangle is the union of the last drawn
7028 // rectangle and this drawn rectangle
7029 wxRect ais_update_rect = ais_draw_rect;
7030 ais_update_rect.Union(ais_rect);
7031
7032 // Invalidate the rectangular region
7033 RefreshRect(ais_update_rect, false);
7034 }
7035
7036 // Save this rectangle for next time
7037 ais_draw_rect = ais_rect;
7038}
7039
7040void ChartCanvas::ToggleCPAWarn() {
7041 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7042 wxString mess;
7043 if (g_bCPAWarn) {
7044 g_bTCPA_Max = true;
7045 mess = _("ON");
7046 } else {
7047 g_bTCPA_Max = false;
7048 mess = _("OFF");
7049 }
7050 // Print to status bar if available.
7051 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7052 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7053 } else {
7054 if (!g_AisFirstTimeUse) {
7055 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7056 _("CPA") + " " + mess, 4, 4);
7057 }
7058 }
7059}
7060
7061void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7062
7063void ChartCanvas::OnSize(wxSizeEvent &event) {
7064 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7065 // GetClientSize returns the size of the canvas area in logical pixels.
7066 GetClientSize(&m_canvas_width, &m_canvas_height);
7067
7068#ifdef __WXOSX__
7069 // Support scaled HDPI displays.
7070 m_displayScale = GetContentScaleFactor();
7071#endif
7072
7073 // Convert to physical pixels.
7074 m_canvas_width *= m_displayScale;
7075 m_canvas_height *= m_displayScale;
7076
7077 // Resize the current viewport
7078 VPoint.pix_width = m_canvas_width;
7079 VPoint.pix_height = m_canvas_height;
7080 VPoint.SetPixelScale(m_displayScale);
7081
7082 // Get some canvas metrics
7083
7084 // Rescale to current value, in order to rebuild VPoint data
7085 // structures for new canvas size
7087
7088 m_absolute_min_scale_ppm =
7089 m_canvas_width /
7090 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7091
7092 // Inform the parent Frame that I am being resized...
7093 top_frame::Get()->ProcessCanvasResize();
7094
7095 // if MUIBar is active, size the bar
7096 // if(g_useMUI && !m_muiBar){ // rebuild if
7097 // necessary
7098 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7099 // m_muiBarHOSize = m_muiBar->GetSize();
7100 // }
7101
7102 if (m_muiBar) {
7103 SetMUIBarPosition();
7104 UpdateFollowButtonState();
7105 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7106 }
7107
7108 // Set up the scroll margins
7109 xr_margin = m_canvas_width * 95 / 100;
7110 xl_margin = m_canvas_width * 5 / 100;
7111 yt_margin = m_canvas_height * 5 / 100;
7112 yb_margin = m_canvas_height * 95 / 100;
7113
7114 if (m_pQuilt)
7115 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7116
7117 // Resize the scratch BM
7118 delete pscratch_bm;
7119 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7120 m_brepaint_piano = true;
7121
7122 // Resize the Route Calculation BM
7123 m_dc_route.SelectObject(wxNullBitmap);
7124 delete proute_bm;
7125 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7126 m_dc_route.SelectObject(*proute_bm);
7127
7128 // Resize the saved Bitmap
7129 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7130
7131 // Resize the working Bitmap
7132 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7133
7134 // Rescale again, to capture all the changes for new canvas size
7136
7137#ifdef ocpnUSE_GL
7138 if (/*g_bopengl &&*/ m_glcc) {
7139 // FIXME (dave) This can go away?
7140 m_glcc->OnSize(event);
7141 }
7142#endif
7143
7144 FormatPianoKeys();
7145 // Invalidate the whole window
7146 ReloadVP();
7147}
7148
7149void ChartCanvas::ProcessNewGUIScale() {
7150 // m_muiBar->Hide();
7151 delete m_muiBar;
7152 m_muiBar = 0;
7153
7154 CreateMUIBar();
7155}
7156
7157void ChartCanvas::CreateMUIBar() {
7158 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7159 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7160 m_muiBar->SetColorScheme(m_cs);
7161 m_muiBarHOSize = m_muiBar->m_size;
7162 }
7163
7164 if (m_muiBar) {
7165 // We need to update the m_bENCGroup flag, not least for the initial
7166 // creation of a MUIBar
7167 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7168
7169 SetMUIBarPosition();
7170 UpdateFollowButtonState();
7171 m_muiBar->UpdateDynamicValues();
7172 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7173 }
7174}
7175
7176void ChartCanvas::SetMUIBarPosition() {
7177 // if MUIBar is active, size the bar
7178 if (m_muiBar) {
7179 // We estimate the piano width based on the canvas width
7180 int pianoWidth = GetClientSize().x * 0.6f;
7181 // If the piano already exists, we can use its exact width
7182 // if(m_Piano)
7183 // pianoWidth = m_Piano->GetWidth();
7184
7185 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7186 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7187 delete m_muiBar;
7188 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7189 m_muiBar->SetColorScheme(m_cs);
7190 }
7191 }
7192
7193 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7194 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7195 delete m_muiBar;
7196 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7197 m_muiBar->SetColorScheme(m_cs);
7198 }
7199 }
7200
7201 m_muiBar->SetBestPosition();
7202 }
7203}
7204
7205void ChartCanvas::DestroyMuiBar() {
7206 if (m_muiBar) {
7207 delete m_muiBar;
7208 m_muiBar = NULL;
7209 }
7210}
7211
7212void ChartCanvas::ShowCompositeInfoWindow(
7213 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7214 if (n_charts > 0) {
7215 if (NULL == m_pCIWin) {
7216 m_pCIWin = new ChInfoWin(this);
7217 m_pCIWin->Hide();
7218 }
7219
7220 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7221 wxString s;
7222
7223 s = _("Composite of ");
7224
7225 wxString s1;
7226 s1.Printf("%d ", n_charts);
7227 if (n_charts > 1)
7228 s1 += _("charts");
7229 else
7230 s1 += _("chart");
7231 s += s1;
7232 s += '\n';
7233
7234 s1.Printf(_("Chart scale"));
7235 s1 += ": ";
7236 wxString s2;
7237 s2.Printf("1:%d\n", scale);
7238 s += s1;
7239 s += s2;
7240
7241 s1 = _("Zoom in for more information");
7242 s += s1;
7243 s += '\n';
7244
7245 int char_width = s1.Length();
7246 int char_height = 3;
7247
7248 if (g_bChartBarEx) {
7249 s += '\n';
7250 int j = 0;
7251 for (int i : index_vector) {
7252 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7253 wxString path = cte.GetFullSystemPath();
7254 s += path;
7255 s += '\n';
7256 char_height++;
7257 char_width = wxMax(char_width, path.Length());
7258 if (j++ >= 9) break;
7259 }
7260 if (j >= 9) {
7261 s += " .\n .\n .\n";
7262 char_height += 3;
7263 }
7264 s += '\n';
7265 char_height += 1;
7266
7267 char_width += 4; // Fluff
7268 }
7269
7270 m_pCIWin->SetString(s);
7271
7272 m_pCIWin->FitToChars(char_width, char_height);
7273
7274 wxPoint p;
7275 p.x = x / GetContentScaleFactor();
7276 if ((p.x + m_pCIWin->GetWinSize().x) >
7277 (m_canvas_width / GetContentScaleFactor()))
7278 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7279 m_pCIWin->GetWinSize().x) /
7280 2; // centered
7281
7282 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7283 4 - m_pCIWin->GetWinSize().y;
7284
7285 m_pCIWin->dbIndex = 0;
7286 m_pCIWin->chart_scale = 0;
7287 m_pCIWin->SetPosition(p);
7288 m_pCIWin->SetBitmap();
7289 m_pCIWin->Refresh();
7290 m_pCIWin->Show();
7291 }
7292 } else {
7293 HideChartInfoWindow();
7294 }
7295}
7296
7297void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7298 if (dbIndex >= 0) {
7299 if (NULL == m_pCIWin) {
7300 m_pCIWin = new ChInfoWin(this);
7301 m_pCIWin->Hide();
7302 }
7303
7304 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7305 wxString s;
7306 ChartBase *pc = NULL;
7307
7308 // TOCTOU race but worst case will reload chart.
7309 // need to lock it or the background spooler may evict charts in
7310 // OpenChartFromDBAndLock
7311 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7312 pc = ChartData->OpenChartFromDBAndLock(
7313 dbIndex, FULL_INIT); // this must come from cache
7314
7315 int char_width, char_height;
7316 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7317 if (pc) ChartData->UnLockCacheChart(dbIndex);
7318
7319 m_pCIWin->SetString(s);
7320 m_pCIWin->FitToChars(char_width, char_height);
7321
7322 wxPoint p;
7323 p.x = x / GetContentScaleFactor();
7324 if ((p.x + m_pCIWin->GetWinSize().x) >
7325 (m_canvas_width / GetContentScaleFactor()))
7326 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7327 m_pCIWin->GetWinSize().x) /
7328 2; // centered
7329
7330 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7331 4 - m_pCIWin->GetWinSize().y;
7332
7333 m_pCIWin->dbIndex = dbIndex;
7334 m_pCIWin->SetPosition(p);
7335 m_pCIWin->SetBitmap();
7336 m_pCIWin->Refresh();
7337 m_pCIWin->Show();
7338 }
7339 } else {
7340 HideChartInfoWindow();
7341 }
7342}
7343
7344void ChartCanvas::HideChartInfoWindow() {
7345 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7346 m_pCIWin->Hide();
7347 m_pCIWin->Destroy();
7348 m_pCIWin = NULL;
7349
7350#ifdef __ANDROID__
7351 androidForceFullRepaint();
7352#endif
7353 }
7354}
7355
7356void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7357 wxMouseEvent ev(wxEVT_MOTION);
7358 ev.m_x = mouse_x;
7359 ev.m_y = mouse_y;
7360 ev.m_leftDown = mouse_leftisdown;
7361
7362 wxEvtHandler *evthp = GetEventHandler();
7363
7364 ::wxPostEvent(evthp, ev);
7365}
7366
7367void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7368 if ((m_panx_target_final - m_panx_target_now) ||
7369 (m_pany_target_final - m_pany_target_now)) {
7370 DoTimedMovementTarget();
7371 } else
7372 DoTimedMovement();
7373}
7374
7375void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7376
7377bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7378 int delta) {
7379 if (m_disable_edge_pan) return false;
7380
7381 bool bft = false;
7382 int pan_margin = m_canvas_width * margin / 100;
7383 int pan_timer_set = 200;
7384 double pan_delta = GetVP().pix_width * delta / 100;
7385 int pan_x = 0;
7386 int pan_y = 0;
7387
7388 if (x > m_canvas_width - pan_margin) {
7389 bft = true;
7390 pan_x = pan_delta;
7391 }
7392
7393 else if (x < pan_margin) {
7394 bft = true;
7395 pan_x = -pan_delta;
7396 }
7397
7398 if (y < pan_margin) {
7399 bft = true;
7400 pan_y = -pan_delta;
7401 }
7402
7403 else if (y > m_canvas_height - pan_margin) {
7404 bft = true;
7405 pan_y = pan_delta;
7406 }
7407
7408 // Of course, if dragging, and the mouse left button is not down, we must
7409 // stop the event injection
7410 if (bdragging) {
7411 if (!g_btouch) {
7412 wxMouseState state = ::wxGetMouseState();
7413#if wxCHECK_VERSION(3, 0, 0)
7414 if (!state.LeftIsDown())
7415#else
7416 if (!state.LeftDown())
7417#endif
7418 bft = false;
7419 }
7420 }
7421 if ((bft) && !pPanTimer->IsRunning()) {
7422 PanCanvas(pan_x, pan_y);
7423 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7424 return true;
7425 }
7426
7427 // This mouse event must not be due to pan timer event injector
7428 // Mouse is out of the pan zone, so prevent any orphan event injection
7429 if ((!bft) && pPanTimer->IsRunning()) {
7430 pPanTimer->Stop();
7431 }
7432
7433 return (false);
7434}
7435
7436// Look for waypoints at the current position.
7437// Used to determine what a mouse event should act on.
7438
7439void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7440 bool setBeingEdited) {
7441 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7442 m_pRoutePointEditTarget = NULL;
7443 m_pFoundPoint = NULL;
7444
7445 SelectItem *pFind = NULL;
7446 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7447 SelectableItemList SelList = pSelect->FindSelectionList(
7448 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7449 for (SelectItem *pFind : SelList) {
7450 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7451
7452 // Get an array of all routes using this point
7453 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7454 // TODO: delete m_pEditRouteArray after use?
7455
7456 // Use route array to determine actual visibility for the point
7457 bool brp_viz = false;
7458 if (m_pEditRouteArray) {
7459 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7460 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7461 if (pr->IsVisible()) {
7462 brp_viz = true;
7463 break;
7464 }
7465 }
7466 } else
7467 brp_viz = frp->IsVisible(); // isolated point
7468
7469 if (brp_viz) {
7470 // Use route array to rubberband all affected routes
7471 if (m_pEditRouteArray) // Editing Waypoint as part of route
7472 {
7473 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7474 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7475 pr->m_bIsBeingEdited = setBeingEdited;
7476 }
7477 m_bRouteEditing = setBeingEdited;
7478 } else // editing Mark
7479 {
7480 frp->m_bRPIsBeingEdited = setBeingEdited;
7481 m_bMarkEditing = setBeingEdited;
7482 }
7483
7484 m_pRoutePointEditTarget = frp;
7485 m_pFoundPoint = pFind;
7486 break; // out of the while(node)
7487 }
7488 } // for (SelectItem...
7489}
7490std::shared_ptr<HostApi121::PiPointContext>
7491ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7492 // General Right Click
7493 // Look for selectable objects
7494 double slat, slon;
7495 GetCanvasPixPoint(x, y, slat, slon);
7496
7497 SelectItem *pFindAIS;
7498 SelectItem *pFindRP;
7499 SelectItem *pFindRouteSeg;
7500 SelectItem *pFindTrackSeg;
7501 SelectItem *pFindCurrent = NULL;
7502 SelectItem *pFindTide = NULL;
7503
7504 // Get all the selectable things at the selected point
7505 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7506 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7507 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7508 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7509 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7510
7511 if (m_bShowCurrent)
7512 pFindCurrent =
7513 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7514
7515 if (m_bShowTide) // look for tide stations
7516 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7517
7518 int seltype = 0;
7519
7520 // Try for AIS targets first
7521 int FoundAIS_MMSI = 0;
7522 if (pFindAIS) {
7523 FoundAIS_MMSI = pFindAIS->GetUserData();
7524
7525 // Make sure the target data is available
7526 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7527 seltype |= SELTYPE_AISTARGET;
7528 }
7529
7530 // Now the various Route Parts
7531
7532 RoutePoint *FoundRoutePoint = NULL;
7533 Route *SelectedRoute = NULL;
7534
7535 if (pFindRP) {
7536 RoutePoint *pFirstVizPoint = NULL;
7537 RoutePoint *pFoundActiveRoutePoint = NULL;
7538 RoutePoint *pFoundVizRoutePoint = NULL;
7539 Route *pSelectedActiveRoute = NULL;
7540 Route *pSelectedVizRoute = NULL;
7541
7542 // There is at least one routepoint, so get the whole list
7543 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7544 SelectableItemList SelList =
7545 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7546 for (SelectItem *pFindSel : SelList) {
7547 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7548
7549 // Get an array of all routes using this point
7550 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7551
7552 // Use route array (if any) to determine actual visibility for this point
7553 bool brp_viz = false;
7554 if (proute_array) {
7555 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7556 Route *pr = (Route *)proute_array->Item(ir);
7557 if (pr->IsVisible()) {
7558 brp_viz = true;
7559 break;
7560 }
7561 }
7562 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7563 // but still exists as a waypoint
7564 brp_viz = prp->IsVisible(); // so treat as isolated point
7565
7566 } else
7567 brp_viz = prp->IsVisible(); // isolated point
7568
7569 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7570
7571 // Use route array to choose the appropriate route
7572 // Give preference to any active route, otherwise select the first visible
7573 // route in the array for this point
7574 if (proute_array) {
7575 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7576 Route *pr = (Route *)proute_array->Item(ir);
7577 if (pr->m_bRtIsActive) {
7578 pSelectedActiveRoute = pr;
7579 pFoundActiveRoutePoint = prp;
7580 break;
7581 }
7582 }
7583
7584 if (NULL == pSelectedVizRoute) {
7585 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7586 Route *pr = (Route *)proute_array->Item(ir);
7587 if (pr->IsVisible()) {
7588 pSelectedVizRoute = pr;
7589 pFoundVizRoutePoint = prp;
7590 break;
7591 }
7592 }
7593 }
7594
7595 delete proute_array;
7596 }
7597 }
7598
7599 // Now choose the "best" selections
7600 if (pFoundActiveRoutePoint) {
7601 FoundRoutePoint = pFoundActiveRoutePoint;
7602 SelectedRoute = pSelectedActiveRoute;
7603 } else if (pFoundVizRoutePoint) {
7604 FoundRoutePoint = pFoundVizRoutePoint;
7605 SelectedRoute = pSelectedVizRoute;
7606 } else
7607 // default is first visible point in list
7608 FoundRoutePoint = pFirstVizPoint;
7609
7610 if (SelectedRoute) {
7611 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7612 } else if (FoundRoutePoint) {
7613 seltype |= SELTYPE_MARKPOINT;
7614 }
7615
7616 // Highlight the selected point, to verify the proper right click selection
7617#if 0
7618 if (m_pFoundRoutePoint) {
7619 m_pFoundRoutePoint->m_bPtIsSelected = true;
7620 wxRect wp_rect;
7621 RoutePointGui(*m_pFoundRoutePoint)
7622 .CalculateDCRect(m_dc_route, this, &wp_rect);
7623 RefreshRect(wp_rect, true);
7624 }
7625#endif
7626 }
7627
7628 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7629 // routes But call the popup handler with identifier appropriate to the type
7630 if (pFindRouteSeg) // there is at least one select item
7631 {
7632 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7633 SelectableItemList SelList =
7634 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7635
7636 if (NULL == SelectedRoute) // the case where a segment only is selected
7637 {
7638 // Choose the first visible route containing segment in the list
7639 for (SelectItem *pFindSel : SelList) {
7640 Route *pr = (Route *)pFindSel->m_pData3;
7641 if (pr->IsVisible()) {
7642 SelectedRoute = pr;
7643 break;
7644 }
7645 }
7646 }
7647
7648 if (SelectedRoute) {
7649 if (NULL == FoundRoutePoint)
7650 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7651
7652 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7653 seltype |= SELTYPE_ROUTESEGMENT;
7654 }
7655 }
7656
7657 if (pFindTrackSeg) {
7658 m_pSelectedTrack = NULL;
7659 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7660 SelectableItemList SelList =
7661 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7662
7663 // Choose the first visible track containing segment in the list
7664 for (SelectItem *pFindSel : SelList) {
7665 Track *pt = (Track *)pFindSel->m_pData3;
7666 if (pt->IsVisible()) {
7667 m_pSelectedTrack = pt;
7668 break;
7669 }
7670 }
7671 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7672 }
7673
7674 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7675
7676 // Populate the return struct
7677 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7678 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7679 rstruct->object_ident = "";
7680
7681 if (seltype == SELTYPE_AISTARGET) {
7682 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7683 wxString val;
7684 val.Printf("%d", FoundAIS_MMSI);
7685 rstruct->object_ident = val.ToStdString();
7686 } else if (seltype & SELTYPE_MARKPOINT) {
7687 if (FoundRoutePoint) {
7688 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7689 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7690 }
7691 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7692 if (SelectedRoute) {
7693 rstruct->object_type =
7694 HostApi121::PiContextObjectType::kObjectRoutesegment;
7695 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7696 }
7697 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7698 if (m_pSelectedTrack) {
7699 rstruct->object_type =
7700 HostApi121::PiContextObjectType::kObjectTracksegment;
7701 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7702 }
7703 }
7704
7705 return rstruct;
7706}
7707
7708void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7709 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7710 singleClickEventIsValid = false;
7711 m_DoubleClickTimer->Stop();
7712}
7713
7714bool leftIsDown;
7715
7716bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7717 if (!m_bChartDragging && !m_bDrawingRoute) {
7718 /*
7719 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7720 * mouse event coordinates are in logical pixels.
7721 */
7722 if (m_Compass && m_Compass->IsShown()) {
7723 wxRect logicalRect = m_Compass->GetLogicalRect();
7724 bool isInCompass = logicalRect.Contains(event.GetPosition());
7725 if (isInCompass || m_mouseWasInCompass) {
7726 if (m_Compass->MouseEvent(event)) {
7727 cursor_region = CENTER;
7728 if (!g_btouch) SetCanvasCursor(event);
7729 m_mouseWasInCompass = isInCompass;
7730 return true;
7731 }
7732 }
7733 m_mouseWasInCompass = isInCompass;
7734 }
7735
7736 if (m_notification_button && m_notification_button->IsShown()) {
7737 wxRect logicalRect = m_notification_button->GetLogicalRect();
7738 bool isinButton = logicalRect.Contains(event.GetPosition());
7739 if (isinButton) {
7740 SetCursor(*pCursorArrow);
7741 if (event.LeftDown()) HandleNotificationMouseClick();
7742 return true;
7743 }
7744 }
7745
7746 if (MouseEventToolbar(event)) return true;
7747
7748 if (MouseEventChartBar(event)) return true;
7749
7750 if (MouseEventMUIBar(event)) return true;
7751
7752 if (MouseEventIENCBar(event)) return true;
7753 }
7754 return false;
7755}
7756
7757void ChartCanvas::HandleNotificationMouseClick() {
7758 if (!m_NotificationsList) {
7759 m_NotificationsList = new NotificationsList(this);
7760
7761 // calculate best size for Notification list
7762 m_NotificationsList->RecalculateSize();
7763 m_NotificationsList->Hide();
7764 }
7765
7766 if (m_NotificationsList->IsShown()) {
7767 m_NotificationsList->Hide();
7768 } else {
7769 m_NotificationsList->RecalculateSize();
7770 m_NotificationsList->ReloadNotificationList();
7771 m_NotificationsList->Show();
7772 }
7773}
7774bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7775 if (!g_bShowChartBar) return false;
7776
7777 if (!m_Piano->MouseEvent(event)) return false;
7778
7779 cursor_region = CENTER;
7780 if (!g_btouch) SetCanvasCursor(event);
7781 return true;
7782}
7783
7784bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7785 if (!IsPrimaryCanvas()) return false;
7786
7787 if (g_MainToolbar) {
7788 if (!g_MainToolbar->MouseEvent(event))
7789 return false;
7790 else
7791 g_MainToolbar->RefreshToolbar();
7792 }
7793
7794 cursor_region = CENTER;
7795 if (!g_btouch) SetCanvasCursor(event);
7796 return true;
7797}
7798
7799bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7800 if (!IsPrimaryCanvas()) return false;
7801
7802 if (g_iENCToolbar) {
7803 if (!g_iENCToolbar->MouseEvent(event))
7804 return false;
7805 else {
7806 g_iENCToolbar->RefreshToolbar();
7807 return true;
7808 }
7809 }
7810 return false;
7811}
7812
7813bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7814 if (m_muiBar) {
7815 if (!m_muiBar->MouseEvent(event)) return false;
7816 }
7817
7818 cursor_region = CENTER;
7819 if (!g_btouch) SetCanvasCursor(event);
7820 if (m_muiBar)
7821 return true;
7822 else
7823 return false;
7824}
7825
7826bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7827 int x, y;
7828
7829 bool bret = false;
7830
7831 event.GetPosition(&x, &y);
7832
7833 x *= m_displayScale;
7834 y *= m_displayScale;
7835
7836 m_MouseDragging = event.Dragging();
7837
7838 // Some systems produce null drag events, where the pointer position has not
7839 // changed from the previous value. Detect this case, and abort further
7840 // processing (FS#1748)
7841#ifdef __WXMSW__
7842 if (event.Dragging()) {
7843 if ((x == mouse_x) && (y == mouse_y)) return true;
7844 }
7845#endif
7846
7847 mouse_x = x;
7848 mouse_y = y;
7849 mouse_leftisdown = event.LeftDown();
7851
7852 // Establish the event region
7853 cursor_region = CENTER;
7854
7855 int chartbar_height = GetChartbarHeight();
7856
7857 if (m_Compass && m_Compass->IsShown() &&
7858 m_Compass->GetRect().Contains(event.GetPosition())) {
7859 cursor_region = CENTER;
7860 } else if (x > xr_margin) {
7861 cursor_region = MID_RIGHT;
7862 } else if (x < xl_margin) {
7863 cursor_region = MID_LEFT;
7864 } else if (y > yb_margin - chartbar_height &&
7865 y < m_canvas_height - chartbar_height) {
7866 cursor_region = MID_TOP;
7867 } else if (y < yt_margin) {
7868 cursor_region = MID_BOT;
7869 } else {
7870 cursor_region = CENTER;
7871 }
7872
7873 if (!g_btouch) SetCanvasCursor(event);
7874
7875 // Protect from leftUp's coming from event handlers in child
7876 // windows who return focus to the canvas.
7877 leftIsDown = event.LeftDown();
7878
7879#ifndef __WXOSX__
7880 if (event.LeftDown()) {
7881 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7882 // The menu bar is temporarily visible due to alt having been pressed.
7883 // Clicking will hide it, and do nothing else.
7884 g_bTempShowMenuBar = false;
7885 top_frame::Get()->ApplyGlobalSettings(false);
7886 return (true);
7887 }
7888 }
7889#endif
7890
7891 // Update modifiers here; some window managers never send the key event
7892 m_modkeys = 0;
7893 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7894 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7895
7896#ifdef __WXMSW__
7897 // TODO Test carefully in other platforms, remove ifdef....
7898 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7899 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7900#endif
7901
7902 event.SetEventObject(this);
7903 if (SendMouseEventToPlugins(event))
7904 return (true); // PlugIn did something, and does not want the canvas to
7905 // do anything else
7906
7907 // Capture LeftUp's and time them, unless it already came from the timer.
7908
7909 // Detect end of chart dragging
7910 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7911 StartChartDragInertia();
7912 }
7913
7914 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7915 !singleClickEventIsValid) {
7916 // Ignore the second LeftUp after the DClick.
7917 if (m_DoubleClickTimer->IsRunning()) {
7918 m_DoubleClickTimer->Stop();
7919 return (true);
7920 }
7921
7922 // Save the event for later running if there is no DClick.
7923 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7924 singleClickEvent = event;
7925 singleClickEventIsValid = true;
7926 return (true);
7927 }
7928
7929 // This logic is necessary on MSW to handle the case where
7930 // a context (right-click) menu is dismissed without action
7931 // by clicking on the chart surface.
7932 // We need to avoid an unintentional pan by eating some clicks...
7933#ifdef __WXMSW__
7934 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7935 if (g_click_stop > 0) {
7936 g_click_stop--;
7937 return (true);
7938 }
7939 }
7940#endif
7941
7942 // Kick off the Rotation control timer
7943 if (GetUpMode() == COURSE_UP_MODE) {
7944 m_b_rot_hidef = false;
7945 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7946 } else
7947 pRotDefTimer->Stop();
7948
7949 // Retrigger the route leg / AIS target popup timer
7950 bool bRoll = !g_btouch;
7951#ifdef __ANDROID__
7952 bRoll = g_bRollover;
7953#endif
7954 if (bRoll) {
7955 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7956 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7957 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7958 m_RolloverPopupTimer.Start(
7959 10,
7960 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7961 else
7962 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7963 }
7964
7965 // Retrigger the cursor tracking timer
7966 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7967
7968// Show cursor position on Status Bar, if present
7969// except for GTK, under which status bar updates are very slow
7970// due to Update() call.
7971// In this case, as a workaround, update the status window
7972// after an interval timer (pCurTrackTimer) pops, which will happen
7973// whenever the mouse has stopped moving for specified interval.
7974// See the method OnCursorTrackTimerEvent()
7975#if !defined(__WXGTK__) && !defined(__WXQT__)
7976 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7977#endif
7978
7979 // Send the current cursor lat/lon to all PlugIns requesting it
7980 if (g_pi_manager) {
7981 // Occasionally, MSW will produce nonsense events on right click....
7982 // This results in an error in cursor geo position, so we skip this case
7983 if ((x >= 0) && (y >= 0))
7984 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7985 }
7986
7987 if (!g_btouch) {
7988 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7989 wxPoint p = ClientToScreen(wxPoint(x, y));
7990 }
7991 }
7992
7993 if (1 ) {
7994 // Route Creation Rubber Banding
7995 if (m_routeState >= 2) {
7996 r_rband.x = x;
7997 r_rband.y = y;
7998 m_bDrawingRoute = true;
7999
8000 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8001 Refresh(false);
8002 }
8003
8004 // Measure Tool Rubber Banding
8005 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8006 r_rband.x = x;
8007 r_rband.y = y;
8008 m_bDrawingRoute = true;
8009
8010 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8011 Refresh(false);
8012 }
8013 }
8014 return bret;
8015}
8016
8017int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8018 // On general Right Click
8019 // Look for selectable objects
8020 double slat = lat;
8021 double slon = lon;
8022
8023#if defined(__WXMAC__) || defined(__ANDROID__)
8024 wxScreenDC sdc;
8025 ocpnDC dc(sdc);
8026#else
8027 wxClientDC cdc(GetParent());
8028 ocpnDC dc(cdc);
8029#endif
8030
8031 SelectItem *pFindAIS;
8032 SelectItem *pFindRP;
8033 SelectItem *pFindRouteSeg;
8034 SelectItem *pFindTrackSeg;
8035 SelectItem *pFindCurrent = NULL;
8036 SelectItem *pFindTide = NULL;
8037
8038 // Deselect any current objects
8039 if (m_pSelectedRoute) {
8040 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8041 m_pSelectedRoute->DeSelectRoute();
8042#ifdef ocpnUSE_GL
8043 if (g_bopengl && m_glcc) {
8044 InvalidateGL();
8045 Update();
8046 } else
8047#endif
8048 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8049 }
8050
8051 if (m_pFoundRoutePoint) {
8052 m_pFoundRoutePoint->m_bPtIsSelected = false;
8053 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8054 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8055 }
8056
8059 if (g_btouch && m_pRoutePointEditTarget) {
8060 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8061 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8062 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8063 }
8064
8065 // Get all the selectable things at the cursor
8066 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8067 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8068 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8069 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8070 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8071
8072 if (m_bShowCurrent)
8073 pFindCurrent =
8074 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8075
8076 if (m_bShowTide) // look for tide stations
8077 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8078
8079 int seltype = 0;
8080
8081 // Try for AIS targets first
8082 if (pFindAIS) {
8083 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8084
8085 // Make sure the target data is available
8086 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8087 seltype |= SELTYPE_AISTARGET;
8088 }
8089
8090 // Now examine the various Route parts
8091
8092 m_pFoundRoutePoint = NULL;
8093 if (pFindRP) {
8094 RoutePoint *pFirstVizPoint = NULL;
8095 RoutePoint *pFoundActiveRoutePoint = NULL;
8096 RoutePoint *pFoundVizRoutePoint = NULL;
8097 Route *pSelectedActiveRoute = NULL;
8098 Route *pSelectedVizRoute = NULL;
8099
8100 // There is at least one routepoint, so get the whole list
8101 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8102 SelectableItemList SelList =
8103 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8104 for (SelectItem *pFindSel : SelList) {
8105 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8106
8107 // Get an array of all routes using this point
8108 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8109
8110 // Use route array (if any) to determine actual visibility for this point
8111 bool brp_viz = false;
8112 if (proute_array) {
8113 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8114 Route *pr = (Route *)proute_array->Item(ir);
8115 if (pr->IsVisible()) {
8116 brp_viz = true;
8117 break;
8118 }
8119 }
8120 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8121 // but still exists as a waypoint
8122 brp_viz = prp->IsVisible(); // so treat as isolated point
8123
8124 } else
8125 brp_viz = prp->IsVisible(); // isolated point
8126
8127 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8128
8129 // Use route array to choose the appropriate route
8130 // Give preference to any active route, otherwise select the first visible
8131 // route in the array for this point
8132 m_pSelectedRoute = NULL;
8133 if (proute_array) {
8134 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8135 Route *pr = (Route *)proute_array->Item(ir);
8136 if (pr->m_bRtIsActive) {
8137 pSelectedActiveRoute = pr;
8138 pFoundActiveRoutePoint = prp;
8139 break;
8140 }
8141 }
8142
8143 if (NULL == pSelectedVizRoute) {
8144 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8145 Route *pr = (Route *)proute_array->Item(ir);
8146 if (pr->IsVisible()) {
8147 pSelectedVizRoute = pr;
8148 pFoundVizRoutePoint = prp;
8149 break;
8150 }
8151 }
8152 }
8153
8154 delete proute_array;
8155 }
8156 }
8157
8158 // Now choose the "best" selections
8159 if (pFoundActiveRoutePoint) {
8160 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8161 m_pSelectedRoute = pSelectedActiveRoute;
8162 } else if (pFoundVizRoutePoint) {
8163 m_pFoundRoutePoint = pFoundVizRoutePoint;
8164 m_pSelectedRoute = pSelectedVizRoute;
8165 } else
8166 // default is first visible point in list
8167 m_pFoundRoutePoint = pFirstVizPoint;
8168
8169 if (m_pSelectedRoute) {
8170 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8171 } else if (m_pFoundRoutePoint) {
8172 seltype |= SELTYPE_MARKPOINT;
8173 }
8174
8175 // Highlight the selected point, to verify the proper right click selection
8176 if (m_pFoundRoutePoint) {
8177 m_pFoundRoutePoint->m_bPtIsSelected = true;
8178 wxRect wp_rect;
8179 RoutePointGui(*m_pFoundRoutePoint)
8180 .CalculateDCRect(m_dc_route, this, &wp_rect);
8181 RefreshRect(wp_rect, true);
8182 }
8183 }
8184
8185 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8186 // routes But call the popup handler with identifier appropriate to the type
8187 if (pFindRouteSeg) // there is at least one select item
8188 {
8189 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8190 SelectableItemList SelList =
8191 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8192
8193 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8194 {
8195 // Choose the first visible route containing segment in the list
8196 for (SelectItem *pFindSel : SelList) {
8197 Route *pr = (Route *)pFindSel->m_pData3;
8198 if (pr->IsVisible()) {
8199 m_pSelectedRoute = pr;
8200 break;
8201 }
8202 }
8203 }
8204
8205 if (m_pSelectedRoute) {
8206 if (NULL == m_pFoundRoutePoint)
8207 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8208
8209 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8210 if (m_pSelectedRoute->m_bRtIsSelected) {
8211#ifdef ocpnUSE_GL
8212 if (g_bopengl && m_glcc) {
8213 InvalidateGL();
8214 Update();
8215 } else
8216#endif
8217 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8218 }
8219 seltype |= SELTYPE_ROUTESEGMENT;
8220 }
8221 }
8222
8223 if (pFindTrackSeg) {
8224 m_pSelectedTrack = NULL;
8225 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8226 SelectableItemList SelList =
8227 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8228
8229 // Choose the first visible track containing segment in the list
8230 for (SelectItem *pFindSel : SelList) {
8231 Track *pt = (Track *)pFindSel->m_pData3;
8232 if (pt->IsVisible()) {
8233 m_pSelectedTrack = pt;
8234 break;
8235 }
8236 }
8237 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8238 }
8239
8240#if 0 // disable tide and current graph on right click
8241 {
8242 if (pFindCurrent) {
8243 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8244 seltype |= SELTYPE_CURRENTPOINT;
8245 }
8246
8247 else if (pFindTide) {
8248 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8249 seltype |= SELTYPE_TIDEPOINT;
8250 }
8251 }
8252#endif
8253
8254 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8255
8256 return seltype;
8257}
8258
8259IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8260 // There may be multiple current entries at the same point.
8261 // For example, there often is a current substation (with directions
8262 // specified) co-located with its master. We want to select the
8263 // substation, so that the direction will be properly indicated on the
8264 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8265 // substation)
8266 IDX_entry *pIDX_best_candidate;
8267
8268 SelectItem *pFind = NULL;
8269 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8270 SelectableItemList SelList =
8271 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8272
8273 // Default is first entry
8274 pFind = *SelList.begin();
8275 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8276
8277 auto node = SelList.begin();
8278 if (SelList.size() > 1) {
8279 for (++node; node != SelList.end(); ++node) {
8280 pFind = *node;
8281 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8282 if (pIDX_candidate->IDX_type == 'c') {
8283 pIDX_best_candidate = pIDX_candidate;
8284 break;
8285 }
8286 } // while (node)
8287 } else {
8288 pFind = *SelList.begin();
8289 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8290 }
8291
8292 return pIDX_best_candidate;
8293}
8294void ChartCanvas::CallPopupMenu(int x, int y) {
8295 last_drag.x = x;
8296 last_drag.y = y;
8297 if (m_routeState) { // creating route?
8298 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8299 return;
8300 }
8301
8303
8304 // If tide or current point is selected, then show the TC dialog immediately
8305 // without context menu
8306 if (SELTYPE_CURRENTPOINT == seltype) {
8307 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8308 Refresh(false);
8309 return;
8310 }
8311
8312 if (SELTYPE_TIDEPOINT == seltype) {
8313 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8314 Refresh(false);
8315 return;
8316 }
8317
8318 InvokeCanvasMenu(x, y, seltype);
8319
8320 // Clean up if not deleted in InvokeCanvasMenu
8321 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8322 m_pSelectedRoute->m_bRtIsSelected = false;
8323 }
8324
8325 m_pSelectedRoute = NULL;
8326
8327 if (m_pFoundRoutePoint) {
8328 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8329 m_pFoundRoutePoint->m_bPtIsSelected = false;
8330 }
8331 m_pFoundRoutePoint = NULL;
8332
8333 Refresh(true);
8334 // Refresh(false); // needed for MSW, not GTK Why??
8335}
8336
8337bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8338 // For now just bail out completely if the point clicked is not on the chart
8339 if (std::isnan(m_cursor_lat)) return false;
8340
8341 // Mouse Clicks
8342 bool ret = false; // return true if processed
8343
8344 int x, y, mx, my;
8345 event.GetPosition(&x, &y);
8346 mx = x;
8347 my = y;
8348
8349 // Calculate meaningful SelectRadius
8350 float SelectRadius;
8351 SelectRadius = g_Platform->GetSelectRadiusPix() /
8352 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8353
8355 // We start with Double Click processing. The first left click just starts a
8356 // timer and is remembered, then we actually do something if there is a
8357 // LeftDClick. If there is, the two single clicks are ignored.
8358
8359 if (event.LeftDClick() && (cursor_region == CENTER)) {
8360 m_DoubleClickTimer->Start();
8361 singleClickEventIsValid = false;
8362
8363 double zlat, zlon;
8365 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8366
8367 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8368 if (m_bShowAIS) {
8369 SelectItem *pFindAIS;
8370 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8371
8372 if (pFindAIS) {
8373 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8374 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8375 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8376 }
8377 return true;
8378 }
8379 }
8380
8381 SelectableItemList rpSelList =
8382 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8383 bool b_onRPtarget = false;
8384 for (SelectItem *pFind : rpSelList) {
8385 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8386 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8387 b_onRPtarget = true;
8388 break;
8389 }
8390 }
8391
8392 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8393
8394 // Get and honor the plugin API ContextMenuMask
8395 std::unique_ptr<HostApi> host_api = GetHostApi();
8396 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8397
8398 if (m_pRoutePointEditTarget) {
8399 if (b_onRPtarget) {
8400 if ((api_121->GetContextMenuMask() &
8401 api_121->kContextMenuDisableWaypoint))
8402 return true;
8403 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8404 return true;
8405 } else {
8406 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8407 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8408 if (g_btouch)
8409 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8410 wxRect wp_rect;
8411 RoutePointGui(*m_pRoutePointEditTarget)
8412 .CalculateDCRect(m_dc_route, this, &wp_rect);
8413 m_pRoutePointEditTarget = NULL; // cancel selection
8414 RefreshRect(wp_rect, true);
8415 return true;
8416 }
8417 } else {
8418 auto node = rpSelList.begin();
8419 if (node != rpSelList.end()) {
8420 SelectItem *pFind = *node;
8421 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8422 if (frp) {
8423 wxArrayPtrVoid *proute_array =
8425
8426 // Use route array (if any) to determine actual visibility for this
8427 // point
8428 bool brp_viz = false;
8429 if (proute_array) {
8430 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8431 Route *pr = (Route *)proute_array->Item(ir);
8432 if (pr->IsVisible()) {
8433 brp_viz = true;
8434 break;
8435 }
8436 }
8437 delete proute_array;
8438 if (!brp_viz &&
8439 frp->IsShared()) // is not visible as part of route, but
8440 // still exists as a waypoint
8441 brp_viz = frp->IsVisible(); // so treat as isolated point
8442 } else
8443 brp_viz = frp->IsVisible(); // isolated point
8444
8445 if (brp_viz) {
8446 if ((api_121->GetContextMenuMask() &
8447 api_121->kContextMenuDisableWaypoint))
8448 return true;
8449
8450 ShowMarkPropertiesDialog(frp);
8451 return true;
8452 }
8453 }
8454 }
8455 }
8456
8457 SelectItem *cursorItem;
8458
8459 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8460 if (cursorItem) {
8461 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8462 return true;
8463 Route *pr = (Route *)cursorItem->m_pData3;
8464 if (pr->IsVisible()) {
8465 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8466 return true;
8467 }
8468 }
8469
8470 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8471 if (cursorItem) {
8472 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8473 return true;
8474 Track *pt = (Track *)cursorItem->m_pData3;
8475 if (pt->IsVisible()) {
8476 ShowTrackPropertiesDialog(pt);
8477 return true;
8478 }
8479 }
8480
8481 // Tide and current points
8482 SelectItem *pFindCurrent = NULL;
8483 SelectItem *pFindTide = NULL;
8484
8485 if (m_bShowCurrent) { // look for current stations
8486 pFindCurrent =
8487 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8488 if (pFindCurrent) {
8489 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8490 // Check for plugin graphic override
8491 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8492 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8493 PlugInContainer *pic = plugin_array->Item(i);
8494 if (pic->m_enabled && pic->m_init_state &&
8495 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8496 if (ptcmgr) {
8497 TCClickInfo info;
8498 if (m_pIDXCandidate) {
8499 info.point_type = CURRENT_STATION;
8500 info.index = m_pIDXCandidate->IDX_rec_num;
8501 info.name = m_pIDXCandidate->IDX_station_name;
8502 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8503 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8504 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8505 };
8506 auto plugin =
8507 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8508 if (plugin) plugin->OnTideCurrentClick(info);
8509 return true;
8510 }
8511 }
8512 }
8513 }
8514
8515 // Default action
8516 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8517 Refresh(false);
8518 return true;
8519 }
8520 }
8521
8522 if (m_bShowTide) { // look for tide stations
8523 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8524 if (pFindTide) {
8525 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8526 // Check for plugin graphic override
8527 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8528 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8529 PlugInContainer *pic = plugin_array->Item(i);
8530 if (pic->m_enabled && pic->m_init_state &&
8531 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8532 if (ptcmgr) {
8533 TCClickInfo info;
8534 if (m_pIDXCandidate) {
8535 info.point_type = TIDE_STATION;
8536 info.index = m_pIDXCandidate->IDX_rec_num;
8537 info.name = m_pIDXCandidate->IDX_station_name;
8538 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8539 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8540 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8541 };
8542 auto plugin =
8543 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8544 if (plugin) plugin->OnTideCurrentClick(info);
8545 return true;
8546 }
8547 }
8548 }
8549 }
8550
8551 // Default action
8552 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8553 Refresh(false);
8554 return true;
8555 }
8556 }
8557
8558 // Found no object to act on, so show chart info.
8559 ShowObjectQueryWindow(x, y, zlat, zlon);
8560 return true;
8561 }
8562
8564 if (event.LeftDown()) {
8565 // This really should not be needed, but....
8566 // on Windows, when using wxAUIManager, sometimes the focus is lost
8567 // when clicking into another pane, e.g.the AIS target list, and then back
8568 // to this pane. Oddly, some mouse events are not lost, however. Like this
8569 // one....
8570 SetFocus();
8571
8572 last_drag.x = mx;
8573 last_drag.y = my;
8574 leftIsDown = true;
8575
8576 if (!g_btouch) {
8577 if (m_routeState) // creating route?
8578 {
8579 double rlat, rlon;
8580 bool appending = false;
8581 bool inserting = false;
8582 Route *tail = 0;
8583
8584 SetCursor(*pCursorPencil);
8585 rlat = m_cursor_lat;
8586 rlon = m_cursor_lon;
8587
8588 m_bRouteEditing = true;
8589
8590 if (m_routeState == 1) {
8591 m_pMouseRoute = new Route();
8592 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8593 pRouteList->push_back(m_pMouseRoute);
8594 r_rband.x = x;
8595 r_rband.y = y;
8596 }
8597
8598 // Check to see if there is a nearby point which may be reused
8599 RoutePoint *pMousePoint = NULL;
8600
8601 // Calculate meaningful SelectRadius
8602 double nearby_radius_meters =
8603 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8604
8605 RoutePoint *pNearbyPoint =
8606 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8607 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8608 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8609 wxArrayPtrVoid *proute_array =
8610 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8611
8612 // Use route array (if any) to determine actual visibility for this
8613 // point
8614 bool brp_viz = false;
8615 if (proute_array) {
8616 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8617 Route *pr = (Route *)proute_array->Item(ir);
8618 if (pr->IsVisible()) {
8619 brp_viz = true;
8620 break;
8621 }
8622 }
8623 delete proute_array;
8624 if (!brp_viz &&
8625 pNearbyPoint->IsShared()) // is not visible as part of route,
8626 // but still exists as a waypoint
8627 brp_viz =
8628 pNearbyPoint->IsVisible(); // so treat as isolated point
8629 } else
8630 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8631
8632 if (brp_viz) {
8633 wxString msg = _("Use nearby waypoint?");
8634 // Don't add a mark without name to the route. Name it if needed
8635 const bool noname(pNearbyPoint->GetName() == "");
8636 if (noname) {
8637 msg =
8638 _("Use nearby nameless waypoint and name it M with"
8639 " a unique number?");
8640 }
8641 // Avoid route finish on focus change for message dialog
8642 m_FinishRouteOnKillFocus = false;
8643 int dlg_return =
8644 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8645 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8646 m_FinishRouteOnKillFocus = true;
8647 if (dlg_return == wxID_YES) {
8648 if (noname) {
8649 if (m_pMouseRoute) {
8650 int last_wp_num = m_pMouseRoute->GetnPoints();
8651 // AP-ECRMB will truncate to 6 characters
8652 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8653 wxString wp_name = wxString::Format(
8654 "M%002i-%s", last_wp_num + 1, guid_short);
8655 pNearbyPoint->SetName(wp_name);
8656 } else
8657 pNearbyPoint->SetName("WPXX");
8658 }
8659 pMousePoint = pNearbyPoint;
8660
8661 // Using existing waypoint, so nothing to delete for undo.
8662 if (m_routeState > 1)
8663 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8664 Undo_HasParent, NULL);
8665
8666 tail =
8667 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8668 bool procede = false;
8669 if (tail) {
8670 procede = true;
8671 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8672 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8673 procede = false;
8674 }
8675
8676 if (procede) {
8677 int dlg_return;
8678 m_FinishRouteOnKillFocus = false;
8679 if (m_routeState ==
8680 1) { // first point in new route, preceeding route to be
8681 // added? Not touch case
8682
8683 wxString dmsg =
8684 _("Insert first part of this route in the new route?");
8685 if (tail->GetIndexOf(pMousePoint) ==
8686 tail->GetnPoints()) // Starting on last point of another
8687 // route?
8688 dmsg = _("Insert this route in the new route?");
8689
8690 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8691 dlg_return = OCPNMessageBox(
8692 this, dmsg, _("OpenCPN Route Create"),
8693 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8694 m_FinishRouteOnKillFocus = true;
8695
8696 if (dlg_return == wxID_YES) {
8697 inserting = true; // part of the other route will be
8698 // preceeding the new route
8699 }
8700 }
8701 } else {
8702 wxString dmsg =
8703 _("Append last part of this route to the new route?");
8704 if (tail->GetIndexOf(pMousePoint) == 1)
8705 dmsg = _(
8706 "Append this route to the new route?"); // Picking the
8707 // first point
8708 // of another
8709 // route?
8710
8711 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8712 dlg_return = OCPNMessageBox(
8713 this, dmsg, _("OpenCPN Route Create"),
8714 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8715 m_FinishRouteOnKillFocus = true;
8716
8717 if (dlg_return == wxID_YES) {
8718 appending = true; // part of the other route will be
8719 // appended to the new route
8720 }
8721 }
8722 }
8723 }
8724
8725 // check all other routes to see if this point appears in any
8726 // other route If it appears in NO other route, then it should e
8727 // considered an isolated mark
8728 if (!FindRouteContainingWaypoint(pMousePoint))
8729 pMousePoint->SetShared(true);
8730 }
8731 }
8732 }
8733
8734 if (NULL == pMousePoint) { // need a new point
8735 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8736 "", wxEmptyString);
8737 pMousePoint->SetNameShown(false);
8738
8739 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8740
8741 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8742
8743 if (m_routeState > 1)
8744 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8745 Undo_IsOrphanded, NULL);
8746 }
8747
8748 if (m_pMouseRoute) {
8749 if (m_routeState == 1) {
8750 // First point in the route.
8751 m_pMouseRoute->AddPoint(pMousePoint);
8752 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8753 } else {
8754 if (m_pMouseRoute->m_NextLegGreatCircle) {
8755 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8756 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8757 &rhumbBearing, &rhumbDist);
8758 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8759 rlat, &gcDist, &gcBearing, NULL);
8760 double gcDistNM = gcDist / 1852.0;
8761
8762 // Empirically found expression to get reasonable route segments.
8763 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8764 pow(rhumbDist - gcDistNM - 1, 0.5);
8765
8766 wxString msg;
8767 msg << _("For this leg the Great Circle route is ")
8768 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8769 << _(" shorter than rhumbline.\n\n")
8770 << _("Would you like include the Great Circle routing points "
8771 "for this leg?");
8772
8773 m_FinishRouteOnKillFocus = false;
8774 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8775 // does not fully capture mouse
8776
8777 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8778 wxYES_NO | wxNO_DEFAULT);
8779
8780 m_disable_edge_pan = false;
8781 m_FinishRouteOnKillFocus = true;
8782
8783 if (answer == wxID_YES) {
8784 RoutePoint *gcPoint;
8785 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8786 wxRealPoint gcCoord;
8787
8788 for (int i = 1; i <= segmentCount; i++) {
8789 double fraction = (double)i * (1.0 / (double)segmentCount);
8790 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8791 gcDist * fraction, gcBearing,
8792 &gcCoord.x, &gcCoord.y, NULL);
8793
8794 if (i < segmentCount) {
8795 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8796 wxEmptyString);
8797 gcPoint->SetNameShown(false);
8798 // pConfig->AddNewWayPoint(gcPoint, -1);
8799 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8800
8801 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8802 gcPoint);
8803 } else {
8804 gcPoint = pMousePoint; // Last point, previously exsisting!
8805 }
8806
8807 m_pMouseRoute->AddPoint(gcPoint);
8808 pSelect->AddSelectableRouteSegment(
8809 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8810 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8811 prevGcPoint = gcPoint;
8812 }
8813
8814 undo->CancelUndoableAction(true);
8815
8816 } else {
8817 m_pMouseRoute->AddPoint(pMousePoint);
8818 pSelect->AddSelectableRouteSegment(
8819 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8820 pMousePoint, m_pMouseRoute);
8821 undo->AfterUndoableAction(m_pMouseRoute);
8822 }
8823 } else {
8824 // Ordinary rhumblinesegment.
8825 m_pMouseRoute->AddPoint(pMousePoint);
8826 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8827 rlon, m_prev_pMousePoint,
8828 pMousePoint, m_pMouseRoute);
8829 undo->AfterUndoableAction(m_pMouseRoute);
8830 }
8831 }
8832 }
8833 m_prev_rlat = rlat;
8834 m_prev_rlon = rlon;
8835 m_prev_pMousePoint = pMousePoint;
8836 if (m_pMouseRoute)
8837 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8838
8839 m_routeState++;
8840
8841 if (appending ||
8842 inserting) { // Appending a route or making a new route
8843 int connect = tail->GetIndexOf(pMousePoint);
8844 if (connect == 1) {
8845 inserting = false; // there is nothing to insert
8846 appending = true; // so append
8847 }
8848 int length = tail->GetnPoints();
8849
8850 int i;
8851 int start, stop;
8852 if (appending) {
8853 start = connect + 1;
8854 stop = length;
8855 } else { // inserting
8856 start = 1;
8857 stop = connect;
8858 m_pMouseRoute->RemovePoint(
8859 m_pMouseRoute
8860 ->GetLastPoint()); // Remove the first and only point
8861 }
8862 for (i = start; i <= stop; i++) {
8863 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8864 if (m_pMouseRoute)
8865 m_pMouseRoute->m_lastMousePointIndex =
8866 m_pMouseRoute->GetnPoints();
8867 m_routeState++;
8868 top_frame::Get()->RefreshAllCanvas();
8869 ret = true;
8870 }
8871 m_prev_rlat =
8872 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8873 m_prev_rlon =
8874 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8875 m_pMouseRoute->FinalizeForRendering();
8876 }
8877 top_frame::Get()->RefreshAllCanvas();
8878 ret = true;
8879 }
8880
8881 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8882 {
8883 SetCursor(*pCursorPencil);
8884
8885 if (!m_pMeasureRoute) {
8886 m_pMeasureRoute = new Route();
8887 pRouteList->push_back(m_pMeasureRoute);
8888 }
8889
8890 if (m_nMeasureState == 1) {
8891 r_rband.x = x;
8892 r_rband.y = y;
8893 }
8894
8895 RoutePoint *pMousePoint =
8896 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8897 wxEmptyString, wxEmptyString);
8898 pMousePoint->m_bShowName = false;
8899 pMousePoint->SetShowWaypointRangeRings(false);
8900
8901 m_pMeasureRoute->AddPoint(pMousePoint);
8902
8903 m_prev_rlat = m_cursor_lat;
8904 m_prev_rlon = m_cursor_lon;
8905 m_prev_pMousePoint = pMousePoint;
8906 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8907
8908 m_nMeasureState++;
8909 top_frame::Get()->RefreshAllCanvas();
8910 ret = true;
8911 }
8912
8913 else {
8914 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8915 }
8916 } // !g_btouch
8917 else { // g_btouch
8918 m_last_touch_down_pos = event.GetPosition();
8919
8920 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8921 // if near screen edge, pan with injection
8922 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8923 // return;
8924 // }
8925 }
8926 }
8927
8928 if (ret) return true;
8929 }
8930
8931 if (event.Dragging()) {
8932 // in touch screen mode ensure the finger/cursor is on the selected point's
8933 // radius to allow dragging
8934 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8935 if (g_btouch) {
8936 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8937 SelectItem *pFind = NULL;
8938 SelectableItemList SelList = pSelect->FindSelectionList(
8939 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8940 for (SelectItem *pFind : SelList) {
8941 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8942 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8943 }
8944 }
8945
8946 // Check for use of dragHandle
8947 if (m_pRoutePointEditTarget &&
8948 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8949 SelectItem *pFind = NULL;
8950 SelectableItemList SelList = pSelect->FindSelectionList(
8951 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8952 for (SelectItem *pFind : SelList) {
8953 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8954 if (m_pRoutePointEditTarget == frp) {
8955 m_bIsInRadius = true;
8956 break;
8957 }
8958 }
8959
8960 if (!m_dragoffsetSet) {
8961 RoutePointGui(*m_pRoutePointEditTarget)
8962 .PresetDragOffset(this, mouse_x, mouse_y);
8963 m_dragoffsetSet = true;
8964 }
8965 }
8966 }
8967
8968 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8969 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8970
8971 if (NULL == g_pMarkInfoDialog) {
8972 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8973 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8974 DraggingAllowed = false;
8975
8976 if (m_pRoutePointEditTarget &&
8977 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8978 DraggingAllowed = false;
8979
8980 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8981
8982 if (DraggingAllowed) {
8983 if (!undo->InUndoableAction()) {
8984 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8985 Undo_NeedsCopy, m_pFoundPoint);
8986 }
8987
8988 // Get the update rectangle for the union of the un-edited routes
8989 wxRect pre_rect;
8990
8991 if (!g_bopengl && m_pEditRouteArray) {
8992 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8993 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8994 // Need to validate route pointer
8995 // Route may be gone due to drgging close to ownship with
8996 // "Delete On Arrival" state set, as in the case of
8997 // navigating to an isolated waypoint on a temporary route
8998 if (g_pRouteMan->IsRouteValid(pr)) {
8999 wxRect route_rect;
9000 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9001 pre_rect.Union(route_rect);
9002 }
9003 }
9004 }
9005
9006 double new_cursor_lat = m_cursor_lat;
9007 double new_cursor_lon = m_cursor_lon;
9008
9009 if (CheckEdgePan(x, y, true, 5, 2))
9010 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9011
9012 // update the point itself
9013 if (g_btouch) {
9014 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9015 // new_cursor_lat, new_cursor_lon);
9016 RoutePointGui(*m_pRoutePointEditTarget)
9017 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9018 // update the Drag Handle entry in the pSelect list
9019 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9020 m_pRoutePointEditTarget,
9021 SELTYPE_DRAGHANDLE);
9022 m_pFoundPoint->m_slat =
9023 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9024 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9025 } else {
9026 m_pRoutePointEditTarget->m_lat =
9027 new_cursor_lat; // update the RoutePoint entry
9028 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9029 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9030 m_pFoundPoint->m_slat =
9031 new_cursor_lat; // update the SelectList entry
9032 m_pFoundPoint->m_slon = new_cursor_lon;
9033 }
9034
9035 // Update the MarkProperties Dialog, if currently shown
9036 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9037 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9038 g_pMarkInfoDialog->UpdateProperties(true);
9039 }
9040
9041 if (g_bopengl) {
9042 // InvalidateGL();
9043 Refresh(false);
9044 } else {
9045 // Get the update rectangle for the edited route
9046 wxRect post_rect;
9047
9048 if (m_pEditRouteArray) {
9049 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9050 ir++) {
9051 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9052 if (g_pRouteMan->IsRouteValid(pr)) {
9053 wxRect route_rect;
9054 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9055 post_rect.Union(route_rect);
9056 }
9057 }
9058 }
9059
9060 // Invalidate the union region
9061 pre_rect.Union(post_rect);
9062 RefreshRect(pre_rect, false);
9063 }
9064 top_frame::Get()->RefreshCanvasOther(this);
9065 m_bRoutePoinDragging = true;
9066 }
9067 ret = true;
9068 } // if Route Editing
9069
9070 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9071 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9072
9073 if (NULL == g_pMarkInfoDialog) {
9074 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9075 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9076 DraggingAllowed = false;
9077
9078 if (m_pRoutePointEditTarget &&
9079 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9080 DraggingAllowed = false;
9081
9082 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9083
9084 if (DraggingAllowed) {
9085 if (!undo->InUndoableAction()) {
9086 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9087 Undo_NeedsCopy, m_pFoundPoint);
9088 }
9089
9090 // The mark may be an anchorwatch
9091 double lpp1 = 0.;
9092 double lpp2 = 0.;
9093 double lppmax;
9094
9095 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9096 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9097 }
9098 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9099 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9100 }
9101 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9102
9103 // Get the update rectangle for the un-edited mark
9104 wxRect pre_rect;
9105 if (!g_bopengl) {
9106 RoutePointGui(*m_pRoutePointEditTarget)
9107 .CalculateDCRect(m_dc_route, this, &pre_rect);
9108 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9109 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9110 (int)(lppmax - (pre_rect.height / 2)));
9111 }
9112
9113 // update the point itself
9114 if (g_btouch) {
9115 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9116 // m_cursor_lat, m_cursor_lon);
9117 RoutePointGui(*m_pRoutePointEditTarget)
9118 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9119 // update the Drag Handle entry in the pSelect list
9120 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9121 m_pRoutePointEditTarget,
9122 SELTYPE_DRAGHANDLE);
9123 m_pFoundPoint->m_slat =
9124 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9125 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9126 } else {
9127 m_pRoutePointEditTarget->m_lat =
9128 m_cursor_lat; // update the RoutePoint entry
9129 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9130 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9131 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9132 m_pFoundPoint->m_slon = m_cursor_lon;
9133 }
9134
9135 // Update the MarkProperties Dialog, if currently shown
9136 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9137 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9138 g_pMarkInfoDialog->UpdateProperties(true);
9139 }
9140
9141 // Invalidate the union region
9142 if (g_bopengl) {
9143 if (!g_btouch) InvalidateGL();
9144 Refresh(false);
9145 } else {
9146 // Get the update rectangle for the edited mark
9147 wxRect post_rect;
9148 RoutePointGui(*m_pRoutePointEditTarget)
9149 .CalculateDCRect(m_dc_route, this, &post_rect);
9150 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9151 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9152 (int)(lppmax - (post_rect.height / 2)));
9153
9154 // Invalidate the union region
9155 pre_rect.Union(post_rect);
9156 RefreshRect(pre_rect, false);
9157 }
9158 top_frame::Get()->RefreshCanvasOther(this);
9159 m_bRoutePoinDragging = true;
9160 }
9161 ret = g_btouch ? m_bRoutePoinDragging : true;
9162 }
9163
9164 if (ret) return true;
9165 } // dragging
9166
9167 if (event.LeftUp()) {
9168 bool b_startedit_route = false;
9169 m_dragoffsetSet = false;
9170
9171 if (g_btouch) {
9172 m_bChartDragging = false;
9173 m_bIsInRadius = false;
9174
9175 if (m_routeState) // creating route?
9176 {
9177 if (m_ignore_next_leftup) {
9178 m_ignore_next_leftup = false;
9179 return false;
9180 }
9181
9182 if (m_bedge_pan) {
9183 m_bedge_pan = false;
9184 return false;
9185 }
9186
9187 double rlat, rlon;
9188 bool appending = false;
9189 bool inserting = false;
9190 Route *tail = 0;
9191
9192 rlat = m_cursor_lat;
9193 rlon = m_cursor_lon;
9194
9195 if (m_pRoutePointEditTarget) {
9196 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9197 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9198 if (!g_bopengl) {
9199 wxRect wp_rect;
9200 RoutePointGui(*m_pRoutePointEditTarget)
9201 .CalculateDCRect(m_dc_route, this, &wp_rect);
9202 RefreshRect(wp_rect, true);
9203 }
9204 m_pRoutePointEditTarget = NULL;
9205 }
9206 m_bRouteEditing = true;
9207
9208 if (m_routeState == 1) {
9209 m_pMouseRoute = new Route();
9210 m_pMouseRoute->SetHiLite(50);
9211 pRouteList->push_back(m_pMouseRoute);
9212 r_rband.x = x;
9213 r_rband.y = y;
9214 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9215 }
9216
9217 // Check to see if there is a nearby point which may be reused
9218 RoutePoint *pMousePoint = NULL;
9219
9220 // Calculate meaningful SelectRadius
9221 double nearby_radius_meters =
9222 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9223
9224 RoutePoint *pNearbyPoint =
9225 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9226 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9227 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9228 int dlg_return;
9229#ifndef __WXOSX__
9230 m_FinishRouteOnKillFocus =
9231 false; // Avoid route finish on focus change for message dialog
9232 dlg_return = OCPNMessageBox(
9233 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9234 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9235 m_FinishRouteOnKillFocus = true;
9236#else
9237 dlg_return = wxID_YES;
9238#endif
9239 if (dlg_return == wxID_YES) {
9240 pMousePoint = pNearbyPoint;
9241
9242 // Using existing waypoint, so nothing to delete for undo.
9243 if (m_routeState > 1)
9244 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9245 Undo_HasParent, NULL);
9246 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9247
9248 bool procede = false;
9249 if (tail) {
9250 procede = true;
9251 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9252 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9253 procede = false;
9254 }
9255
9256 if (procede) {
9257 int dlg_return;
9258 m_FinishRouteOnKillFocus = false;
9259 if (m_routeState == 1) { // first point in new route, preceeding
9260 // route to be added? touch case
9261
9262 wxString dmsg =
9263 _("Insert first part of this route in the new route?");
9264 if (tail->GetIndexOf(pMousePoint) ==
9265 tail->GetnPoints()) // Starting on last point of another
9266 // route?
9267 dmsg = _("Insert this route in the new route?");
9268
9269 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9270 dlg_return =
9271 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9272 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9273 m_FinishRouteOnKillFocus = true;
9274
9275 if (dlg_return == wxID_YES) {
9276 inserting = true; // part of the other route will be
9277 // preceeding the new route
9278 }
9279 }
9280 } else {
9281 wxString dmsg =
9282 _("Append last part of this route to the new route?");
9283 if (tail->GetIndexOf(pMousePoint) == 1)
9284 dmsg = _(
9285 "Append this route to the new route?"); // Picking the
9286 // first point of
9287 // another route?
9288
9289 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9290 dlg_return =
9291 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9292 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9293 m_FinishRouteOnKillFocus = true;
9294
9295 if (dlg_return == wxID_YES) {
9296 appending = true; // part of the other route will be
9297 // appended to the new route
9298 }
9299 }
9300 }
9301 }
9302
9303 // check all other routes to see if this point appears in any other
9304 // route If it appears in NO other route, then it should e
9305 // considered an isolated mark
9306 if (!FindRouteContainingWaypoint(pMousePoint))
9307 pMousePoint->SetShared(true);
9308 }
9309 }
9310
9311 if (NULL == pMousePoint) { // need a new point
9312 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9313 "", wxEmptyString);
9314 pMousePoint->SetNameShown(false);
9315
9316 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9317
9318 if (m_routeState > 1)
9319 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9320 Undo_IsOrphanded, NULL);
9321 }
9322
9323 if (m_routeState == 1) {
9324 // First point in the route.
9325 m_pMouseRoute->AddPoint(pMousePoint);
9326 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9327
9328 } else {
9329 if (m_pMouseRoute->m_NextLegGreatCircle) {
9330 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9331 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9332 &rhumbBearing, &rhumbDist);
9333 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9334 &gcDist, &gcBearing, NULL);
9335 double gcDistNM = gcDist / 1852.0;
9336
9337 // Empirically found expression to get reasonable route segments.
9338 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9339 pow(rhumbDist - gcDistNM - 1, 0.5);
9340
9341 wxString msg;
9342 msg << _("For this leg the Great Circle route is ")
9343 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9344 << _(" shorter than rhumbline.\n\n")
9345 << _("Would you like include the Great Circle routing points "
9346 "for this leg?");
9347
9348#ifndef __WXOSX__
9349 m_FinishRouteOnKillFocus = false;
9350 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9351 wxYES_NO | wxNO_DEFAULT);
9352 m_FinishRouteOnKillFocus = true;
9353#else
9354 int answer = wxID_NO;
9355#endif
9356
9357 if (answer == wxID_YES) {
9358 RoutePoint *gcPoint;
9359 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9360 wxRealPoint gcCoord;
9361
9362 for (int i = 1; i <= segmentCount; i++) {
9363 double fraction = (double)i * (1.0 / (double)segmentCount);
9364 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9365 gcDist * fraction, gcBearing,
9366 &gcCoord.x, &gcCoord.y, NULL);
9367
9368 if (i < segmentCount) {
9369 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9370 wxEmptyString);
9371 gcPoint->SetNameShown(false);
9372 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9373 gcPoint);
9374 } else {
9375 gcPoint = pMousePoint; // Last point, previously exsisting!
9376 }
9377
9378 m_pMouseRoute->AddPoint(gcPoint);
9379 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9380
9381 pSelect->AddSelectableRouteSegment(
9382 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9383 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9384 prevGcPoint = gcPoint;
9385 }
9386
9387 undo->CancelUndoableAction(true);
9388
9389 } else {
9390 m_pMouseRoute->AddPoint(pMousePoint);
9391 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9392 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9393 rlon, m_prev_pMousePoint,
9394 pMousePoint, m_pMouseRoute);
9395 undo->AfterUndoableAction(m_pMouseRoute);
9396 }
9397 } else {
9398 // Ordinary rhumblinesegment.
9399 m_pMouseRoute->AddPoint(pMousePoint);
9400 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9401
9402 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9403 rlon, m_prev_pMousePoint,
9404 pMousePoint, m_pMouseRoute);
9405 undo->AfterUndoableAction(m_pMouseRoute);
9406 }
9407 }
9408
9409 m_prev_rlat = rlat;
9410 m_prev_rlon = rlon;
9411 m_prev_pMousePoint = pMousePoint;
9412 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9413
9414 m_routeState++;
9415
9416 if (appending ||
9417 inserting) { // Appending a route or making a new route
9418 int connect = tail->GetIndexOf(pMousePoint);
9419 if (connect == 1) {
9420 inserting = false; // there is nothing to insert
9421 appending = true; // so append
9422 }
9423 int length = tail->GetnPoints();
9424
9425 int i;
9426 int start, stop;
9427 if (appending) {
9428 start = connect + 1;
9429 stop = length;
9430 } else { // inserting
9431 start = 1;
9432 stop = connect;
9433 m_pMouseRoute->RemovePoint(
9434 m_pMouseRoute
9435 ->GetLastPoint()); // Remove the first and only point
9436 }
9437 for (i = start; i <= stop; i++) {
9438 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9439 if (m_pMouseRoute)
9440 m_pMouseRoute->m_lastMousePointIndex =
9441 m_pMouseRoute->GetnPoints();
9442 m_routeState++;
9443 top_frame::Get()->RefreshAllCanvas();
9444 ret = true;
9445 }
9446 m_prev_rlat =
9447 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9448 m_prev_rlon =
9449 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9450 m_pMouseRoute->FinalizeForRendering();
9451 }
9452
9453 Refresh(true);
9454 ret = true;
9455 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9456 {
9457 if (m_bedge_pan) {
9458 m_bedge_pan = false;
9459 return false;
9460 }
9461
9462 if (m_ignore_next_leftup) {
9463 m_ignore_next_leftup = false;
9464 return false;
9465 }
9466
9467 if (m_nMeasureState == 1) {
9468 m_pMeasureRoute = new Route();
9469 pRouteList->push_back(m_pMeasureRoute);
9470 r_rband.x = x;
9471 r_rband.y = y;
9472 }
9473
9474 if (m_pMeasureRoute) {
9475 RoutePoint *pMousePoint =
9476 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9477 wxEmptyString, wxEmptyString);
9478 pMousePoint->m_bShowName = false;
9479
9480 m_pMeasureRoute->AddPoint(pMousePoint);
9481
9482 m_prev_rlat = m_cursor_lat;
9483 m_prev_rlon = m_cursor_lon;
9484 m_prev_pMousePoint = pMousePoint;
9485 m_pMeasureRoute->m_lastMousePointIndex =
9486 m_pMeasureRoute->GetnPoints();
9487
9488 m_nMeasureState++;
9489 } else {
9490 CancelMeasureRoute();
9491 }
9492
9493 Refresh(true);
9494 ret = true;
9495 } else {
9496 bool bSelectAllowed = true;
9497 if (NULL == g_pMarkInfoDialog) {
9498 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9499 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9500 bSelectAllowed = false;
9501
9502 // Avoid accidental selection of routepoint if last touchdown started
9503 // a significant chart drag operation
9504 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9505 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9506 significant_drag) ||
9507 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9508 significant_drag)) {
9509 bSelectAllowed = false;
9510 }
9511
9512 /*if this left up happens at the end of a route point dragging and if
9513 the cursor/thumb is on the draghandle icon, not on the point iself a new
9514 selection will select nothing and the drag will never be ended, so the
9515 legs around this point never selectable. At this step we don't need a
9516 new selection, just keep the previoulsly selected and dragged point */
9517 if (m_bRoutePoinDragging) bSelectAllowed = false;
9518
9519 if (bSelectAllowed) {
9520 bool b_was_editing_mark = m_bMarkEditing;
9521 bool b_was_editing_route = m_bRouteEditing;
9522 FindRoutePointsAtCursor(SelectRadius,
9523 true); // Possibly selecting a point in a
9524 // route for later dragging
9525
9526 /*route and a mark points in layer can't be dragged so should't be
9527 * selected and no draghandle icon*/
9528 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9529 m_pRoutePointEditTarget = NULL;
9530
9531 if (!b_was_editing_route) {
9532 if (m_pEditRouteArray) {
9533 b_startedit_route = true;
9534
9535 // Hide the track and route rollover during route point edit, not
9536 // needed, and may be confusing
9537 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9538 m_pTrackRolloverWin->IsActive(false);
9539 }
9540 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9541 m_pRouteRolloverWin->IsActive(false);
9542 }
9543
9544 wxRect pre_rect;
9545 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9546 ir++) {
9547 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9548 // Need to validate route pointer
9549 // Route may be gone due to drgging close to ownship with
9550 // "Delete On Arrival" state set, as in the case of
9551 // navigating to an isolated waypoint on a temporary route
9552 if (g_pRouteMan->IsRouteValid(pr)) {
9553 // pr->SetHiLite(50);
9554 wxRect route_rect;
9555 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9556 pre_rect.Union(route_rect);
9557 }
9558 }
9559 RefreshRect(pre_rect, true);
9560 }
9561 } else {
9562 b_startedit_route = false;
9563 }
9564
9565 // Mark editing in touch mode, left-up event.
9566 if (m_pRoutePointEditTarget) {
9567 if (b_was_editing_mark ||
9568 b_was_editing_route) { // kill previous hilight
9569 if (m_lastRoutePointEditTarget) {
9570 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9571 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9572 RoutePointGui(*m_lastRoutePointEditTarget)
9573 .EnableDragHandle(false);
9574 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9575 SELTYPE_DRAGHANDLE);
9576 }
9577 }
9578
9579 if (m_pRoutePointEditTarget) {
9580 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9581 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9582 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9583 wxPoint2DDouble dragHandlePoint =
9584 RoutePointGui(*m_pRoutePointEditTarget)
9585 .GetDragHandlePoint(this);
9586 pSelect->AddSelectablePoint(
9587 dragHandlePoint.m_y, dragHandlePoint.m_x,
9588 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9589 }
9590 } else { // Deselect everything
9591 if (m_lastRoutePointEditTarget) {
9592 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9593 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9594 RoutePointGui(*m_lastRoutePointEditTarget)
9595 .EnableDragHandle(false);
9596 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9597 SELTYPE_DRAGHANDLE);
9598
9599 // Clear any routes being edited, probably orphans
9600 wxArrayPtrVoid *lastEditRouteArray =
9602 m_lastRoutePointEditTarget);
9603 if (lastEditRouteArray) {
9604 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9605 ir++) {
9606 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9607 if (g_pRouteMan->IsRouteValid(pr)) {
9608 pr->m_bIsBeingEdited = false;
9609 }
9610 }
9611 delete lastEditRouteArray;
9612 }
9613 }
9614 }
9615
9616 // Do the refresh
9617
9618 if (g_bopengl) {
9619 InvalidateGL();
9620 Refresh(false);
9621 } else {
9622 if (m_lastRoutePointEditTarget) {
9623 wxRect wp_rect;
9624 RoutePointGui(*m_lastRoutePointEditTarget)
9625 .CalculateDCRect(m_dc_route, this, &wp_rect);
9626 RefreshRect(wp_rect, true);
9627 }
9628
9629 if (m_pRoutePointEditTarget) {
9630 wxRect wp_rect;
9631 RoutePointGui(*m_pRoutePointEditTarget)
9632 .CalculateDCRect(m_dc_route, this, &wp_rect);
9633 RefreshRect(wp_rect, true);
9634 }
9635 }
9636 }
9637 } // bSelectAllowed
9638
9639 // Check to see if there is a route or AIS target under the cursor
9640 // If so, start the rollover timer which creates the popup
9641 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9642 bool b_start_rollover = false;
9643 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9644 SelectItem *pFind = pSelectAIS->FindSelection(
9645 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9646 if (pFind) b_start_rollover = true;
9647 }
9648
9649 if (!b_start_rollover && !b_startedit_route) {
9650 SelectableItemList SelList = pSelect->FindSelectionList(
9651 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9652 for (SelectItem *pFindSel : SelList) {
9653 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9654 if (pr && pr->IsVisible()) {
9655 b_start_rollover = true;
9656 break;
9657 }
9658 } // while
9659 }
9660
9661 if (!b_start_rollover && !b_startedit_route) {
9662 SelectableItemList SelList = pSelect->FindSelectionList(
9663 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9664 for (SelectItem *pFindSel : SelList) {
9665 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9666 if (tr && tr->IsVisible()) {
9667 b_start_rollover = true;
9668 break;
9669 }
9670 } // while
9671 }
9672
9673 if (b_start_rollover)
9674 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9675 wxTIMER_ONE_SHOT);
9676 Route *tail = 0;
9677 Route *current = 0;
9678 bool appending = false;
9679 bool inserting = false;
9680 int connect = 0;
9681 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9682 // drag
9683 if (m_pRoutePointEditTarget) {
9684 // Check to see if there is a nearby point which may replace the
9685 // dragged one
9686 RoutePoint *pMousePoint = NULL;
9687
9688 int index_last;
9689 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9690 double nearby_radius_meters =
9691 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9692 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9693 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9694 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9695 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9696 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9697 bool duplicate =
9698 false; // ensure we won't create duplicate point in routes
9699 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9700 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9701 ir++) {
9702 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9703 if (pr && pr->pRoutePointList) {
9704 auto *list = pr->pRoutePointList;
9705 auto pos =
9706 std::find(list->begin(), list->end(), pNearbyPoint);
9707 if (pos != list->end()) {
9708 duplicate = true;
9709 break;
9710 }
9711 }
9712 }
9713 }
9714
9715 // Special case:
9716 // Allow "re-use" of a route's waypoints iff it is a simple
9717 // isolated route. This allows, for instance, creation of a closed
9718 // polygon route
9719 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9720
9721 if (!duplicate) {
9722 int dlg_return;
9723 dlg_return =
9724 OCPNMessageBox(this,
9725 _("Replace this RoutePoint by the nearby "
9726 "Waypoint?"),
9727 _("OpenCPN RoutePoint change"),
9728 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9729 if (dlg_return == wxID_YES) {
9730 /*double confirmation if the dragged point has been manually
9731 * created which can be important and could be deleted
9732 * unintentionally*/
9733
9734 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9735 pNearbyPoint);
9736 current =
9737 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9738
9739 if (tail && current && (tail != current)) {
9740 int dlg_return1;
9741 connect = tail->GetIndexOf(pNearbyPoint);
9742 int index_current_route =
9743 current->GetIndexOf(m_pRoutePointEditTarget);
9744 index_last = current->GetIndexOf(current->GetLastPoint());
9745 dlg_return1 = wxID_NO;
9746 if (index_last ==
9747 index_current_route) { // we are dragging the last
9748 // point of the route
9749 if (connect != tail->GetnPoints()) { // anything to do?
9750
9751 wxString dmsg(
9752 _("Last part of route to be appended to dragged "
9753 "route?"));
9754 if (connect == 1)
9755 dmsg =
9756 _("Full route to be appended to dragged route?");
9757
9758 dlg_return1 = OCPNMessageBox(
9759 this, dmsg, _("OpenCPN Route Create"),
9760 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9761 if (dlg_return1 == wxID_YES) {
9762 appending = true;
9763 }
9764 }
9765 } else if (index_current_route ==
9766 1) { // dragging the first point of the route
9767 if (connect != 1) { // anything to do?
9768
9769 wxString dmsg(
9770 _("First part of route to be inserted into dragged "
9771 "route?"));
9772 if (connect == tail->GetnPoints())
9773 dmsg = _(
9774 "Full route to be inserted into dragged route?");
9775
9776 dlg_return1 = OCPNMessageBox(
9777 this, dmsg, _("OpenCPN Route Create"),
9778 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9779 if (dlg_return1 == wxID_YES) {
9780 inserting = true;
9781 }
9782 }
9783 }
9784 }
9785
9786 if (m_pRoutePointEditTarget->IsShared()) {
9787 // dlg_return = wxID_NO;
9788 dlg_return = OCPNMessageBox(
9789 this,
9790 _("Do you really want to delete and replace this "
9791 "WayPoint") +
9792 "\n" + _("which has been created manually?"),
9793 ("OpenCPN RoutePoint warning"),
9794 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9795 }
9796 }
9797 if (dlg_return == wxID_YES) {
9798 pMousePoint = pNearbyPoint;
9799 if (pMousePoint->m_bIsolatedMark) {
9800 pMousePoint->SetShared(true);
9801 }
9802 pMousePoint->m_bIsolatedMark =
9803 false; // definitely no longer isolated
9804 pMousePoint->m_bIsInRoute = true;
9805 }
9806 }
9807 }
9808 }
9809 if (!pMousePoint)
9810 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9811
9812 if (m_pEditRouteArray) {
9813 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9814 ir++) {
9815 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9816 if (g_pRouteMan->IsRouteValid(pr)) {
9817 if (pMousePoint) { // remove the dragged point and insert the
9818 // nearby
9819 auto *list = pr->pRoutePointList;
9820 auto pos = std::find(list->begin(), list->end(),
9821 m_pRoutePointEditTarget);
9822
9823 pSelect->DeleteAllSelectableRoutePoints(pr);
9824 pSelect->DeleteAllSelectableRouteSegments(pr);
9825
9826 pr->pRoutePointList->insert(pos, pMousePoint);
9827 pos = std::find(list->begin(), list->end(),
9828 m_pRoutePointEditTarget);
9829 pr->pRoutePointList->erase(pos);
9830
9831 pSelect->AddAllSelectableRouteSegments(pr);
9832 pSelect->AddAllSelectableRoutePoints(pr);
9833 }
9834 pr->FinalizeForRendering();
9835 pr->UpdateSegmentDistances();
9836 if (m_bRoutePoinDragging) {
9837 // pConfig->UpdateRoute(pr);
9838 NavObj_dB::GetInstance().UpdateRoute(pr);
9839 }
9840 }
9841 }
9842 }
9843
9844 // Update the RouteProperties Dialog, if currently shown
9845 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9846 if (m_pEditRouteArray) {
9847 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9848 ir++) {
9849 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9850 if (g_pRouteMan->IsRouteValid(pr)) {
9851 if (pRoutePropDialog->GetRoute() == pr) {
9852 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9853 }
9854 /* cannot edit track points anyway
9855 else if ( ( NULL !=
9856 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9857 pTrackPropDialog->m_pTrack == pr ) {
9858 pTrackPropDialog->SetTrackAndUpdate(
9859 pr );
9860 }
9861 */
9862 }
9863 }
9864 }
9865 }
9866 if (pMousePoint) { // clear all about the dragged point
9867 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9868 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9869 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9870 // Hide mark properties dialog if open on the replaced point
9871 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9872 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9873 g_pMarkInfoDialog->Hide();
9874
9875 delete m_pRoutePointEditTarget;
9876 m_lastRoutePointEditTarget = NULL;
9877 m_pRoutePointEditTarget = NULL;
9878 undo->AfterUndoableAction(pMousePoint);
9879 undo->InvalidateUndo();
9880 }
9881 }
9882 }
9883
9884 else if (m_bMarkEditing) { // End of way point drag
9885 if (m_pRoutePointEditTarget)
9886 if (m_bRoutePoinDragging) {
9887 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9888 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9889 }
9890 }
9891
9892 if (m_pRoutePointEditTarget)
9893 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9894
9895 if (!m_pRoutePointEditTarget) {
9896 delete m_pEditRouteArray;
9897 m_pEditRouteArray = NULL;
9898 m_bRouteEditing = false;
9899 }
9900 m_bRoutePoinDragging = false;
9901
9902 if (appending) { // Appending to the route of which the last point is
9903 // dragged onto another route
9904
9905 // copy tail from connect until length to end of current after dragging
9906
9907 int length = tail->GetnPoints();
9908 for (int i = connect + 1; i <= length; i++) {
9909 current->AddPointAndSegment(tail->GetPoint(i), false);
9910 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9911 m_routeState++;
9912 top_frame::Get()->RefreshAllCanvas();
9913 ret = true;
9914 }
9915 current->FinalizeForRendering();
9916 current->m_bIsBeingEdited = false;
9917 FinishRoute();
9918 g_pRouteMan->DeleteRoute(tail);
9919 }
9920 if (inserting) {
9921 pSelect->DeleteAllSelectableRoutePoints(current);
9922 pSelect->DeleteAllSelectableRouteSegments(current);
9923 for (int i = 1; i < connect; i++) { // numbering in the tail route
9924 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9925 }
9926 pSelect->AddAllSelectableRouteSegments(current);
9927 pSelect->AddAllSelectableRoutePoints(current);
9928 current->FinalizeForRendering();
9929 current->m_bIsBeingEdited = false;
9930 g_pRouteMan->DeleteRoute(tail);
9931 }
9932
9933 // Update the RouteProperties Dialog, if currently shown
9934 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9935 if (m_pEditRouteArray) {
9936 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9937 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9938 if (g_pRouteMan->IsRouteValid(pr)) {
9939 if (pRoutePropDialog->GetRoute() == pr) {
9940 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9941 }
9942 }
9943 }
9944 }
9945 }
9946
9947 } // g_btouch
9948
9949 else { // !g_btouch
9950 if (m_bRouteEditing) { // End of RoutePoint drag
9951 Route *tail = 0;
9952 Route *current = 0;
9953 bool appending = false;
9954 bool inserting = false;
9955 int connect = 0;
9956 int index_last;
9957 if (m_pRoutePointEditTarget) {
9958 m_pRoutePointEditTarget->m_bBlink = false;
9959 // Check to see if there is a nearby point which may replace the
9960 // dragged one
9961 RoutePoint *pMousePoint = NULL;
9962 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9963 double nearby_radius_meters =
9964 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9965 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9966 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9967 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9968 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9969 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9970 bool duplicate = false; // don't create duplicate point in routes
9971 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9972 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9973 ir++) {
9974 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9975 if (pr && pr->pRoutePointList) {
9976 auto *list = pr->pRoutePointList;
9977 auto pos =
9978 std::find(list->begin(), list->end(), pNearbyPoint);
9979 if (pos != list->end()) {
9980 duplicate = true;
9981 break;
9982 }
9983 }
9984 }
9985 }
9986
9987 // Special case:
9988 // Allow "re-use" of a route's waypoints iff it is a simple
9989 // isolated route. This allows, for instance, creation of a closed
9990 // polygon route
9991 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9992
9993 if (!duplicate) {
9994 int dlg_return;
9995 dlg_return =
9996 OCPNMessageBox(this,
9997 _("Replace this RoutePoint by the nearby "
9998 "Waypoint?"),
9999 _("OpenCPN RoutePoint change"),
10000 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10001 if (dlg_return == wxID_YES) {
10002 /*double confirmation if the dragged point has been manually
10003 * created which can be important and could be deleted
10004 * unintentionally*/
10005 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10006 pNearbyPoint);
10007 current =
10008 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10009
10010 if (tail && current && (tail != current)) {
10011 int dlg_return1;
10012 connect = tail->GetIndexOf(pNearbyPoint);
10013 int index_current_route =
10014 current->GetIndexOf(m_pRoutePointEditTarget);
10015 index_last = current->GetIndexOf(current->GetLastPoint());
10016 dlg_return1 = wxID_NO;
10017 if (index_last ==
10018 index_current_route) { // we are dragging the last
10019 // point of the route
10020 if (connect != tail->GetnPoints()) { // anything to do?
10021
10022 wxString dmsg(
10023 _("Last part of route to be appended to dragged "
10024 "route?"));
10025 if (connect == 1)
10026 dmsg =
10027 _("Full route to be appended to dragged route?");
10028
10029 dlg_return1 = OCPNMessageBox(
10030 this, dmsg, _("OpenCPN Route Create"),
10031 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10032 if (dlg_return1 == wxID_YES) {
10033 appending = true;
10034 }
10035 }
10036 } else if (index_current_route ==
10037 1) { // dragging the first point of the route
10038 if (connect != 1) { // anything to do?
10039
10040 wxString dmsg(
10041 _("First part of route to be inserted into dragged "
10042 "route?"));
10043 if (connect == tail->GetnPoints())
10044 dmsg = _(
10045 "Full route to be inserted into dragged route?");
10046
10047 dlg_return1 = OCPNMessageBox(
10048 this, dmsg, _("OpenCPN Route Create"),
10049 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10050 if (dlg_return1 == wxID_YES) {
10051 inserting = true;
10052 }
10053 }
10054 }
10055 }
10056
10057 if (m_pRoutePointEditTarget->IsShared()) {
10058 dlg_return = wxID_NO;
10059 dlg_return = OCPNMessageBox(
10060 this,
10061 _("Do you really want to delete and replace this "
10062 "WayPoint") +
10063 "\n" + _("which has been created manually?"),
10064 ("OpenCPN RoutePoint warning"),
10065 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10066 }
10067 }
10068 if (dlg_return == wxID_YES) {
10069 pMousePoint = pNearbyPoint;
10070 if (pMousePoint->m_bIsolatedMark) {
10071 pMousePoint->SetShared(true);
10072 }
10073 pMousePoint->m_bIsolatedMark =
10074 false; // definitely no longer isolated
10075 pMousePoint->m_bIsInRoute = true;
10076 }
10077 }
10078 }
10079 }
10080 if (!pMousePoint)
10081 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10082
10083 if (m_pEditRouteArray) {
10084 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10085 ir++) {
10086 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10087 if (g_pRouteMan->IsRouteValid(pr)) {
10088 if (pMousePoint) { // replace dragged point by nearby one
10089 auto *list = pr->pRoutePointList;
10090 auto pos = std::find(list->begin(), list->end(),
10091 m_pRoutePointEditTarget);
10092
10093 pSelect->DeleteAllSelectableRoutePoints(pr);
10094 pSelect->DeleteAllSelectableRouteSegments(pr);
10095
10096 pr->pRoutePointList->insert(pos, pMousePoint);
10097 pos = std::find(list->begin(), list->end(),
10098 m_pRoutePointEditTarget);
10099 if (pos != list->end()) list->erase(pos);
10100 // pr->pRoutePointList->erase(pos + 1);
10101
10102 pSelect->AddAllSelectableRouteSegments(pr);
10103 pSelect->AddAllSelectableRoutePoints(pr);
10104 }
10105 pr->FinalizeForRendering();
10106 pr->UpdateSegmentDistances();
10107 pr->m_bIsBeingEdited = false;
10108
10109 if (m_bRoutePoinDragging) {
10110 // Special case optimization.
10111 // Dragging a single point of a route
10112 // without any point additions or re-ordering
10113 if (!pMousePoint)
10114 NavObj_dB::GetInstance().UpdateRoutePoint(
10115 m_pRoutePointEditTarget);
10116 else
10117 NavObj_dB::GetInstance().UpdateRoute(pr);
10118 }
10119 pr->SetHiLite(0);
10120 }
10121 }
10122 Refresh(false);
10123 }
10124
10125 if (appending) {
10126 // copy tail from connect until length to end of current after
10127 // dragging
10128
10129 int length = tail->GetnPoints();
10130 for (int i = connect + 1; i <= length; i++) {
10131 current->AddPointAndSegment(tail->GetPoint(i), false);
10132 if (current)
10133 current->m_lastMousePointIndex = current->GetnPoints();
10134 m_routeState++;
10135 top_frame::Get()->RefreshAllCanvas();
10136 ret = true;
10137 }
10138 current->FinalizeForRendering();
10139 current->m_bIsBeingEdited = false;
10140 FinishRoute();
10141 g_pRouteMan->DeleteRoute(tail);
10142 }
10143 if (inserting) {
10144 pSelect->DeleteAllSelectableRoutePoints(current);
10145 pSelect->DeleteAllSelectableRouteSegments(current);
10146 for (int i = 1; i < connect; i++) { // numbering in the tail route
10147 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10148 }
10149 pSelect->AddAllSelectableRouteSegments(current);
10150 pSelect->AddAllSelectableRoutePoints(current);
10151 current->FinalizeForRendering();
10152 current->m_bIsBeingEdited = false;
10153 g_pRouteMan->DeleteRoute(tail);
10154 }
10155
10156 // Update the RouteProperties Dialog, if currently shown
10157 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10158 if (m_pEditRouteArray) {
10159 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10160 ir++) {
10161 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10162 if (g_pRouteMan->IsRouteValid(pr)) {
10163 if (pRoutePropDialog->GetRoute() == pr) {
10164 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10165 }
10166 }
10167 }
10168 }
10169 }
10170
10171 if (pMousePoint) {
10172 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10173 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10174 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10175 // Hide mark properties dialog if open on the replaced point
10176 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10177 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10178 g_pMarkInfoDialog->Hide();
10179
10180 delete m_pRoutePointEditTarget;
10181 m_lastRoutePointEditTarget = NULL;
10182 undo->AfterUndoableAction(pMousePoint);
10183 undo->InvalidateUndo();
10184 } else {
10185 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10186 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10187
10188 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10189 }
10190
10191 delete m_pEditRouteArray;
10192 m_pEditRouteArray = NULL;
10193 }
10194
10195 InvalidateGL();
10196 m_bRouteEditing = false;
10197 m_pRoutePointEditTarget = NULL;
10198
10199 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10200 ret = true;
10201 }
10202
10203 else if (m_bMarkEditing) { // end of Waypoint drag
10204 if (m_pRoutePointEditTarget) {
10205 if (m_bRoutePoinDragging) {
10206 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10207 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10208 }
10209 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10210 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10211 if (!g_bopengl) {
10212 wxRect wp_rect;
10213 RoutePointGui(*m_pRoutePointEditTarget)
10214 .CalculateDCRect(m_dc_route, this, &wp_rect);
10215 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10216 RefreshRect(wp_rect, true);
10217 }
10218 }
10219 m_pRoutePointEditTarget = NULL;
10220 m_bMarkEditing = false;
10221 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10222 ret = true;
10223 }
10224
10225 else if (leftIsDown) { // left click for chart center
10226 leftIsDown = false;
10227 ret = false;
10228
10229 if (!g_btouch) {
10230 if (!m_bChartDragging && !m_bMeasure_Active) {
10231 } else {
10232 m_bChartDragging = false;
10233 }
10234 }
10235 }
10236 m_bRoutePoinDragging = false;
10237 } // !btouch
10238
10239 if (ret) return true;
10240 } // left up
10241
10242 if (event.RightDown()) {
10243 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10244 last_drag.x = mx;
10245 last_drag.y = my;
10246
10247 if (g_btouch) {
10248 // if( m_pRoutePointEditTarget )
10249 // return false;
10250 }
10251
10252 ret = true;
10253 m_FinishRouteOnKillFocus = false;
10254 CallPopupMenu(mx, my);
10255 m_FinishRouteOnKillFocus = true;
10256 } // Right down
10257
10258 return ret;
10259}
10260
10261bool panleftIsDown;
10262bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10263 // Skip all mouse processing if shift is held.
10264 // This allows plugins to implement shift+drag behaviors.
10265 if (event.ShiftDown()) {
10266 return false;
10267 }
10268 int x, y;
10269 event.GetPosition(&x, &y);
10270
10271 x *= m_displayScale;
10272 y *= m_displayScale;
10273
10274 // Check for wheel rotation
10275 // ideally, should be just longer than the time between
10276 // processing accumulated mouse events from the event queue
10277 // as would happen during screen redraws.
10278 int wheel_dir = event.GetWheelRotation();
10279
10280 if (wheel_dir) {
10281 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10282 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10283
10284 double factor = g_mouse_zoom_sensitivity;
10285 if (wheel_dir < 0) factor = 1 / factor;
10286
10287 if (g_bsmoothpanzoom) {
10288 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10289 if (wheel_dir == m_last_wheel_dir) {
10290 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10291 // m_zoom_target /= factor;
10292 } else
10293 StopMovement();
10294 } else {
10295 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10296 m_wheelstopwatch.Start(0);
10297 // m_zoom_target = VPoint.chart_scale / factor;
10298 }
10299 }
10300
10301 m_last_wheel_dir = wheel_dir;
10302
10303 ZoomCanvas(factor, true, false);
10304 }
10305
10306 if (event.LeftDown()) {
10307 // Skip the first left click if it will cause a canvas focus shift
10308 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10309 return false;
10310 }
10311
10312 last_drag.x = x, last_drag.y = y;
10313 panleftIsDown = true;
10314 }
10315
10316 if (event.LeftUp()) {
10317 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10318 // seen here.
10319 panleftIsDown = false;
10320
10321 if (!g_btouch) {
10322 if (!m_bChartDragging && !m_bMeasure_Active) {
10323 switch (cursor_region) {
10324 case MID_RIGHT: {
10325 PanCanvas(100, 0);
10326 break;
10327 }
10328
10329 case MID_LEFT: {
10330 PanCanvas(-100, 0);
10331 break;
10332 }
10333
10334 case MID_TOP: {
10335 PanCanvas(0, 100);
10336 break;
10337 }
10338
10339 case MID_BOT: {
10340 PanCanvas(0, -100);
10341 break;
10342 }
10343
10344 case CENTER: {
10345 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10346 break;
10347 }
10348 }
10349 } else {
10350 m_bChartDragging = false;
10351 }
10352 }
10353 }
10354 }
10355
10356 if (event.Dragging() && event.LeftIsDown()) {
10357 /*
10358 * fixed dragging.
10359 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10360 * before the drag event. Hence, as there is no mouse down event, last_drag
10361 * is not reset before the drag. And that results in one single drag
10362 * session, meaning you cannot drag the map a few miles north, lift your
10363 * finger, and the go even further north. Instead, the map resets itself
10364 * always to the very first drag start (since there is not reset of
10365 * last_drag).
10366 *
10367 * Besides, should not left down and dragging be enough of a situation to
10368 * start a drag procedure?
10369 *
10370 * Anyways, guarded it to be active in touch situations only.
10371 */
10372 if (g_btouch && !m_inPinch) {
10373 struct timespec now;
10374 clock_gettime(CLOCK_MONOTONIC, &now);
10375 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10376
10377 bool trigger_hold = false;
10378 if (false == m_bChartDragging) {
10379 if (m_DragTrigger < 0) {
10380 // printf("\ntrigger1\n");
10381 m_DragTrigger = 0;
10382 m_DragTriggerStartTime = tnow;
10383 trigger_hold = true;
10384 } else {
10385 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10386 m_DragTrigger = -1; // Reset trigger
10387 // printf("trigger fired\n");
10388 }
10389 }
10390 }
10391 if (trigger_hold) return true;
10392
10393 if (false == m_bChartDragging) {
10394 // printf("starting drag\n");
10395 // Reset drag calculation members
10396 last_drag.x = x - 1, last_drag.y = y - 1;
10397 m_bChartDragging = true;
10398 m_chart_drag_total_time = 0;
10399 m_chart_drag_total_x = 0;
10400 m_chart_drag_total_y = 0;
10401 m_inertia_last_drag_x = x;
10402 m_inertia_last_drag_y = y;
10403 m_drag_vec_x.clear();
10404 m_drag_vec_y.clear();
10405 m_drag_vec_t.clear();
10406 m_last_drag_time = tnow;
10407 }
10408
10409 // Calculate and store drag dynamics.
10410 uint64_t delta_t = tnow - m_last_drag_time;
10411 double delta_tf = delta_t / 1e9;
10412
10413 m_chart_drag_total_time += delta_tf;
10414 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10415 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10416
10417 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10418 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10419 m_drag_vec_t.push_back(delta_tf);
10420
10421 m_inertia_last_drag_x = x;
10422 m_inertia_last_drag_y = y;
10423 m_last_drag_time = tnow;
10424
10425 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10426 m_bChartDragging = true;
10427 StartTimedMovement();
10428 m_pan_drag.x += last_drag.x - x;
10429 m_pan_drag.y += last_drag.y - y;
10430 last_drag.x = x, last_drag.y = y;
10431 }
10432 } else if (!g_btouch) {
10433 if ((last_drag.x != x) || (last_drag.y != y)) {
10434 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10435 // dragging on route create.
10436 // github #2994
10437 m_bChartDragging = true;
10438 StartTimedMovement();
10439 m_pan_drag.x += last_drag.x - x;
10440 m_pan_drag.y += last_drag.y - y;
10441 last_drag.x = x, last_drag.y = y;
10442 }
10443 }
10444 }
10445
10446 // Handle some special cases
10447 if (g_btouch) {
10448 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10449 // deactivate next LeftUp to ovoid creating an unexpected point
10450 m_ignore_next_leftup = true;
10451 m_DoubleClickTimer->Start();
10452 singleClickEventIsValid = false;
10453 }
10454 }
10455 }
10456
10457 return true;
10458}
10459
10460void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10461 if (MouseEventOverlayWindows(event)) return;
10462
10463 if (MouseEventSetup(event)) return; // handled, no further action required
10464
10465 bool nm = MouseEventProcessObjects(event);
10466 if (!nm) MouseEventProcessCanvas(event);
10467}
10468
10469void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10470 // Switch to the appropriate cursor on mouse movement
10471
10472 wxCursor *ptarget_cursor = pCursorArrow;
10473 if (!pPlugIn_Cursor) {
10474 ptarget_cursor = pCursorArrow;
10475 if ((!m_routeState) &&
10476 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10477 if (cursor_region == MID_RIGHT) {
10478 ptarget_cursor = pCursorRight;
10479 } else if (cursor_region == MID_LEFT) {
10480 ptarget_cursor = pCursorLeft;
10481 } else if (cursor_region == MID_TOP) {
10482 ptarget_cursor = pCursorDown;
10483 } else if (cursor_region == MID_BOT) {
10484 ptarget_cursor = pCursorUp;
10485 } else {
10486 ptarget_cursor = pCursorArrow;
10487 }
10488 } else if (m_bMeasure_Active ||
10489 m_routeState) // If Measure tool use Pencil Cursor
10490 ptarget_cursor = pCursorPencil;
10491 } else {
10492 ptarget_cursor = pPlugIn_Cursor;
10493 }
10494
10495 SetCursor(*ptarget_cursor);
10496}
10497
10498void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10499 SetCursor(*pCursorArrow);
10500}
10501
10502void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10503 ChartPlugInWrapper *target_plugin_chart = NULL;
10504 s57chart *Chs57 = NULL;
10505 wxFileName file;
10506 wxArrayString files;
10507
10508 ChartBase *target_chart = GetChartAtCursor();
10509 if (target_chart) {
10510 file.Assign(target_chart->GetFullPath());
10511 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10512 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10513 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10514 else
10515 Chs57 = dynamic_cast<s57chart *>(target_chart);
10516 } else { // target_chart = null, might be mbtiles
10517 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10518 unsigned int im = stackIndexArray.size();
10519 int scale = 2147483647; // max 32b integer
10520 if (VPoint.b_quilt && im > 0) {
10521 for (unsigned int is = 0; is < im; is++) {
10522 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10523 CHART_TYPE_MBTILES) {
10524 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10525 double lat, lon;
10526 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10527 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10528 .GetBBox()
10529 .Contains(lat, lon)) {
10530 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10531 scale) {
10532 scale =
10533 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10534 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10535 }
10536 }
10537 }
10538 }
10539 }
10540 }
10541
10542 std::vector<Ais8_001_22 *> area_notices;
10543
10544 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10545 float vp_scale = GetVPScale();
10546
10547 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10548 auto target_data = target.second;
10549 if (!target_data->area_notices.empty()) {
10550 for (auto &ani : target_data->area_notices) {
10551 Ais8_001_22 &area_notice = ani.second;
10552
10553 BoundingBox bbox;
10554
10555 for (Ais8_001_22_SubAreaList::iterator sa =
10556 area_notice.sub_areas.begin();
10557 sa != area_notice.sub_areas.end(); ++sa) {
10558 switch (sa->shape) {
10559 case AIS8_001_22_SHAPE_CIRCLE: {
10560 wxPoint target_point;
10561 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10562 bbox.Expand(target_point);
10563 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10564 break;
10565 }
10566 case AIS8_001_22_SHAPE_RECT: {
10567 wxPoint target_point;
10568 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10569 bbox.Expand(target_point);
10570 if (sa->e_dim_m > sa->n_dim_m)
10571 bbox.EnLarge(sa->e_dim_m * vp_scale);
10572 else
10573 bbox.EnLarge(sa->n_dim_m * vp_scale);
10574 break;
10575 }
10576 case AIS8_001_22_SHAPE_POLYGON:
10577 case AIS8_001_22_SHAPE_POLYLINE: {
10578 for (int i = 0; i < 4; ++i) {
10579 double lat = sa->latitude;
10580 double lon = sa->longitude;
10581 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10582 &lat, &lon);
10583 wxPoint target_point;
10584 GetCanvasPointPix(lat, lon, &target_point);
10585 bbox.Expand(target_point);
10586 }
10587 break;
10588 }
10589 case AIS8_001_22_SHAPE_SECTOR: {
10590 double lat1 = sa->latitude;
10591 double lon1 = sa->longitude;
10592 double lat, lon;
10593 wxPoint target_point;
10594 GetCanvasPointPix(lat1, lon1, &target_point);
10595 bbox.Expand(target_point);
10596 for (int i = 0; i < 18; ++i) {
10597 ll_gc_ll(
10598 lat1, lon1,
10599 sa->left_bound_deg +
10600 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10601 sa->radius_m / 1852.0, &lat, &lon);
10602 GetCanvasPointPix(lat, lon, &target_point);
10603 bbox.Expand(target_point);
10604 }
10605 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10606 &lat, &lon);
10607 GetCanvasPointPix(lat, lon, &target_point);
10608 bbox.Expand(target_point);
10609 break;
10610 }
10611 }
10612 }
10613
10614 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10615 area_notices.push_back(&area_notice);
10616 }
10617 }
10618 }
10619 }
10620 }
10621
10622 if (target_chart || !area_notices.empty() || file.HasName()) {
10623 // Go get the array of all objects at the cursor lat/lon
10624 int sel_rad_pix = 5;
10625 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10626
10627 // Make sure we always get the lights from an object, even if we are
10628 // currently not displaying lights on the chart.
10629
10630 SetCursor(wxCURSOR_WAIT);
10631 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10632 if (!lightsVis) SetShowENCLights(true);
10633 ;
10634
10635 ListOfObjRazRules *rule_list = NULL;
10636 ListOfPI_S57Obj *pi_rule_list = NULL;
10637 if (Chs57)
10638 rule_list =
10639 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10640 else if (target_plugin_chart)
10641 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10642 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10643
10644 ListOfObjRazRules *overlay_rule_list = NULL;
10645 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10646 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10647
10648 if (CHs57_Overlay) {
10649 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10650 zlat, zlon, SelectRadius, &GetVP());
10651 }
10652
10653 if (!lightsVis) SetShowENCLights(false);
10654
10655 wxString objText;
10656 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10657 wxString face = dFont->GetFaceName();
10658
10659 if (NULL == g_pObjectQueryDialog) {
10661 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10662 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10663 }
10664
10665 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10666 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10667
10668#ifdef __WXOSX__
10669 // Auto Adjustment for dark mode
10670 fg = g_pObjectQueryDialog->GetForegroundColour();
10671#endif
10672
10673 objText.Printf(
10674 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10675 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10676
10677#ifdef __WXOSX__
10678 int points = dFont->GetPointSize();
10679#else
10680 int points = dFont->GetPointSize() + 1;
10681#endif
10682
10683 int sizes[7];
10684 for (int i = -2; i < 5; i++) {
10685 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10686 }
10687 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10688
10689 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10690
10691 if (overlay_rule_list && CHs57_Overlay) {
10692 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10693 objText << "<hr noshade>";
10694 }
10695
10696 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10697 an != area_notices.end(); ++an) {
10698 objText << "<b>AIS Area Notice:</b> ";
10699 objText << ais8_001_22_notice_names[(*an)->notice_type];
10700 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10701 (*an)->sub_areas.begin();
10702 sa != (*an)->sub_areas.end(); ++sa)
10703 if (!sa->text.empty()) objText << sa->text;
10704 objText << "<br>expires: " << (*an)->expiry_time.Format();
10705 objText << "<hr noshade>";
10706 }
10707
10708 if (Chs57)
10709 objText << Chs57->CreateObjDescriptions(rule_list);
10710 else if (target_plugin_chart)
10711 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10712 pi_rule_list);
10713
10714 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10715
10716 // Add the additional info files
10717 wxString AddFiles, filenameOK;
10718 int filecount = 0;
10719 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10720 // plugin
10721
10722 AddFiles = wxString::Format(
10723 "<hr noshade><br><b>Additional info files attached to: </b> "
10724 "<font "
10725 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10726 "cellpadding=3>",
10727 file.GetFullName());
10728 file.Normalize();
10729 file.Assign(file.GetPath(), "");
10730 wxDir dir(file.GetFullPath());
10731 wxString filename;
10732 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10733 while (cont) {
10734 file.Assign(dir.GetNameWithSep().append(filename));
10735 wxString FormatString =
10736 "<td valign=top><font size=-2><a "
10737 "href=\"%s\">%s</a></font></td>";
10738 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10739 filenameOK = file.GetFullPath(); // remember last valid name
10740 // we are making a 3 columns table. New row only every third file
10741 if (3 * ((int)filecount / 3) == filecount)
10742 FormatString.Prepend("<tr>"); // new row
10743 else
10744 FormatString.Prepend(
10745 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10746 // spacer column
10747
10748 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10749 file.GetFullName());
10750 filecount++;
10751 }
10752 cont = dir.GetNext(&filename);
10753 }
10754 objText << AddFiles << "</table>";
10755 }
10756 objText << "</font>";
10757 objText << "</body></html>";
10758
10759 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10760 g_pObjectQueryDialog->SetHTMLPage(objText);
10761 g_pObjectQueryDialog->Show();
10762 }
10763 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10764 // generate an event to avoid double code
10765 wxHtmlLinkInfo hli(filenameOK);
10766 wxHtmlLinkEvent hle(1, hli);
10767 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10768 }
10769
10770 if (rule_list) rule_list->Clear();
10771 delete rule_list;
10772
10773 if (overlay_rule_list) overlay_rule_list->Clear();
10774 delete overlay_rule_list;
10775
10776 if (pi_rule_list) pi_rule_list->Clear();
10777 delete pi_rule_list;
10778
10779 SetCursor(wxCURSOR_ARROW);
10780 }
10781}
10782
10783void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10784 bool bNew = false;
10785 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10786 // Dialog
10787 g_pMarkInfoDialog = new MarkInfoDlg(this);
10788 bNew = true;
10789 }
10790
10791 if (1 /*g_bresponsive*/) {
10792 wxSize canvas_size = GetSize();
10793
10794 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10795 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10796
10797 g_pMarkInfoDialog->Layout();
10798
10799 wxPoint canvas_pos = GetPosition();
10800 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10801
10802 bool newFit = false;
10803 if (canvas_size.x < fitted_size.x) {
10804 fitted_size.x = canvas_size.x - 40;
10805 if (canvas_size.y < fitted_size.y)
10806 fitted_size.y -= 40; // scrollbar added
10807 }
10808 if (canvas_size.y < fitted_size.y) {
10809 fitted_size.y = canvas_size.y - 40;
10810 if (canvas_size.x < fitted_size.x)
10811 fitted_size.x -= 40; // scrollbar added
10812 }
10813
10814 if (newFit) {
10815 g_pMarkInfoDialog->SetSize(fitted_size);
10816 g_pMarkInfoDialog->Centre();
10817 }
10818 }
10819
10820 markPoint->m_bRPIsBeingEdited = false;
10821
10822 wxString title_base = _("Mark Properties");
10823 if (markPoint->m_bIsInRoute) {
10824 title_base = _("Waypoint Properties");
10825 }
10826 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10827 g_pMarkInfoDialog->UpdateProperties();
10828 if (markPoint->m_bIsInLayer) {
10829 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10830 GetLayerName(markPoint->m_LayerID)));
10831 g_pMarkInfoDialog->SetDialogTitle(caption);
10832 } else
10833 g_pMarkInfoDialog->SetDialogTitle(title_base);
10834
10835 g_pMarkInfoDialog->Show();
10836 g_pMarkInfoDialog->Raise();
10837 g_pMarkInfoDialog->InitialFocus();
10838 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10839}
10840
10841void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10842 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10843 pRoutePropDialog->SetRouteAndUpdate(selected);
10844 // pNew->UpdateProperties();
10845 pRoutePropDialog->Show();
10846 pRoutePropDialog->Raise();
10847 return;
10848 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10849 this); // There is one global instance of the RouteProp Dialog
10850
10851 if (g_bresponsive) {
10852 wxSize canvas_size = GetSize();
10853 wxPoint canvas_pos = GetPosition();
10854 wxSize fitted_size = pRoutePropDialog->GetSize();
10855 ;
10856
10857 if (canvas_size.x < fitted_size.x) {
10858 fitted_size.x = canvas_size.x;
10859 if (canvas_size.y < fitted_size.y)
10860 fitted_size.y -= 20; // scrollbar added
10861 }
10862 if (canvas_size.y < fitted_size.y) {
10863 fitted_size.y = canvas_size.y;
10864 if (canvas_size.x < fitted_size.x)
10865 fitted_size.x -= 20; // scrollbar added
10866 }
10867
10868 pRoutePropDialog->SetSize(fitted_size);
10869 pRoutePropDialog->Centre();
10870
10871 // int xp = (canvas_size.x - fitted_size.x)/2;
10872 // int yp = (canvas_size.y - fitted_size.y)/2;
10873
10874 wxPoint xxp = ClientToScreen(canvas_pos);
10875 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10876 }
10877
10878 pRoutePropDialog->SetRouteAndUpdate(selected);
10879
10880 pRoutePropDialog->Show();
10881
10882 Refresh(false);
10883}
10884
10885void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10886 pTrackPropDialog = TrackPropDlg::getInstance(
10887 this); // There is one global instance of the RouteProp Dialog
10888
10889 pTrackPropDialog->SetTrackAndUpdate(selected);
10891
10892 pTrackPropDialog->Show();
10893
10894 Refresh(false);
10895}
10896
10897void pupHandler_PasteWaypoint() {
10898 Kml kml;
10899
10900 int pasteBuffer = kml.ParsePasteBuffer();
10901 RoutePoint *pasted = kml.GetParsedRoutePoint();
10902 if (!pasted) return;
10903
10904 double nearby_radius_meters =
10905 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10906
10907 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10908 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10909
10910 int answer = wxID_NO;
10911 if (nearPoint && !nearPoint->m_bIsInLayer) {
10912 wxString msg;
10913 msg << _(
10914 "There is an existing waypoint at the same location as the one you are "
10915 "pasting. Would you like to merge the pasted data with it?\n\n");
10916 msg << _("Answering 'No' will create a new waypoint at the same location.");
10917 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10918 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10919 }
10920
10921 if (answer == wxID_YES) {
10922 nearPoint->SetName(pasted->GetName());
10923 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10924 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10925 pRouteManagerDialog->UpdateWptListCtrl();
10926 }
10927
10928 if (answer == wxID_NO) {
10929 RoutePoint *newPoint = new RoutePoint(pasted);
10930 newPoint->m_bIsolatedMark = true;
10931 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10932 newPoint);
10933 // pConfig->AddNewWayPoint(newPoint, -1);
10934 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10935
10936 pWayPointMan->AddRoutePoint(newPoint);
10937 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10938 pRouteManagerDialog->UpdateWptListCtrl();
10939 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10940 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10941 }
10942
10943 top_frame::Get()->InvalidateAllGL();
10944 top_frame::Get()->RefreshAllCanvas(false);
10945}
10946
10947void pupHandler_PasteRoute() {
10948 Kml kml;
10949
10950 int pasteBuffer = kml.ParsePasteBuffer();
10951 Route *pasted = kml.GetParsedRoute();
10952 if (!pasted) return;
10953
10954 double nearby_radius_meters =
10955 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10956
10957 RoutePoint *curPoint;
10958 RoutePoint *nearPoint;
10959 RoutePoint *prevPoint = NULL;
10960
10961 bool mergepoints = false;
10962 bool createNewRoute = true;
10963 int existingWaypointCounter = 0;
10964
10965 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10966 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10967 nearPoint = pWayPointMan->GetNearbyWaypoint(
10968 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10969 if (nearPoint) {
10970 mergepoints = true;
10971 existingWaypointCounter++;
10972 // Small hack here to avoid both extending RoutePoint and repeating all
10973 // the GetNearbyWaypoint calculations. Use existin data field in
10974 // RoutePoint as temporary storage.
10975 curPoint->m_bPtIsSelected = true;
10976 }
10977 }
10978
10979 int answer = wxID_NO;
10980 if (mergepoints) {
10981 wxString msg;
10982 msg << _(
10983 "There are existing waypoints at the same location as some of the ones "
10984 "you are pasting. Would you like to just merge the pasted data into "
10985 "them?\n\n");
10986 msg << _("Answering 'No' will create all new waypoints for this route.");
10987 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10988 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10989
10990 if (answer == wxID_CANCEL) {
10991 return;
10992 }
10993 }
10994
10995 // If all waypoints exist since before, and a route with the same name, we
10996 // don't create a new route.
10997 if (mergepoints && answer == wxID_YES &&
10998 existingWaypointCounter == pasted->GetnPoints()) {
10999 for (Route *proute : *pRouteList) {
11000 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11001 createNewRoute = false;
11002 break;
11003 }
11004 }
11005 }
11006
11007 Route *newRoute = 0;
11008 RoutePoint *newPoint = 0;
11009
11010 if (createNewRoute) {
11011 newRoute = new Route();
11012 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11013 }
11014
11015 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11016 curPoint = pasted->GetPoint(i);
11017 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11018 curPoint->m_bPtIsSelected = false;
11019 newPoint = pWayPointMan->GetNearbyWaypoint(
11020 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11021 newPoint->SetName(curPoint->GetName());
11022 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11023
11024 if (createNewRoute) newRoute->AddPoint(newPoint);
11025 } else {
11026 curPoint->m_bPtIsSelected = false;
11027
11028 newPoint = new RoutePoint(curPoint);
11029 newPoint->m_bIsolatedMark = false;
11030 newPoint->SetIconName("circle");
11031 newPoint->m_bIsVisible = true;
11032 newPoint->m_bShowName = false;
11033 newPoint->SetShared(false);
11034
11035 newRoute->AddPoint(newPoint);
11036 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11037 newPoint);
11038 // pConfig->AddNewWayPoint(newPoint, -1);
11039 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11040 pWayPointMan->AddRoutePoint(newPoint);
11041 }
11042 if (i > 1 && createNewRoute)
11043 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11044 curPoint->m_lat, curPoint->m_lon,
11045 prevPoint, newPoint, newRoute);
11046 prevPoint = newPoint;
11047 }
11048
11049 if (createNewRoute) {
11050 pRouteList->push_back(newRoute);
11051 // pConfig->AddNewRoute(newRoute); // use auto next num
11052 NavObj_dB::GetInstance().InsertRoute(newRoute);
11053
11054 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11055 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11056 }
11057
11058 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11059 pRouteManagerDialog->UpdateRouteListCtrl();
11060 pRouteManagerDialog->UpdateWptListCtrl();
11061 }
11062 top_frame::Get()->InvalidateAllGL();
11063 top_frame::Get()->RefreshAllCanvas(false);
11064 }
11065 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11066 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11067}
11068
11069void pupHandler_PasteTrack() {
11070 Kml kml;
11071
11072 int pasteBuffer = kml.ParsePasteBuffer();
11073 Track *pasted = kml.GetParsedTrack();
11074 if (!pasted) return;
11075
11076 TrackPoint *curPoint;
11077
11078 Track *newTrack = new Track();
11079 TrackPoint *newPoint;
11080 TrackPoint *prevPoint = NULL;
11081
11082 newTrack->SetName(pasted->GetName());
11083
11084 for (int i = 0; i < pasted->GetnPoints(); i++) {
11085 curPoint = pasted->GetPoint(i);
11086
11087 newPoint = new TrackPoint(curPoint);
11088
11089 wxDateTime now = wxDateTime::Now();
11090 newPoint->SetCreateTime(curPoint->GetCreateTime());
11091
11092 newTrack->AddPoint(newPoint);
11093
11094 if (prevPoint)
11095 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11096 newPoint->m_lat, newPoint->m_lon,
11097 prevPoint, newPoint, newTrack);
11098
11099 prevPoint = newPoint;
11100 }
11101
11102 g_TrackList.push_back(newTrack);
11103 // pConfig->AddNewTrack(newTrack);
11104 NavObj_dB::GetInstance().InsertTrack(newTrack);
11105
11106 top_frame::Get()->InvalidateAllGL();
11107 top_frame::Get()->RefreshAllCanvas(false);
11108}
11109
11110bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11111 wxJSONValue v;
11112 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11113 v["CursorPosition_x"] = x;
11114 v["CursorPosition_y"] = y;
11115 // Send a limited set of selection types depending on what is
11116 // found under the mouse point.
11117 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11118 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11119 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11120
11121 wxJSONWriter w;
11122 wxString out;
11123 w.Write(v, out);
11124 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11125
11126 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11127
11128#if 0
11129#define SELTYPE_UNKNOWN 0x0001
11130#define SELTYPE_ROUTEPOINT 0x0002
11131#define SELTYPE_ROUTESEGMENT 0x0004
11132#define SELTYPE_TIDEPOINT 0x0008
11133#define SELTYPE_CURRENTPOINT 0x0010
11134#define SELTYPE_ROUTECREATE 0x0020
11135#define SELTYPE_AISTARGET 0x0040
11136#define SELTYPE_MARKPOINT 0x0080
11137#define SELTYPE_TRACKSEGMENT 0x0100
11138#define SELTYPE_DRAGHANDLE 0x0200
11139#endif
11140
11141 if (g_bhide_context_menus) return true;
11142 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11143 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11144 m_pIDXCandidate, m_nmea_log);
11145
11146 Connect(
11147 wxEVT_COMMAND_MENU_SELECTED,
11148 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11149
11150#ifdef __WXGTK__
11151 // Funny requirement here for gtk, to clear the menu trigger event
11152 // TODO
11153 // Causes a slight "flasH" of the menu,
11154 if (m_inLongPress) {
11155 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11156 m_inLongPress = false;
11157 }
11158#endif
11159
11160 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11161
11162 Disconnect(
11163 wxEVT_COMMAND_MENU_SELECTED,
11164 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11165
11166 delete m_canvasMenu;
11167 m_canvasMenu = NULL;
11168
11169#ifdef __WXQT__
11170 // gFrame->SurfaceToolbar();
11171 // g_MainToolbar->Raise();
11172#endif
11173
11174 return true;
11175}
11176
11177void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11178 // Pass menu events from the canvas to the menu handler
11179 // This is necessarily in ChartCanvas since that is the menu's parent.
11180 if (m_canvasMenu) {
11181 m_canvasMenu->PopupMenuHandler(event);
11182 }
11183 return;
11184}
11185
11186void ChartCanvas::StartRoute() {
11187 // Do not allow more than one canvas to create a route at one time.
11188 if (g_brouteCreating) return;
11189
11190 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11191
11192 g_brouteCreating = true;
11193 m_routeState = 1;
11194 m_bDrawingRoute = false;
11195 SetCursor(*pCursorPencil);
11196 // SetCanvasToolbarItemState(ID_ROUTE, true);
11197 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11198
11199 HideGlobalToolbar();
11200
11201#ifdef __ANDROID__
11202 androidSetRouteAnnunciator(true);
11203#endif
11204}
11205
11206wxString ChartCanvas::FinishRoute() {
11207 m_routeState = 0;
11208 m_prev_pMousePoint = NULL;
11209 m_bDrawingRoute = false;
11210 wxString rv = "";
11211 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11212
11213 // SetCanvasToolbarItemState(ID_ROUTE, false);
11214 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11215#ifdef __ANDROID__
11216 androidSetRouteAnnunciator(false);
11217#endif
11218
11219 SetCursor(*pCursorArrow);
11220
11221 if (m_pMouseRoute) {
11222 if (m_bAppendingRoute) {
11223 // pConfig->UpdateRoute(m_pMouseRoute);
11224 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11225 } else {
11226 if (m_pMouseRoute->GetnPoints() > 1) {
11227 // pConfig->AddNewRoute(m_pMouseRoute);
11228 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11229 } else {
11230 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11231 m_pMouseRoute = NULL;
11232 }
11233 }
11234 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11235
11236 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11237 (pRoutePropDialog->IsShown())) {
11238 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11239 }
11240
11241 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11242 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11243 pRouteManagerDialog->UpdateRouteListCtrl();
11244 }
11245 }
11246 m_bAppendingRoute = false;
11247 m_pMouseRoute = NULL;
11248
11249 m_pSelectedRoute = NULL;
11250
11251 undo->InvalidateUndo();
11252 top_frame::Get()->RefreshAllCanvas(true);
11253
11254 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11255
11256 ShowGlobalToolbar();
11257
11258 g_brouteCreating = false;
11259
11260 return rv;
11261}
11262
11263void ChartCanvas::HideGlobalToolbar() {
11264 if (m_canvasIndex == 0) {
11265 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11266 }
11267}
11268
11269void ChartCanvas::ShowGlobalToolbar() {
11270 if (m_canvasIndex == 0) {
11271 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11272 }
11273}
11274
11275void ChartCanvas::ShowAISTargetList() {
11276 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11277 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11278 }
11279
11280 g_pAISTargetList->UpdateAISTargetList();
11281}
11282
11283void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11284 if (!m_bShowOutlines) return;
11285
11286 if (!ChartData) return;
11287
11288 int nEntry = ChartData->GetChartTableEntries();
11289
11290 for (int i = 0; i < nEntry; i++) {
11291 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11292
11293 // Check to see if the candidate chart is in the currently active group
11294 bool b_group_draw = false;
11295 if (m_groupIndex > 0) {
11296 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11297 int index = pt->GetGroupArray()[ig];
11298 if (m_groupIndex == index) {
11299 b_group_draw = true;
11300 break;
11301 }
11302 }
11303 } else
11304 b_group_draw = true;
11305
11306 if (b_group_draw) RenderChartOutline(dc, i, vp);
11307 }
11308
11309 // On CM93 Composite Charts, draw the outlines of the next smaller
11310 // scale cell
11311 cm93compchart *pcm93 = NULL;
11312 if (VPoint.b_quilt) {
11313 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11314 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11315 pcm93 = (cm93compchart *)pch;
11316 break;
11317 }
11318 } else if (m_singleChart &&
11319 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11320 pcm93 = (cm93compchart *)m_singleChart;
11321
11322 if (pcm93) {
11323 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11324 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11325
11326 if (zoom_factor > 8.0) {
11327 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11328 dc.SetPen(mPen);
11329 } else {
11330 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11331 dc.SetPen(mPen);
11332 }
11333
11334 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11335 }
11336}
11337
11338void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11339#ifdef ocpnUSE_GL
11340 if (g_bopengl && m_glcc) {
11341 /* opengl version specially optimized */
11342 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11343 return;
11344 }
11345#endif
11346
11347 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11348 if (!ChartData->IsChartAvailable(dbIndex)) return;
11349 }
11350
11351 float plylat, plylon;
11352 float plylat1, plylon1;
11353
11354 int pixx, pixy, pixx1, pixy1;
11355
11356 LLBBox box;
11357 ChartData->GetDBBoundingBox(dbIndex, box);
11358
11359 // Don't draw an outline in the case where the chart covers the entire world
11360 // */
11361 if (box.GetLonRange() == 360) return;
11362
11363 double lon_bias = 0;
11364 // chart is outside of viewport lat/lon bounding box
11365 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11366
11367 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11368
11369 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11370 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11371
11372 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11373 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11374
11375 else
11376 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11377
11378 // Are there any aux ply entries?
11379 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11380 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11381 {
11382 wxPoint r, r1;
11383
11384 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11385 plylon += lon_bias;
11386
11387 GetCanvasPointPix(plylat, plylon, &r);
11388 pixx = r.x;
11389 pixy = r.y;
11390
11391 for (int i = 0; i < nPly - 1; i++) {
11392 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11393 plylon1 += lon_bias;
11394
11395 GetCanvasPointPix(plylat1, plylon1, &r1);
11396 pixx1 = r1.x;
11397 pixy1 = r1.y;
11398
11399 int pixxs1 = pixx1;
11400 int pixys1 = pixy1;
11401
11402 bool b_skip = false;
11403
11404 if (vp.chart_scale > 5e7) {
11405 // calculate projected distance between these two points in meters
11406 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11407 pow((double)(pixy1 - pixy), 2)) /
11408 vp.view_scale_ppm;
11409
11410 if (dist > 0.0) {
11411 // calculate GC distance between these two points in meters
11412 double distgc =
11413 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11414
11415 // If the distances are nonsense, it means that the scale is very
11416 // small and the segment wrapped the world So skip it....
11417 // TODO improve this to draw two segments
11418 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11419 b_skip = true;
11420 } else
11421 b_skip = true;
11422 }
11423
11424 ClipResult res = cohen_sutherland_line_clip_i(
11425 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11426 if (res != Invisible && !b_skip)
11427 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11428
11429 plylat = plylat1;
11430 plylon = plylon1;
11431 pixx = pixxs1;
11432 pixy = pixys1;
11433 }
11434
11435 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11436 plylon1 += lon_bias;
11437
11438 GetCanvasPointPix(plylat1, plylon1, &r1);
11439 pixx1 = r1.x;
11440 pixy1 = r1.y;
11441
11442 ClipResult res = cohen_sutherland_line_clip_i(
11443 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11444 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11445 }
11446
11447 else // Use Aux PlyPoints
11448 {
11449 wxPoint r, r1;
11450
11451 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11452 for (int j = 0; j < nAuxPlyEntries; j++) {
11453 int nAuxPly =
11454 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11455 GetCanvasPointPix(plylat, plylon, &r);
11456 pixx = r.x;
11457 pixy = r.y;
11458
11459 for (int i = 0; i < nAuxPly - 1; i++) {
11460 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11461
11462 GetCanvasPointPix(plylat1, plylon1, &r1);
11463 pixx1 = r1.x;
11464 pixy1 = r1.y;
11465
11466 int pixxs1 = pixx1;
11467 int pixys1 = pixy1;
11468
11469 bool b_skip = false;
11470
11471 if (vp.chart_scale > 5e7) {
11472 // calculate projected distance between these two points in meters
11473 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11474 ((pixy1 - pixy) * (pixy1 - pixy))) /
11475 vp.view_scale_ppm;
11476 if (dist > 0.0) {
11477 // calculate GC distance between these two points in meters
11478 double distgc =
11479 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11480
11481 // If the distances are nonsense, it means that the scale is very
11482 // small and the segment wrapped the world So skip it....
11483 // TODO improve this to draw two segments
11484 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11485 b_skip = true;
11486 } else
11487 b_skip = true;
11488 }
11489
11490 ClipResult res = cohen_sutherland_line_clip_i(
11491 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11492 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11493
11494 plylat = plylat1;
11495 plylon = plylon1;
11496 pixx = pixxs1;
11497 pixy = pixys1;
11498 }
11499
11500 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11501 GetCanvasPointPix(plylat1, plylon1, &r1);
11502 pixx1 = r1.x;
11503 pixy1 = r1.y;
11504
11505 ClipResult res = cohen_sutherland_line_clip_i(
11506 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11507 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11508 }
11509 }
11510}
11511
11512static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11513 const wxArrayString &legend) {
11514 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11515
11516 int pointsize = dFont->GetPointSize();
11517 pointsize /= OCPN_GetWinDIPScaleFactor();
11518
11519 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11520 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11521 false, dFont->GetFaceName());
11522
11523 dc.SetFont(*psRLI_font);
11524
11525 int h = 0;
11526 int w = 0;
11527 int hl, wl;
11528
11529 int xp, yp;
11530 int hilite_offset = 3;
11531
11532 for (wxString line : legend) {
11533#ifdef __WXMAC__
11534 wxScreenDC sdc;
11535 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11536#else
11537 dc.GetTextExtent(line, &wl, &hl);
11538 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11539 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11540#endif
11541 h += hl;
11542 w = wxMax(w, wl);
11543 }
11544 w += (hl / 2); // Add a little right pad
11545
11546 xp = ref_point.x - w;
11547 yp = ref_point.y;
11548 yp += hilite_offset;
11549
11550 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11551
11552 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11553 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11554
11555 for (wxString line : legend) {
11556 dc.DrawText(line, xp, yp);
11557 yp += hl;
11558 }
11559}
11560
11561void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11562 if (!g_bAllowShipToActive) return;
11563
11564 Route *rt = g_pRouteMan->GetpActiveRoute();
11565 if (!rt) return;
11566
11567 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11568 wxPoint2DDouble pa, pb;
11570 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11571
11572 // set pen
11573 int width =
11574 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11575 if (rt->m_width != wxPENSTYLE_INVALID)
11576 width = rt->m_width; // set route pen style if any
11577 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11578 g_shipToActiveStyle, 5)]; // get setting pen style
11579 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11580 wxColour color =
11581 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11582 : // set setting route pen color
11583 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11584 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11585
11586 dc.SetPen(*mypen);
11587 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11588
11589 if (!Use_Opengl)
11590 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11591 (int)pb.m_y, GetVP(), true);
11592
11593#ifdef ocpnUSE_GL
11594 else {
11595#ifdef USE_ANDROID_GLES2
11596 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11597#else
11598 if (style != wxPENSTYLE_SOLID) {
11599 if (glChartCanvas::dash_map.find(style) !=
11600 glChartCanvas::dash_map.end()) {
11601 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11602 dc.SetPen(*mypen);
11603 }
11604 }
11605 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11606#endif
11607
11608 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11609 (int)pb.m_x, (int)pb.m_y, GetVP());
11610 }
11611#endif
11612 }
11613}
11614
11615void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11616 Route *route = 0;
11617 if (m_routeState >= 2) route = m_pMouseRoute;
11618 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11619 route = m_pMeasureRoute;
11620
11621 if (!route) return;
11622
11623 // Validate route pointer
11624 if (!g_pRouteMan->IsRouteValid(route)) return;
11625
11626 double render_lat = m_cursor_lat;
11627 double render_lon = m_cursor_lon;
11628
11629 int np = route->GetnPoints();
11630 if (np) {
11631 if (g_btouch && (np > 1)) np--;
11632 RoutePoint rp = route->GetPoint(np);
11633 render_lat = rp.m_lat;
11634 render_lon = rp.m_lon;
11635 }
11636
11637 double rhumbBearing, rhumbDist;
11638 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11639 &rhumbBearing, &rhumbDist);
11640 double brg = rhumbBearing;
11641 double dist = rhumbDist;
11642
11643 // Skip GreatCircle rubberbanding on touch devices.
11644 if (!g_btouch) {
11645 double gcBearing, gcBearing2, gcDist;
11646 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11647 m_cursor_lat, &gcDist, &gcBearing,
11648 &gcBearing2);
11649 double gcDistm = gcDist / 1852.0;
11650
11651 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11652 rhumbBearing = 90.;
11653
11654 wxPoint destPoint, lastPoint;
11655
11656 route->m_NextLegGreatCircle = false;
11657 int milesDiff = rhumbDist - gcDistm;
11658 if (milesDiff > 1) {
11659 brg = gcBearing;
11660 dist = gcDistm;
11661 route->m_NextLegGreatCircle = true;
11662 }
11663
11664 // FIXME (MacOS, the first segment is rendered wrong)
11665 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11666 &lastPoint);
11667
11668 if (route->m_NextLegGreatCircle) {
11669 for (int i = 1; i <= milesDiff; i++) {
11670 double p = (double)i * (1.0 / (double)milesDiff);
11671 double pLat, pLon;
11672 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11673 &pLon, &pLat, &gcBearing2);
11674 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11675 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11676 false);
11677 lastPoint = destPoint;
11678 }
11679 } else {
11680 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11681 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11682 false);
11683 if (m_bMeasure_DistCircle) {
11684 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11685 powf((float)(r_rband.y - lastPoint.y), 2));
11686
11687 dc.SetPen(*g_pRouteMan->GetRoutePen());
11688 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11689 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11690 }
11691 }
11692 }
11693 }
11694
11695 wxString routeInfo;
11696 wxArrayString infoArray;
11697 double varBrg = 0;
11698 if (g_bShowTrue)
11699 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11700 0x00B0);
11701
11702 if (g_bShowMag) {
11703 double latAverage = (m_cursor_lat + render_lat) / 2;
11704 double lonAverage = (m_cursor_lon + render_lon) / 2;
11705 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11706
11707 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11708 (int)varBrg, 0x00B0);
11709 }
11710 routeInfo << " " << FormatDistanceAdaptive(dist);
11711 infoArray.Add(routeInfo);
11712 routeInfo.Clear();
11713
11714 // To make it easier to use a route as a bearing on a charted object add for
11715 // the first leg also the reverse bearing.
11716 if (np == 1) {
11717 routeInfo << "Reverse: ";
11718 if (g_bShowTrue)
11719 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11720 (int)(brg + 180.) % 360, 0x00B0);
11721 if (g_bShowMag)
11722 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11723 (int)(varBrg + 180.) % 360, 0x00B0);
11724 infoArray.Add(routeInfo);
11725 routeInfo.Clear();
11726 }
11727
11728 wxString s0;
11729 if (!route->m_bIsInLayer)
11730 s0.Append(_("Route") + ": ");
11731 else
11732 s0.Append(_("Layer Route: "));
11733
11734 double disp_length = route->m_route_length;
11735 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11736 s0 += FormatDistanceAdaptive(disp_length);
11737
11738 infoArray.Add(s0);
11739 routeInfo.Clear();
11740
11741 RouteLegInfo(dc, r_rband, infoArray);
11742
11743 m_brepaint_piano = true;
11744}
11745
11746void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11747 if (!m_bShowVisibleSectors) return;
11748
11749 if (g_bDeferredInitDone) {
11750 // need to re-evaluate sectors?
11751 double rhumbBearing, rhumbDist;
11752 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11753 &rhumbBearing, &rhumbDist);
11754
11755 if (rhumbDist > 0.05) // miles
11756 {
11757 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11758 m_sectorlegsVisible);
11759 m_sector_glat = gLat;
11760 m_sector_glon = gLon;
11761 }
11762 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11763 }
11764}
11765
11766void ChartCanvas::WarpPointerDeferred(int x, int y) {
11767 warp_x = x;
11768 warp_y = y;
11769 warp_flag = true;
11770}
11771
11772int s_msg;
11773
11774void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11775 if (!ps52plib) return;
11776
11777 if (VPoint.b_quilt) { // quilted
11778 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11779
11780 if (m_pQuilt->IsQuiltVector()) {
11781 if (ps52plib->GetStateHash() != m_s52StateHash) {
11782 UpdateS52State();
11783 m_s52StateHash = ps52plib->GetStateHash();
11784 }
11785 }
11786 } else {
11787 if (ps52plib->GetStateHash() != m_s52StateHash) {
11788 UpdateS52State();
11789 m_s52StateHash = ps52plib->GetStateHash();
11790 }
11791 }
11792
11793 // Plugin charts
11794 bool bSendPlibState = true;
11795 if (VPoint.b_quilt) { // quilted
11796 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11797 }
11798
11799 if (bSendPlibState) {
11800 wxJSONValue v;
11801 v["OpenCPN Version Major"] = VERSION_MAJOR;
11802 v["OpenCPN Version Minor"] = VERSION_MINOR;
11803 v["OpenCPN Version Patch"] = VERSION_PATCH;
11804 v["OpenCPN Version Date"] = VERSION_DATE;
11805 v["OpenCPN Version Full"] = VERSION_FULL;
11806
11807 // S52PLIB state
11808 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11809 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11810 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11811 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11812 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11813 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11814 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11815
11816 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11817
11818 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11819 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11820
11821 // Global S52 options
11822
11823 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11824 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11825 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11826 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11827 ps52plib->m_bShowS57ImportantTextOnly;
11828 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11829 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11830 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11831 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11832 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11833
11834 // Some global GUI parameters, for completeness
11835 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11836 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11837 v["OpenCPN Scale Factor Exp"] =
11838 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11839 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11840
11841 wxJSONWriter w;
11842 wxString out;
11843 w.Write(v, out);
11844
11845 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11846 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11847 g_lastS52PLIBPluginMessage = out;
11848 }
11849 }
11850}
11851int spaint;
11852int s_in_update;
11853void ChartCanvas::OnPaint(wxPaintEvent &event) {
11854 wxPaintDC dc(this);
11855
11856 // GetToolbar()->Show( m_bToolbarEnable );
11857
11858 // Paint updates may have been externally disabled (temporarily, to avoid
11859 // Yield() recursion performance loss) It is important that the wxPaintDC is
11860 // built, even if we elect to not process this paint message. Otherwise, the
11861 // paint message may not be removed from the message queue, esp on Windows.
11862 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11863
11864 if (!m_b_paint_enable) {
11865 return;
11866 }
11867
11868 // If necessary, reconfigure the S52 PLIB
11870
11871#ifdef ocpnUSE_GL
11872 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11873
11874 if (m_glcc && g_bopengl) {
11875 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11876 s_in_update++;
11877 m_glcc->Update();
11878 s_in_update--;
11879 }
11880
11881 return;
11882 }
11883#endif
11884
11885 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11886
11887 wxRegion ru = GetUpdateRegion();
11888
11889 int rx, ry, rwidth, rheight;
11890 ru.GetBox(rx, ry, rwidth, rheight);
11891
11892#ifdef ocpnUSE_DIBSECTION
11893 ocpnMemDC temp_dc;
11894#else
11895 wxMemoryDC temp_dc;
11896#endif
11897
11898 long height = GetVP().pix_height;
11899
11900#ifdef __WXMAC__
11901 // On OS X we have to explicitly extend the region for the piano area
11902 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11903 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11904 height += m_Piano->GetHeight();
11905#endif // __WXMAC__
11906 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11907
11908 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11909 if (pthumbwin) {
11910 int thumbx, thumby, thumbsx, thumbsy;
11911 pthumbwin->GetPosition(&thumbx, &thumby);
11912 pthumbwin->GetSize(&thumbsx, &thumbsy);
11913 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11914
11915 if (pthumbwin->IsShown()) {
11916 rgn_chart.Subtract(rgn_thumbwin);
11917 ru.Subtract(rgn_thumbwin);
11918 }
11919 }
11920
11921 // subtract the chart bar if it isn't transparent, and determine if we need to
11922 // paint it
11923 wxRegion rgn_blit = ru;
11924 if (g_bShowChartBar) {
11925 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11926 GetClientSize().x, m_Piano->GetHeight());
11927
11928 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11929 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11930 if (style->chartStatusWindowTransparent)
11931 m_brepaint_piano = true;
11932 else
11933 ru.Subtract(chart_bar_rect);
11934 }
11935 }
11936
11937 if (m_Compass && m_Compass->IsShown()) {
11938 wxRect compassRect = m_Compass->GetRect();
11939 if (ru.Contains(compassRect) != wxOutRegion) {
11940 ru.Subtract(compassRect);
11941 }
11942 }
11943
11944 if (m_notification_button) {
11945 wxRect noteRect = m_notification_button->GetRect();
11946 if (ru.Contains(noteRect) != wxOutRegion) {
11947 ru.Subtract(noteRect);
11948 }
11949 }
11950
11951 // Is this viewpoint the same as the previously painted one?
11952 bool b_newview = true;
11953
11954 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11955 (m_cache_vp.rotation == VPoint.rotation) &&
11956 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11957 m_cache_vp.IsValid()) {
11958 b_newview = false;
11959 }
11960
11961 // If the ViewPort is skewed or rotated, we may be able to use the cached
11962 // rotated bitmap.
11963 bool b_rcache_ok = false;
11964 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11965 b_rcache_ok = !b_newview;
11966
11967 // Make a special VP
11968 if (VPoint.b_MercatorProjectionOverride)
11969 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11970 ViewPort svp = VPoint;
11971
11972 svp.pix_width = svp.rv_rect.width;
11973 svp.pix_height = svp.rv_rect.height;
11974
11975 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11976 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11977 // VPoint.rv_rect.height);
11978
11979 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11980
11981 // If we are going to use the cached rotated image, there is no need to fetch
11982 // any chart data and this will do it...
11983 if (b_rcache_ok) chart_get_region.Clear();
11984
11985 // Blit pan acceleration
11986 if (VPoint.b_quilt) // quilted
11987 {
11988 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11989
11990 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11991
11992 bool busy = false;
11993 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11994 m_cache_vp.rotation != VPoint.rotation)) {
11995 AbstractPlatform::ShowBusySpinner();
11996 busy = true;
11997 }
11998
11999 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12000 (m_working_bm.GetHeight() != svp.pix_height))
12001 m_working_bm.Create(svp.pix_width, svp.pix_height,
12002 -1); // make sure the target is big enoug
12003
12004 if (fabs(VPoint.rotation) < 0.01) {
12005 bool b_save = true;
12006
12007 if (g_SencThreadManager) {
12008 if (g_SencThreadManager->GetJobCount()) {
12009 b_save = false;
12010 m_cache_vp.Invalidate();
12011 }
12012 }
12013
12014 // If the saved wxBitmap from last OnPaint is useable
12015 // calculate the blit parameters
12016
12017 // We can only do screen blit painting if subsequent ViewPorts differ by
12018 // whole pixels So, in small scale bFollow mode, force the full screen
12019 // render. This seems a hack....There may be better logic here.....
12020
12021 // if(m_bFollow)
12022 // b_save = false;
12023
12024 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12025 if (b_newview) {
12026 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12027 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12028
12029 int dy = c_new.y - c_old.y;
12030 int dx = c_new.x - c_old.x;
12031
12032 // printf("In OnPaint Trying Blit dx: %d
12033 // dy:%d\n\n", dx, dy);
12034
12035 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12036 if (dx || dy) {
12037 // Blit the reuseable portion of the cached wxBitmap to a working
12038 // bitmap
12039 temp_dc.SelectObject(m_working_bm);
12040
12041 wxMemoryDC cache_dc;
12042 cache_dc.SelectObject(m_cached_chart_bm);
12043
12044 if (dy > 0) {
12045 if (dx > 0) {
12046 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12047 VPoint.pix_height - dy, &cache_dc, dx, dy);
12048 } else {
12049 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12050 VPoint.pix_height - dy, &cache_dc, 0, dy);
12051 }
12052
12053 } else {
12054 if (dx > 0) {
12055 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12056 VPoint.pix_height + dy, &cache_dc, dx, 0);
12057 } else {
12058 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12059 VPoint.pix_height + dy, &cache_dc, 0, 0);
12060 }
12061 }
12062
12063 OCPNRegion update_region;
12064 if (dy) {
12065 if (dy > 0)
12066 update_region.Union(
12067 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12068 else
12069 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12070 }
12071
12072 if (dx) {
12073 if (dx > 0)
12074 update_region.Union(
12075 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12076 else
12077 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12078 }
12079
12080 // Render the new region
12081 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12082 update_region);
12083 cache_dc.SelectObject(wxNullBitmap);
12084 } else {
12085 // No sensible (dx, dy) change in the view, so use the cached
12086 // member bitmap
12087 temp_dc.SelectObject(m_cached_chart_bm);
12088 b_save = false;
12089 }
12090 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12091
12092 } else // not blitable
12093 {
12094 temp_dc.SelectObject(m_working_bm);
12095 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12096 chart_get_region);
12097 }
12098 } else {
12099 // No change in the view, so use the cached member bitmap2
12100 temp_dc.SelectObject(m_cached_chart_bm);
12101 b_save = false;
12102 }
12103 } else // cached bitmap is not yet valid
12104 {
12105 temp_dc.SelectObject(m_working_bm);
12106 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12107 chart_get_region);
12108 }
12109
12110 // Save the fully rendered quilt image as a wxBitmap member of this class
12111 if (b_save) {
12112 // if((m_cached_chart_bm.GetWidth() !=
12113 // svp.pix_width) ||
12114 // (m_cached_chart_bm.GetHeight() !=
12115 // svp.pix_height))
12116 // m_cached_chart_bm.Create(svp.pix_width,
12117 // svp.pix_height, -1); // target wxBitmap
12118 // is big enough
12119 wxMemoryDC scratch_dc_0;
12120 scratch_dc_0.SelectObject(m_cached_chart_bm);
12121 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12122
12123 scratch_dc_0.SelectObject(wxNullBitmap);
12124
12125 m_bm_cache_vp =
12126 VPoint; // save the ViewPort associated with the cached wxBitmap
12127 }
12128 }
12129
12130 else // quilted, rotated
12131 {
12132 temp_dc.SelectObject(m_working_bm);
12133 OCPNRegion chart_get_all_region(
12134 wxRect(0, 0, svp.pix_width, svp.pix_height));
12135 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12136 chart_get_all_region);
12137 }
12138
12139 AbstractPlatform::HideBusySpinner();
12140
12141 }
12142
12143 else // not quilted
12144 {
12145 if (!m_singleChart) {
12146 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12147 dc.Clear();
12148 return;
12149 }
12150
12151 if (!chart_get_region.IsEmpty()) {
12152 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12153 }
12154 }
12155
12156 if (temp_dc.IsOk()) {
12157 // Arrange to render the World Chart vector data behind the rendered
12158 // current chart so that uncovered canvas areas show at least the world
12159 // chart.
12160 OCPNRegion chartValidRegion;
12161 if (!VPoint.b_quilt) {
12162 // Make a region covering the current chart on the canvas
12163
12164 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12165 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12166 else {
12167 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12168 // require that the viewport passed here have pix_width and pix_height
12169 // set to the actual display, not the virtual (rv_rect) sizes
12170 // (the vector calculations require the virtual sizes in svp)
12171
12172 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12173 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12174 }
12175 } else
12176 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12177
12178 temp_dc.DestroyClippingRegion();
12179
12180 // Copy current chart region
12181 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12182
12183 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12184
12185 if (!backgroundRegion.IsEmpty()) {
12186 // Draw the Background Chart only in the areas NOT covered by the
12187 // current chart view
12188
12189 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12190 clipping regions with more than 1 rectangle so... */
12191 wxColour water = pWorldBackgroundChart->water;
12192 if (water.IsOk()) {
12193 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12194 temp_dc.SetBrush(wxBrush(water));
12195 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12196 while (upd.HaveRects()) {
12197 wxRect rect = upd.GetRect();
12198 temp_dc.DrawRectangle(rect);
12199 upd.NextRect();
12200 }
12201 }
12202 // Associate with temp_dc
12203 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12204 temp_dc.SetDeviceClippingRegion(*clip_region);
12205 delete clip_region;
12206
12207 ocpnDC bgdc(temp_dc);
12208 double r = VPoint.rotation;
12209 SetVPRotation(VPoint.skew);
12210
12211 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12212 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12213
12214 SetVPRotation(r);
12215 }
12216 } // temp_dc.IsOk();
12217
12218 wxMemoryDC *pChartDC = &temp_dc;
12219 wxMemoryDC rotd_dc;
12220
12221 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12222 // Can we use the current rotated image cache?
12223 if (!b_rcache_ok) {
12224#ifdef __WXMSW__
12225 wxMemoryDC tbase_dc;
12226 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12227 tbase_dc.SelectObject(bm_base);
12228 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12229 tbase_dc.SelectObject(wxNullBitmap);
12230#else
12231 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12232#endif
12233
12234 wxImage base_image;
12235 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12236
12237 // Use a local static image rotator to improve wxWidgets code profile
12238 // Especially, on GTK the wxRound and wxRealPoint functions are very
12239 // expensive.....
12240
12241 double angle = GetVP().skew - GetVP().rotation;
12242 wxImage ri;
12243 bool b_rot_ok = false;
12244 if (base_image.IsOk()) {
12245 ViewPort rot_vp = GetVP();
12246
12247 m_b_rot_hidef = false;
12248
12249 ri = Image_Rotate(
12250 base_image, angle,
12251 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12252 m_b_rot_hidef, &m_roffset);
12253
12254 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12255 (rot_vp.rotation == VPoint.rotation) &&
12256 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12257 rot_vp.IsValid() && (ri.IsOk())) {
12258 b_rot_ok = true;
12259 }
12260 }
12261
12262 if (b_rot_ok) {
12263 delete m_prot_bm;
12264 m_prot_bm = new wxBitmap(ri);
12265 }
12266
12267 m_roffset.x += VPoint.rv_rect.x;
12268 m_roffset.y += VPoint.rv_rect.y;
12269 }
12270
12271 if (m_prot_bm && m_prot_bm->IsOk()) {
12272 rotd_dc.SelectObject(*m_prot_bm);
12273 pChartDC = &rotd_dc;
12274 } else {
12275 pChartDC = &temp_dc;
12276 m_roffset = wxPoint(0, 0);
12277 }
12278 } else { // unrotated
12279 pChartDC = &temp_dc;
12280 m_roffset = wxPoint(0, 0);
12281 }
12282
12283 wxPoint offset = m_roffset;
12284
12285 // Save the PixelCache viewpoint for next time
12286 m_cache_vp = VPoint;
12287
12288 // Set up a scratch DC for overlay objects
12289 wxMemoryDC mscratch_dc;
12290 mscratch_dc.SelectObject(*pscratch_bm);
12291
12292 mscratch_dc.ResetBoundingBox();
12293 mscratch_dc.DestroyClippingRegion();
12294 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12295
12296 // Blit the externally invalidated areas of the chart onto the scratch dc
12297 wxRegionIterator upd(rgn_blit); // get the update rect list
12298 while (upd) {
12299 wxRect rect = upd.GetRect();
12300
12301 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12302 rect.x - offset.x, rect.y - offset.y);
12303 upd++;
12304 }
12305
12306 // If multi-canvas, indicate which canvas has keyboard focus
12307 // by drawing a simple blue bar at the top.
12308 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12309 if (this == wxWindow::FindFocus()) {
12310 g_focusCanvas = this;
12311
12312 wxColour colour = GetGlobalColor("BLUE4");
12313 mscratch_dc.SetPen(wxPen(colour));
12314 mscratch_dc.SetBrush(wxBrush(colour));
12315
12316 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12317 mscratch_dc.DrawRectangle(activeRect);
12318 }
12319 }
12320
12321 // Any MBtiles?
12322 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12323 unsigned int im = stackIndexArray.size();
12324 if (VPoint.b_quilt && im > 0) {
12325 std::vector<int> tiles_to_show;
12326 for (unsigned int is = 0; is < im; is++) {
12327 const ChartTableEntry &cte =
12328 ChartData->GetChartTableEntry(stackIndexArray[is]);
12329 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12330 continue;
12331 }
12332 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12333 tiles_to_show.push_back(stackIndexArray[is]);
12334 }
12335 }
12336
12337 if (tiles_to_show.size())
12338 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12339 }
12340
12341 // May get an unexpected OnPaint call while switching display modes
12342 // Guard for that.
12343 if (!g_bopengl) {
12344 ocpnDC scratch_dc(mscratch_dc);
12345 RenderAlertMessage(mscratch_dc, GetVP());
12346 }
12347
12348#if 0
12349 // quiting?
12350 if (g_bquiting) {
12351#ifdef ocpnUSE_DIBSECTION
12352 ocpnMemDC q_dc;
12353#else
12354 wxMemoryDC q_dc;
12355#endif
12356 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12357 q_dc.SelectObject(qbm);
12358
12359 // Get a copy of the screen
12360 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12361
12362 // Draw a rectangle over the screen with a stipple brush
12363 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12364 q_dc.SetBrush(qbr);
12365 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12366
12367 // Blit back into source
12368 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12369 wxCOPY);
12370
12371 q_dc.SelectObject(wxNullBitmap);
12372 }
12373#endif
12374
12375#if 0
12376 // It is possible that this two-step method may be reuired for some platforms.
12377 // So, retain in the code base to aid recovery if necessary
12378
12379 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12380 if( VPoint.b_quilt ) {
12381 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12382 ChartBase *chart = m_pQuilt->GetRefChart();
12383 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12384
12385 // Clear the text Global declutter list
12386 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12387 if(ChPI)
12388 ChPI->ClearPLIBTextList();
12389 else{
12390 if(ps52plib)
12391 ps52plib->ClearTextList();
12392 }
12393
12394 wxMemoryDC t_dc;
12395 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12396
12397 wxColor maskBackground = wxColour(1,0,0);
12398 t_dc.SelectObject( qbm );
12399 t_dc.SetBackground(wxBrush(maskBackground));
12400 t_dc.Clear();
12401
12402 // Copy the scratch DC into the new bitmap
12403 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12404
12405 // Render the text to the new bitmap
12406 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12407 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12408
12409 // Copy the new bitmap back to the scratch dc
12410 wxRegionIterator upd_final( ru );
12411 while( upd_final ) {
12412 wxRect rect = upd_final.GetRect();
12413 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12414 upd_final++;
12415 }
12416
12417 t_dc.SelectObject( wxNullBitmap );
12418 }
12419 }
12420 }
12421#endif
12422 // Direct rendering model...
12423 if (VPoint.b_quilt) {
12424 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12425 ChartBase *chart = m_pQuilt->GetRefChart();
12426 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12427 // Clear the text Global declutter list
12428 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12429 if (ChPI)
12430 ChPI->ClearPLIBTextList();
12431 else {
12432 if (ps52plib) ps52plib->ClearTextList();
12433 }
12434
12435 // Render the text directly to the scratch bitmap
12436 OCPNRegion chart_all_text_region(
12437 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12438
12439 if (g_bShowChartBar && m_Piano) {
12440 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12441 GetVP().pix_width, m_Piano->GetHeight());
12442
12443 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12444 if (!style->chartStatusWindowTransparent)
12445 chart_all_text_region.Subtract(chart_bar_rect);
12446 }
12447
12448 if (m_Compass && m_Compass->IsShown()) {
12449 wxRect compassRect = m_Compass->GetRect();
12450 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12451 chart_all_text_region.Subtract(compassRect);
12452 }
12453 }
12454
12455 mscratch_dc.DestroyClippingRegion();
12456
12457 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12458 chart_all_text_region);
12459 }
12460 }
12461 }
12462
12463 // Now that charts are fully rendered, apply the overlay objects as decals.
12464 ocpnDC scratch_dc(mscratch_dc);
12465 DrawOverlayObjects(scratch_dc, ru);
12466
12467 // And finally, blit the scratch dc onto the physical dc
12468 wxRegionIterator upd_final(rgn_blit);
12469 while (upd_final) {
12470 wxRect rect = upd_final.GetRect();
12471 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12472 rect.y);
12473 upd_final++;
12474 }
12475
12476 // Deselect the chart bitmap from the temp_dc, so that it will not be
12477 // destroyed in the temp_dc dtor
12478 temp_dc.SelectObject(wxNullBitmap);
12479 // And for the scratch bitmap
12480 mscratch_dc.SelectObject(wxNullBitmap);
12481
12482 dc.DestroyClippingRegion();
12483
12484 PaintCleanup();
12485}
12486
12487void ChartCanvas::PaintCleanup() {
12488 // Handle the current graphic window, if present
12489 if (m_inPinch) return;
12490
12491 if (pCwin) {
12492 pCwin->Show();
12493 if (m_bTCupdate) {
12494 pCwin->Refresh();
12495 pCwin->Update();
12496 }
12497 }
12498
12499 // And set flags for next time
12500 m_bTCupdate = false;
12501
12502 // Handle deferred WarpPointer
12503 if (warp_flag) {
12504 WarpPointer(warp_x, warp_y);
12505 warp_flag = false;
12506 }
12507
12508 // Start movement timers, this runs nearly immediately.
12509 // the reason we cannot simply call it directly is the
12510 // refresh events it emits may be blocked from this paint event
12511 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12512 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12513}
12514
12515#if 0
12516wxColour GetErrorGraphicColor(double val)
12517{
12518 /*
12519 double valm = wxMin(val_max, val);
12520
12521 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12522 unsigned char red = (unsigned char)(255 * (valm/val_max));
12523
12524 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12525
12526 hv.saturation = 1.0;
12527 hv.value = 1.0;
12528
12529 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12530 return wxColour(rv.red, rv.green, rv.blue);
12531 */
12532
12533 // HTML colors taken from NOAA WW3 Web representation
12534 wxColour c;
12535 if((val > 0) && (val < 1)) c.Set("#002ad9");
12536 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12537 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12538 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12539 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12540 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12541 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12542 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12543 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12544 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12545 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12546 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12547 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12548 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12549 else if((val >= 30) && (val < 36)) c.Set("#870000");
12550 else if((val >= 36) && (val < 42)) c.Set("#690000");
12551 else if((val >= 42) && (val < 48)) c.Set("#550000");
12552 else if( val >= 48) c.Set("#410000");
12553
12554 return c;
12555}
12556
12557void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12558{
12559 wxImage gr_image(vp->pix_width, vp->pix_height);
12560 gr_image.InitAlpha();
12561
12562 double maxval = -10000;
12563 double minval = 10000;
12564
12565 double rlat, rlon;
12566 double glat, glon;
12567
12568 GetCanvasPixPoint(0, 0, rlat, rlon);
12569
12570 for(int i=1; i < vp->pix_height-1; i++)
12571 {
12572 for(int j=0; j < vp->pix_width; j++)
12573 {
12574 // Reference mercator value
12575// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12576
12577 // Georef value
12578 GetCanvasPixPoint(j, i, glat, glon);
12579
12580 maxval = wxMax(maxval, (glat - rlat));
12581 minval = wxMin(minval, (glat - rlat));
12582
12583 }
12584 rlat = glat;
12585 }
12586
12587 GetCanvasPixPoint(0, 0, rlat, rlon);
12588 for(int i=1; i < vp->pix_height-1; i++)
12589 {
12590 for(int j=0; j < vp->pix_width; j++)
12591 {
12592 // Reference mercator value
12593// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12594
12595 // Georef value
12596 GetCanvasPixPoint(j, i, glat, glon);
12597
12598 double f = ((glat - rlat)-minval)/(maxval - minval);
12599
12600 double dy = (f * 40);
12601
12602 wxColour c = GetErrorGraphicColor(dy);
12603 unsigned char r = c.Red();
12604 unsigned char g = c.Green();
12605 unsigned char b = c.Blue();
12606
12607 gr_image.SetRGB(j, i, r,g,b);
12608 if((glat - rlat )!= 0)
12609 gr_image.SetAlpha(j, i, 128);
12610 else
12611 gr_image.SetAlpha(j, i, 255);
12612
12613 }
12614 rlat = glat;
12615 }
12616
12617 // Create a Bitmap
12618 wxBitmap *pbm = new wxBitmap(gr_image);
12619 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12620 pbm->SetMask(gr_mask);
12621
12622 pmdc->DrawBitmap(*pbm, 0,0);
12623
12624 delete pbm;
12625
12626}
12627
12628#endif
12629
12630void ChartCanvas::CancelMouseRoute() {
12631 m_routeState = 0;
12632 m_pMouseRoute = NULL;
12633 m_bDrawingRoute = false;
12634}
12635
12636int ChartCanvas::GetNextContextMenuId() {
12637 return CanvasMenuHandler::GetNextContextMenuId();
12638}
12639
12640bool ChartCanvas::SetCursor(const wxCursor &c) {
12641#ifdef ocpnUSE_GL
12642 if (g_bopengl && m_glcc)
12643 return m_glcc->SetCursor(c);
12644 else
12645#endif
12646 return wxWindow::SetCursor(c);
12647}
12648
12649void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12650 if (g_bquiting) return;
12651 // Keep the mouse position members up to date
12652 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12653
12654 // Retrigger the route leg popup timer
12655 // This handles the case when the chart is moving in auto-follow mode,
12656 // but no user mouse input is made. The timer handler may Hide() the
12657 // popup if the chart moved enough n.b. We use slightly longer oneshot
12658 // value to allow this method's Refresh() to complete before potentially
12659 // getting another Refresh() in the popup timer handler.
12660 if (!m_RolloverPopupTimer.IsRunning() &&
12661 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12662 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12663 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12664 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12665
12666#ifdef ocpnUSE_GL
12667 if (m_glcc && g_bopengl) {
12668 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12669 // overlay objects.
12670 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12671
12672 m_glcc->Refresh(eraseBackground,
12673 NULL); // We always are going to render the entire screen
12674 // anyway, so make
12675 // sure that the window managers understand the invalid area
12676 // is actually the entire client area.
12677
12678 // We need to selectively Refresh some child windows, if they are visible.
12679 // Note that some children are refreshed elsewhere on timer ticks, so don't
12680 // need attention here.
12681
12682 // Thumbnail chart
12683 if (pthumbwin && pthumbwin->IsShown()) {
12684 pthumbwin->Raise();
12685 pthumbwin->Refresh(false);
12686 }
12687
12688 // ChartInfo window
12689 if (m_pCIWin && m_pCIWin->IsShown()) {
12690 m_pCIWin->Raise();
12691 m_pCIWin->Refresh(false);
12692 }
12693
12694 // if(g_MainToolbar)
12695 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12696
12697 } else
12698#endif
12699 wxWindow::Refresh(eraseBackground, rect);
12700}
12701
12702void ChartCanvas::Update() {
12703 if (m_glcc && g_bopengl) {
12704#ifdef ocpnUSE_GL
12705 m_glcc->Update();
12706#endif
12707 } else
12708 wxWindow::Update();
12709}
12710
12711void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12712 if (!pemboss) return;
12713 int x = pemboss->x, y = pemboss->y;
12714 const double factor = 200;
12715
12716 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12717 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12718 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12719
12720 // Grab a snipped image out of the chart
12721 wxMemoryDC snip_dc;
12722 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12723 snip_dc.SelectObject(snip_bmp);
12724
12725 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12726 snip_dc.SelectObject(wxNullBitmap);
12727
12728 wxImage snip_img = snip_bmp.ConvertToImage();
12729
12730 // Apply Emboss map to the snip image
12731 unsigned char *pdata = snip_img.GetData();
12732 if (pdata) {
12733 for (int y = 0; y < pemboss->height; y++) {
12734 int map_index = (y * pemboss->width);
12735 for (int x = 0; x < pemboss->width; x++) {
12736 double val = (pemboss->pmap[map_index] * factor) / 256.;
12737
12738 int nred = (int)((*pdata) + val);
12739 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12740 *pdata++ = (unsigned char)nred;
12741
12742 int ngreen = (int)((*pdata) + val);
12743 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12744 *pdata++ = (unsigned char)ngreen;
12745
12746 int nblue = (int)((*pdata) + val);
12747 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12748 *pdata++ = (unsigned char)nblue;
12749
12750 map_index++;
12751 }
12752 }
12753 }
12754
12755 // Convert embossed snip to a bitmap
12756 wxBitmap emb_bmp(snip_img);
12757
12758 // Map to another memoryDC
12759 wxMemoryDC result_dc;
12760 result_dc.SelectObject(emb_bmp);
12761
12762 // Blit to target
12763 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12764
12765 result_dc.SelectObject(wxNullBitmap);
12766}
12767
12768emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12769 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12770
12771 if (GetQuiltMode()) {
12772 // disable Overzoom indicator for MBTiles
12773 int refIndex = GetQuiltRefChartdbIndex();
12774 if (refIndex >= 0) {
12775 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12776 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12777 if (current_type == CHART_TYPE_MBTILES) {
12778 ChartBase *pChart = m_pQuilt->GetRefChart();
12779 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12780 if (ptc) {
12781 zoom_factor = ptc->GetZoomFactor();
12782 }
12783 }
12784 }
12785
12786 if (zoom_factor <= 3.9) return NULL;
12787 } else {
12788 if (m_singleChart) {
12789 if (zoom_factor <= 3.9) return NULL;
12790 } else
12791 return NULL;
12792 }
12793
12794 if (m_pEM_OverZoom) {
12795 m_pEM_OverZoom->x = 4;
12796 m_pEM_OverZoom->y = 0;
12797 if (g_MainToolbar && IsPrimaryCanvas()) {
12798 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12799 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12800 }
12801 }
12802 return m_pEM_OverZoom;
12803}
12804
12805void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12806 GridDraw(dc);
12807
12808 // bool pluginOverlayRender = true;
12809 //
12810 // if(g_canvasConfig > 0){ // Multi canvas
12811 // if(IsPrimaryCanvas())
12812 // pluginOverlayRender = false;
12813 // }
12814
12815 g_overlayCanvas = this;
12816
12817 if (g_pi_manager) {
12818 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12819 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12821 }
12822
12823 AISDrawAreaNotices(dc, GetVP(), this);
12824
12825 wxDC *pdc = dc.GetDC();
12826 if (pdc) {
12827 pdc->DestroyClippingRegion();
12828 wxDCClipper(*pdc, ru);
12829 }
12830
12831 if (m_bShowNavobjects) {
12832 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12833 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12834 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12835 DrawAnchorWatchPoints(dc);
12836 } else {
12837 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12838 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12839 }
12840
12841 AISDraw(dc, GetVP(), this);
12842 ShipDraw(dc);
12843 AlertDraw(dc);
12844
12845 RenderVisibleSectorLights(dc);
12846
12847 RenderAllChartOutlines(dc, GetVP());
12848 RenderRouteLegs(dc);
12849 RenderShipToActive(dc, false);
12850 ScaleBarDraw(dc);
12851 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12852 if (g_pi_manager) {
12853 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12855 }
12856
12857 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12858 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12859
12860 if (g_pi_manager) {
12861 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12863 }
12864
12865 if (m_bShowTide) {
12866 RebuildTideSelectList(GetVP().GetBBox());
12867 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12868 }
12869
12870 if (m_bShowCurrent) {
12871 RebuildCurrentSelectList(GetVP().GetBBox());
12872 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12873 }
12874
12875 if (!g_PrintingInProgress) {
12876 if (IsPrimaryCanvas()) {
12877 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12878 }
12879
12880 if (IsPrimaryCanvas()) {
12881 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12882 }
12883
12884 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12885
12886 if (m_pTrackRolloverWin) {
12887 m_pTrackRolloverWin->Draw(dc);
12888 m_brepaint_piano = true;
12889 }
12890
12891 if (m_pRouteRolloverWin) {
12892 m_pRouteRolloverWin->Draw(dc);
12893 m_brepaint_piano = true;
12894 }
12895
12896 if (m_pAISRolloverWin) {
12897 m_pAISRolloverWin->Draw(dc);
12898 m_brepaint_piano = true;
12899 }
12900 if (m_brepaint_piano && g_bShowChartBar) {
12901 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12902 }
12903
12904 if (m_Compass) m_Compass->Paint(dc);
12905
12906 if (!g_CanvasHideNotificationIcon) {
12907 if (IsPrimaryCanvas()) {
12908 auto &noteman = NotificationManager::GetInstance();
12909 if (noteman.GetNotificationCount()) {
12910 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12911 if (m_notification_button->UpdateStatus()) Refresh();
12912 m_notification_button->Show(true);
12913 m_notification_button->Paint(dc);
12914 } else {
12915 m_notification_button->Show(false);
12916 }
12917 }
12918 }
12919 }
12920 if (g_pi_manager) {
12921 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12923 }
12924}
12925
12926emboss_data *ChartCanvas::EmbossDepthScale() {
12927 if (!m_bShowDepthUnits) return NULL;
12928
12929 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12930
12931 if (GetQuiltMode()) {
12932 wxString s = m_pQuilt->GetQuiltDepthUnit();
12933 s.MakeUpper();
12934 if (s == "FEET")
12935 depth_unit_type = DEPTH_UNIT_FEET;
12936 else if (s.StartsWith("FATHOMS"))
12937 depth_unit_type = DEPTH_UNIT_FATHOMS;
12938 else if (s.StartsWith("METERS"))
12939 depth_unit_type = DEPTH_UNIT_METERS;
12940 else if (s.StartsWith("METRES"))
12941 depth_unit_type = DEPTH_UNIT_METERS;
12942 else if (s.StartsWith("METRIC"))
12943 depth_unit_type = DEPTH_UNIT_METERS;
12944 else if (s.StartsWith("METER"))
12945 depth_unit_type = DEPTH_UNIT_METERS;
12946
12947 } else {
12948 if (m_singleChart) {
12949 depth_unit_type = m_singleChart->GetDepthUnitType();
12950 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12951 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12952 }
12953 }
12954
12955 emboss_data *ped = NULL;
12956 switch (depth_unit_type) {
12957 case DEPTH_UNIT_FEET:
12958 ped = m_pEM_Feet;
12959 break;
12960 case DEPTH_UNIT_METERS:
12961 ped = m_pEM_Meters;
12962 break;
12963 case DEPTH_UNIT_FATHOMS:
12964 ped = m_pEM_Fathoms;
12965 break;
12966 default:
12967 return NULL;
12968 }
12969
12970 ped->x = (GetVP().pix_width - ped->width);
12971
12972 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12973 wxRect r = m_Compass->GetRect();
12974 ped->y = r.y + r.height;
12975 } else {
12976 ped->y = 40;
12977 }
12978 return ped;
12979}
12980
12981void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12982 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12983 wxFont font;
12984 if (style->embossFont == wxEmptyString) {
12985 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12986 font = *dFont;
12987 font.SetPointSize(60);
12988 font.SetWeight(wxFONTWEIGHT_BOLD);
12989 } else
12990 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12991 wxFONTWEIGHT_BOLD, false, style->embossFont);
12992
12993 int emboss_width = 500;
12994 int emboss_height = 200;
12995
12996 // Free any existing emboss maps
12997 delete m_pEM_Feet;
12998 delete m_pEM_Meters;
12999 delete m_pEM_Fathoms;
13000
13001 // Create the 3 DepthUnit emboss map structures
13002 m_pEM_Feet =
13003 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13004 m_pEM_Meters =
13005 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13006 m_pEM_Fathoms =
13007 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13008}
13009
13010#define OVERZOOM_TEXT _("OverZoom")
13011
13012void ChartCanvas::SetOverzoomFont() {
13013 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13014 int w, h;
13015
13016 wxFont font;
13017 if (style->embossFont == wxEmptyString) {
13018 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13019 font = *dFont;
13020 font.SetPointSize(40);
13021 font.SetWeight(wxFONTWEIGHT_BOLD);
13022 } else
13023 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13024 wxFONTWEIGHT_BOLD, false, style->embossFont);
13025
13026 wxClientDC dc(this);
13027 dc.SetFont(font);
13028 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13029
13030 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13031 font.SetPointSize(font.GetPointSize() - 1);
13032 dc.SetFont(font);
13033 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13034 }
13035 m_overzoomFont = font;
13036 m_overzoomTextWidth = w;
13037 m_overzoomTextHeight = h;
13038}
13039
13040void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13041 delete m_pEM_OverZoom;
13042
13043 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13044 m_pEM_OverZoom =
13045 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13046 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13047}
13048
13049emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13050 int height, const wxString &str,
13051 ColorScheme cs) {
13052 int *pmap;
13053
13054 // Create a temporary bitmap
13055 wxBitmap bmp(width, height, -1);
13056
13057 // Create a memory DC
13058 wxMemoryDC temp_dc;
13059 temp_dc.SelectObject(bmp);
13060
13061 // Paint on it
13062 temp_dc.SetBackground(*wxWHITE_BRUSH);
13063 temp_dc.SetTextBackground(*wxWHITE);
13064 temp_dc.SetTextForeground(*wxBLACK);
13065
13066 temp_dc.Clear();
13067
13068 temp_dc.SetFont(font);
13069
13070 int str_w, str_h;
13071 temp_dc.GetTextExtent(str, &str_w, &str_h);
13072 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13073 temp_dc.DrawText(str, 1, 1);
13074
13075 // Deselect the bitmap
13076 temp_dc.SelectObject(wxNullBitmap);
13077
13078 // Convert bitmap the wxImage for manipulation
13079 wxImage img = bmp.ConvertToImage();
13080
13081 int image_width = str_w * 105 / 100;
13082 int image_height = str_h * 105 / 100;
13083 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13084 wxMin(image_height, img.GetHeight()));
13085 wxImage imgs = img.GetSubImage(r);
13086
13087 double val_factor;
13088 switch (cs) {
13089 case GLOBAL_COLOR_SCHEME_DAY:
13090 default:
13091 val_factor = 1;
13092 break;
13093 case GLOBAL_COLOR_SCHEME_DUSK:
13094 val_factor = .5;
13095 break;
13096 case GLOBAL_COLOR_SCHEME_NIGHT:
13097 val_factor = .25;
13098 break;
13099 }
13100
13101 int val;
13102 int index;
13103 const int w = imgs.GetWidth();
13104 const int h = imgs.GetHeight();
13105 pmap = (int *)calloc(w * h * sizeof(int), 1);
13106 // Create emboss map by differentiating the emboss image
13107 // and storing integer results in pmap
13108 // n.b. since the image is B/W, it is sufficient to check
13109 // one channel (i.e. red) only
13110 for (int y = 1; y < h - 1; y++) {
13111 for (int x = 1; x < w - 1; x++) {
13112 val =
13113 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13114 val = (int)(val * val_factor);
13115 index = (y * w) + x;
13116 pmap[index] = val;
13117 }
13118 }
13119
13120 emboss_data *pret = new emboss_data;
13121 pret->pmap = pmap;
13122 pret->width = w;
13123 pret->height = h;
13124
13125 return pret;
13126}
13127
13128void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13129 Track *active_track = NULL;
13130 for (Track *pTrackDraw : g_TrackList) {
13131 if (g_pActiveTrack == pTrackDraw) {
13132 active_track = pTrackDraw;
13133 continue;
13134 }
13135
13136 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13137 }
13138
13139 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13140}
13141
13142void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13143 Track *active_track = NULL;
13144 for (Track *pTrackDraw : g_TrackList) {
13145 if (g_pActiveTrack == pTrackDraw) {
13146 active_track = pTrackDraw;
13147 break;
13148 }
13149 }
13150 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13151}
13152
13153void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13154 Route *active_route = NULL;
13155 for (Route *pRouteDraw : *pRouteList) {
13156 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13157 active_route = pRouteDraw;
13158 continue;
13159 }
13160
13161 // if(m_canvasIndex == 1)
13162 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13163 }
13164
13165 // Draw any active or selected route (or track) last, so that is is always on
13166 // top
13167 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13168}
13169
13170void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13171 Route *active_route = NULL;
13172
13173 for (Route *pRouteDraw : *pRouteList) {
13174 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13175 active_route = pRouteDraw;
13176 break;
13177 }
13178 }
13179 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13180}
13181
13182void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13183 if (!pWayPointMan) return;
13184
13185 auto node = pWayPointMan->GetWaypointList()->begin();
13186
13187 while (node != pWayPointMan->GetWaypointList()->end()) {
13188 RoutePoint *pWP = *node;
13189 if (pWP) {
13190 if (pWP->m_bIsInRoute) {
13191 ++node;
13192 continue;
13193 }
13194
13195 /* technically incorrect... waypoint has bounding box */
13196 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13197 RoutePointGui(*pWP).Draw(dc, this, NULL);
13198 else {
13199 // Are Range Rings enabled?
13200 if (pWP->GetShowWaypointRangeRings() &&
13201 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13202 double factor = 1.00;
13203 if (pWP->GetWaypointRangeRingsStepUnits() ==
13204 1) // convert kilometers to NMi
13205 factor = 1 / 1.852;
13206
13207 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13208 pWP->GetWaypointRangeRingsStep() / 60.;
13209 radius *= 2; // Fudge factor
13210
13211 LLBBox radar_box;
13212 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13213 pWP->m_lat + radius, pWP->m_lon + radius);
13214 if (!BltBBox.IntersectOut(radar_box)) {
13215 RoutePointGui(*pWP).Draw(dc, this, NULL);
13216 }
13217 }
13218 }
13219 }
13220
13221 ++node;
13222 }
13223}
13224
13225void ChartCanvas::DrawBlinkObjects() {
13226 // All RoutePoints
13227 wxRect update_rect;
13228
13229 if (!pWayPointMan) return;
13230
13231 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13232 if (pWP) {
13233 if (pWP->m_bBlink) {
13234 update_rect.Union(pWP->CurrentRect_in_DC);
13235 }
13236 }
13237 }
13238 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13239}
13240
13241void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13242 // draw anchor watch rings, if activated
13243
13245 wxPoint r1, r2;
13246 wxPoint lAnchorPoint1, lAnchorPoint2;
13247 double lpp1 = 0.0;
13248 double lpp2 = 0.0;
13249 if (pAnchorWatchPoint1) {
13250 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13252 &lAnchorPoint1);
13253 }
13254 if (pAnchorWatchPoint2) {
13255 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13257 &lAnchorPoint2);
13258 }
13259
13260 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13261 wxPen ppPenr(GetGlobalColor("URED"), 2);
13262
13263 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13264 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13265 dc.SetBrush(*ppBrush);
13266
13267 if (lpp1 > 0) {
13268 dc.SetPen(ppPeng);
13269 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13270 }
13271
13272 if (lpp2 > 0) {
13273 dc.SetPen(ppPeng);
13274 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13275 }
13276
13277 if (lpp1 < 0) {
13278 dc.SetPen(ppPenr);
13279 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13280 }
13281
13282 if (lpp2 < 0) {
13283 dc.SetPen(ppPenr);
13284 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13285 }
13286 }
13287}
13288
13289double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13290 double lpp = 0.;
13291 wxPoint r1;
13292 wxPoint lAnchorPoint;
13293 double d1 = 0.0;
13294 double dabs;
13295 double tlat1, tlon1;
13296
13297 if (pAnchorWatchPoint) {
13298 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13299 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13300 dabs = fabs(d1 / 1852.);
13301 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13302 &tlat1, &tlon1);
13303 GetCanvasPointPix(tlat1, tlon1, &r1);
13304 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13305 &lAnchorPoint);
13306 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13307 pow((double)(lAnchorPoint.y - r1.y), 2));
13308
13309 // This is an entry watch
13310 if (d1 < 0) lpp = -lpp;
13311 }
13312 return lpp;
13313}
13314
13315//------------------------------------------------------------------------------------------
13316// Tides Support
13317//------------------------------------------------------------------------------------------
13318void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13319 if (!ptcmgr) return;
13320
13321 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13322
13323 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13324 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13325 double lon = pIDX->IDX_lon;
13326 double lat = pIDX->IDX_lat;
13327
13328 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13329 if ((type == 't') || (type == 'T')) {
13330 if (BBox.Contains(lat, lon)) {
13331 // Manage the point selection list
13332 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13333 }
13334 }
13335 }
13336}
13337
13338void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13339 if (!ptcmgr) return;
13340
13341 wxDateTime this_now = gTimeSource;
13342 bool cur_time = !gTimeSource.IsValid();
13343 if (cur_time) this_now = wxDateTime::Now();
13344 time_t t_this_now = this_now.GetTicks();
13345
13346 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13347 wxPENSTYLE_SOLID);
13348 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13349 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13350 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13351 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13352
13353 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13354 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13355 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13356 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13357 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13358 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13359
13360 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13361 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13362 int font_size = wxMax(10, dFont->GetPointSize());
13363 font_size /= g_Platform->GetDisplayDIPMult(this);
13364 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13365 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13366 false, dFont->GetFaceName());
13367
13368 dc.SetPen(*pblack_pen);
13369 dc.SetBrush(*pgreen_brush);
13370
13371 wxBitmap bm;
13372 switch (m_cs) {
13373 case GLOBAL_COLOR_SCHEME_DAY:
13374 bm = m_bmTideDay;
13375 break;
13376 case GLOBAL_COLOR_SCHEME_DUSK:
13377 bm = m_bmTideDusk;
13378 break;
13379 case GLOBAL_COLOR_SCHEME_NIGHT:
13380 bm = m_bmTideNight;
13381 break;
13382 default:
13383 bm = m_bmTideDay;
13384 break;
13385 }
13386
13387 int bmw = bm.GetWidth();
13388 int bmh = bm.GetHeight();
13389
13390 float scale_factor = 1.0;
13391
13392 // Set the onscreen size of the symbol
13393 // Compensate for various display resolutions
13394 float icon_pixelRefDim = 45;
13395
13396 // Tidal report graphic is scaled by the text size of the label in use
13397 wxScreenDC sdc;
13398 int height;
13399 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13400 height *= g_Platform->GetDisplayDIPMult(this);
13401 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13402
13403 scale_factor *= pix_factor;
13404
13405 float user_scale_factor = g_ChartScaleFactorExp;
13406 if (g_ChartScaleFactorExp > 1.0)
13407 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13408 1.2; // soften the scale factor a bit
13409
13410 scale_factor *= user_scale_factor;
13411 scale_factor *= GetContentScaleFactor();
13412
13413 {
13414 double marge = 0.05;
13415 std::vector<LLBBox> drawn_boxes;
13416 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13417 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13418
13419 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13420 if ((type == 't') || (type == 'T')) // only Tides
13421 {
13422 double lon = pIDX->IDX_lon;
13423 double lat = pIDX->IDX_lat;
13424
13425 if (BBox.ContainsMarge(lat, lon, marge)) {
13426 // Avoid drawing detailed graphic for duplicate tide stations
13427 if (GetVP().chart_scale < 500000) {
13428 bool bdrawn = false;
13429 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13430 if (drawn_boxes[i].Contains(lat, lon)) {
13431 bdrawn = true;
13432 break;
13433 }
13434 }
13435 if (bdrawn) continue; // the station loop
13436
13437 LLBBox this_box;
13438 this_box.Set(lat, lon, lat, lon);
13439 this_box.EnLarge(.005);
13440 drawn_boxes.push_back(this_box);
13441 }
13442
13443 wxPoint r;
13444 GetCanvasPointPix(lat, lon, &r);
13445 // draw standard icons
13446 if (GetVP().chart_scale > 500000) {
13447 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13448 }
13449 // draw "extended" icons
13450 else {
13451 dc.SetFont(*plabelFont);
13452 {
13453 {
13454 float val, nowlev;
13455 float ltleve = 0.;
13456 float htleve = 0.;
13457 time_t tctime;
13458 time_t lttime = 0;
13459 time_t httime = 0;
13460 bool wt;
13461 // define if flood or ebb in the last ten minutes and verify if
13462 // data are useable
13463 if (ptcmgr->GetTideFlowSens(
13464 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13465 pIDX->IDX_rec_num, nowlev, val, wt)) {
13466 // search forward the first HW or LW near "now" ( starting at
13467 // "now" - ten minutes )
13468 ptcmgr->GetHightOrLowTide(
13469 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13470 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13471 wt, pIDX->IDX_rec_num, val, tctime);
13472 if (wt) {
13473 httime = tctime;
13474 htleve = val;
13475 } else {
13476 lttime = tctime;
13477 ltleve = val;
13478 }
13479 wt = !wt;
13480
13481 // then search opposite tide near "now"
13482 if (tctime > t_this_now) // search backward
13483 ptcmgr->GetHightOrLowTide(
13484 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13485 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13486 pIDX->IDX_rec_num, val, tctime);
13487 else
13488 // or search forward
13489 ptcmgr->GetHightOrLowTide(
13490 t_this_now, FORWARD_TEN_MINUTES_STEP,
13491 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13492 val, tctime);
13493 if (wt) {
13494 httime = tctime;
13495 htleve = val;
13496 } else {
13497 lttime = tctime;
13498 ltleve = val;
13499 }
13500
13501 // draw the tide rectangle:
13502
13503 // tide icon rectangle has default pre-scaled width = 12 ,
13504 // height = 45
13505 int width = (int)(12 * scale_factor + 0.5);
13506 int height = (int)(45 * scale_factor + 0.5);
13507 int linew = wxMax(1, (int)(scale_factor));
13508 int xDraw = r.x - (width / 2);
13509 int yDraw = r.y - (height / 2);
13510
13511 // process tide state ( %height and flow sens )
13512 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13513 int hs = (httime > lttime) ? -4 : 4;
13514 hs *= (int)(scale_factor + 0.5);
13515 if (ts > 0.995 || ts < 0.005) hs = 0;
13516 int ht_y = (int)(height * ts);
13517
13518 // draw yellow tide rectangle outlined in black
13519 pblack_pen->SetWidth(linew);
13520 dc.SetPen(*pblack_pen);
13521 dc.SetBrush(*pyelo_brush);
13522 dc.DrawRectangle(xDraw, yDraw, width, height);
13523
13524 // draw blue rectangle as water height, smaller in width than
13525 // yellow rectangle
13526 dc.SetPen(*pblue_pen);
13527 dc.SetBrush(*pblue_brush);
13528 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13529 (width - (4 * linew)), height - ht_y);
13530
13531 // draw sens arrows (ensure they are not "under-drawn" by top
13532 // line of blue rectangle )
13533 int hl;
13534 wxPoint arrow[3];
13535 arrow[0].x = xDraw + 2 * linew;
13536 arrow[1].x = xDraw + width / 2;
13537 arrow[2].x = xDraw + width - 2 * linew;
13538 pyelo_pen->SetWidth(linew);
13539 pblue_pen->SetWidth(linew);
13540 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13541 {
13542 hl = (int)(height * 0.25) + yDraw;
13543 arrow[0].y = hl;
13544 arrow[1].y = hl + hs;
13545 arrow[2].y = hl;
13546 if (ts < 0.15)
13547 dc.SetPen(*pyelo_pen);
13548 else
13549 dc.SetPen(*pblue_pen);
13550 dc.DrawLines(3, arrow);
13551 }
13552 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13553 {
13554 hl = (int)(height * 0.5) + yDraw;
13555 arrow[0].y = hl;
13556 arrow[1].y = hl + hs;
13557 arrow[2].y = hl;
13558 if (ts < 0.40)
13559 dc.SetPen(*pyelo_pen);
13560 else
13561 dc.SetPen(*pblue_pen);
13562 dc.DrawLines(3, arrow);
13563 }
13564 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13565 {
13566 hl = (int)(height * 0.75) + yDraw;
13567 arrow[0].y = hl;
13568 arrow[1].y = hl + hs;
13569 arrow[2].y = hl;
13570 if (ts < 0.65)
13571 dc.SetPen(*pyelo_pen);
13572 else
13573 dc.SetPen(*pblue_pen);
13574 dc.DrawLines(3, arrow);
13575 }
13576 // draw tide level text
13577 wxString s;
13578 s.Printf("%3.1f", nowlev);
13579 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13580 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13581 int wx1;
13582 dc.GetTextExtent(s, &wx1, NULL);
13583 wx1 *= g_Platform->GetDisplayDIPMult(this);
13584 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13585 }
13586 }
13587 }
13588 }
13589 }
13590 }
13591 }
13592 }
13593}
13594
13595//------------------------------------------------------------------------------------------
13596// Currents Support
13597//------------------------------------------------------------------------------------------
13598
13599void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13600 if (!ptcmgr) return;
13601
13602 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13603
13604 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13605 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13606 double lon = pIDX->IDX_lon;
13607 double lat = pIDX->IDX_lat;
13608
13609 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13610 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13611 if ((BBox.Contains(lat, lon))) {
13612 // Manage the point selection list
13613 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13614 }
13615 }
13616 }
13617}
13618
13619void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13620 if (!ptcmgr) return;
13621
13622 float tcvalue, dir;
13623 bool bnew_val;
13624 char sbuf[20];
13625 wxFont *pTCFont;
13626 double lon_last = 0.;
13627 double lat_last = 0.;
13628 // arrow size for Raz Blanchard : 12 knots north
13629 double marge = 0.2;
13630 bool cur_time = !gTimeSource.IsValid();
13631
13632 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13633 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13634
13635 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13636 wxPENSTYLE_SOLID);
13637 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13638 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13639 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13640 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13641 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13642 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13643 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13644 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13645
13646 double skew_angle = GetVPRotation();
13647
13648 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13649 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13650 int font_size = wxMax(10, dFont->GetPointSize());
13651 font_size /= g_Platform->GetDisplayDIPMult(this);
13652 pTCFont = FontMgr::Get().FindOrCreateFont(
13653 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13654 false, dFont->GetFaceName());
13655
13656 float scale_factor = 1.0;
13657
13658 // Set the onscreen size of the symbol
13659 // Current report graphic is scaled by the text size of the label in use
13660 wxScreenDC sdc;
13661 int height;
13662 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13663 height *= g_Platform->GetDisplayDIPMult(this);
13664 float nominal_icon_size_pixels = 15;
13665 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13666
13667 scale_factor *= pix_factor;
13668
13669 float user_scale_factor = g_ChartScaleFactorExp;
13670 if (g_ChartScaleFactorExp > 1.0)
13671 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13672 1.2; // soften the scale factor a bit
13673
13674 scale_factor *= user_scale_factor;
13675
13676 scale_factor *= GetContentScaleFactor();
13677
13678 {
13679 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13680 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13681 double lon = pIDX->IDX_lon;
13682 double lat = pIDX->IDX_lat;
13683
13684 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13685 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13686 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13687 wxPoint r;
13688 GetCanvasPointPix(lat, lon, &r);
13689
13690 wxPoint d[4]; // points of a diamond at the current station location
13691 int dd = (int)(5.0 * scale_factor + 0.5);
13692 d[0].x = r.x;
13693 d[0].y = r.y + dd;
13694 d[1].x = r.x + dd;
13695 d[1].y = r.y;
13696 d[2].x = r.x;
13697 d[2].y = r.y - dd;
13698 d[3].x = r.x - dd;
13699 d[3].y = r.y;
13700
13701 if (1) {
13702 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13703 dc.SetPen(*pblack_pen);
13704 dc.SetBrush(*porange_brush);
13705 dc.DrawPolygon(4, d);
13706
13707 if (type == 'C') {
13708 dc.SetBrush(*pblack_brush);
13709 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13710 }
13711
13712 if (GetVP().chart_scale < 1000000) {
13713 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13714 continue;
13715 } else
13716 continue;
13717
13718 if (1 /*type == 'c'*/) {
13719 {
13720 // Get the display pixel location of the current station
13721 int pixxc, pixyc;
13722 pixxc = r.x;
13723 pixyc = r.y;
13724
13725 // Adjust drawing size using logarithmic scale. tcvalue is
13726 // current in knots
13727 double a1 = fabs(tcvalue) * 10.;
13728 // Current values <= 0.1 knot will have no arrow
13729 a1 = wxMax(1.0, a1);
13730 double a2 = log10(a1);
13731
13732 float cscale = scale_factor * a2 * 0.3;
13733
13734 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13735 dc.SetPen(*porange_pen);
13736 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13737 cscale);
13738 // Draw text, if enabled
13739
13740 if (bDrawCurrentValues) {
13741 dc.SetFont(*pTCFont);
13742 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13743 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13744 }
13745 }
13746 } // scale
13747 }
13748 /* This is useful for debugging the TC database
13749 else
13750 {
13751 dc.SetPen ( *porange_pen );
13752 dc.SetBrush ( *pgray_brush );
13753 dc.DrawPolygon ( 4, d );
13754 }
13755 */
13756 }
13757 lon_last = lon;
13758 lat_last = lat;
13759 }
13760 }
13761 }
13762}
13763
13764void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13765 ShowSingleTideDialog(x, y, pvIDX);
13766}
13767
13768void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13769 if (!pvIDX) return; // Validate input
13770
13771 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13772
13773 // Check if a tide dialog is already open and visible
13774 if (pCwin && pCwin->IsShown()) {
13775 // Same tide station: bring existing dialog to front (preserves user
13776 // context)
13777 if (pCwin->GetCurrentIDX() == pNewIDX) {
13778 pCwin->Raise();
13779 pCwin->SetFocus();
13780
13781 // Provide subtle visual feedback that dialog is already open
13782 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13783 return;
13784 }
13785
13786 // Different tide station: close current dialog before opening new one
13787 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13788 }
13789
13790 if (pCwin) {
13791 // This shouldn't happen but ensures clean state
13792 pCwin->Destroy();
13793 pCwin = NULL;
13794 }
13795
13796 // Create and display new tide dialog
13797 pCwin = new TCWin(this, x, y, pvIDX);
13798
13799 // Ensure the dialog is properly shown and focused
13800 if (pCwin) {
13801 pCwin->Show();
13802 pCwin->Raise();
13803 pCwin->SetFocus();
13804 }
13805}
13806
13807bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13808
13810 if (pCwin) {
13811 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13812 }
13813}
13814
13815#define NUM_CURRENT_ARROW_POINTS 9
13816static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13817 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13818 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13819 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13820
13821void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13822 double scale) {
13823 if (scale > 1e-2) {
13824 float sin_rot = sin(rot_angle * PI / 180.);
13825 float cos_rot = cos(rot_angle * PI / 180.);
13826
13827 // Move to the first point
13828
13829 float xt = CurrentArrowArray[0].x;
13830 float yt = CurrentArrowArray[0].y;
13831
13832 float xp = (xt * cos_rot) - (yt * sin_rot);
13833 float yp = (xt * sin_rot) + (yt * cos_rot);
13834 int x1 = (int)(xp * scale);
13835 int y1 = (int)(yp * scale);
13836
13837 // Walk thru the point list
13838 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13839 xt = CurrentArrowArray[ip].x;
13840 yt = CurrentArrowArray[ip].y;
13841
13842 float xp = (xt * cos_rot) - (yt * sin_rot);
13843 float yp = (xt * sin_rot) + (yt * cos_rot);
13844 int x2 = (int)(xp * scale);
13845 int y2 = (int)(yp * scale);
13846
13847 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13848
13849 x1 = x2;
13850 y1 = y2;
13851 }
13852 }
13853}
13854
13855wxString ChartCanvas::FindValidUploadPort() {
13856 wxString port;
13857 // Try to use the saved persistent upload port first
13858 if (!g_uploadConnection.IsEmpty() &&
13859 g_uploadConnection.StartsWith("Serial")) {
13860 port = g_uploadConnection;
13861 }
13862
13863 else {
13864 // If there is no persistent upload port recorded (yet)
13865 // then use the first available serial connection which has output defined.
13866 for (auto *cp : TheConnectionParams()) {
13867 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13868 port << "Serial:" << cp->Port;
13869 }
13870 }
13871 return port;
13872}
13873
13874void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13875 if (!win) return;
13876
13877 if (NULL == g_pais_query_dialog_active) {
13878 int pos_x = g_ais_query_dialog_x;
13879 int pos_y = g_ais_query_dialog_y;
13880
13881 if (g_pais_query_dialog_active) {
13882 g_pais_query_dialog_active->Destroy();
13883 g_pais_query_dialog_active = new AISTargetQueryDialog();
13884 } else {
13885 g_pais_query_dialog_active = new AISTargetQueryDialog();
13886 }
13887
13888 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13889 wxPoint(pos_x, pos_y));
13890
13891 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13892 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13893 g_pais_query_dialog_active->SetMMSI(mmsi);
13894 g_pais_query_dialog_active->UpdateText();
13895 wxSize sz = g_pais_query_dialog_active->GetSize();
13896
13897 bool b_reset_pos = false;
13898#ifdef __WXMSW__
13899 // Support MultiMonitor setups which an allow negative window positions.
13900 // If the requested window title bar does not intersect any installed
13901 // monitor, then default to simple primary monitor positioning.
13902 RECT frame_title_rect;
13903 frame_title_rect.left = pos_x;
13904 frame_title_rect.top = pos_y;
13905 frame_title_rect.right = pos_x + sz.x;
13906 frame_title_rect.bottom = pos_y + 30;
13907
13908 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13909 b_reset_pos = true;
13910#else
13911
13912 // Make sure drag bar (title bar) of window intersects wxClient Area of
13913 // screen, with a little slop...
13914 wxRect window_title_rect; // conservative estimate
13915 window_title_rect.x = pos_x;
13916 window_title_rect.y = pos_y;
13917 window_title_rect.width = sz.x;
13918 window_title_rect.height = 30;
13919
13920 wxRect ClientRect = wxGetClientDisplayRect();
13921 ClientRect.Deflate(
13922 60, 60); // Prevent the new window from being too close to the edge
13923 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13924
13925#endif
13926
13927 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13928
13929 } else {
13930 g_pais_query_dialog_active->SetMMSI(mmsi);
13931 g_pais_query_dialog_active->UpdateText();
13932 }
13933
13934 g_pais_query_dialog_active->Show();
13935}
13936
13937void ChartCanvas::ToggleCanvasQuiltMode() {
13938 bool cur_mode = GetQuiltMode();
13939
13940 if (!GetQuiltMode())
13941 SetQuiltMode(true);
13942 else if (GetQuiltMode()) {
13943 SetQuiltMode(false);
13944 g_sticky_chart = GetQuiltReferenceChartIndex();
13945 }
13946
13947 if (cur_mode != GetQuiltMode()) {
13948 SetupCanvasQuiltMode();
13949 DoCanvasUpdate();
13950 InvalidateGL();
13951 Refresh();
13952 }
13953 // TODO What to do about this?
13954 // g_bQuiltEnable = GetQuiltMode();
13955
13956 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13957 if (ps52plib) ps52plib->GenerateStateHash();
13958
13959 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13960 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13961}
13962
13963void ChartCanvas::DoCanvasStackDelta(int direction) {
13964 if (!GetQuiltMode()) {
13965 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13966 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13967 if ((current_stack_index + direction) < 0) return;
13968
13969 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13970 int new_dbIndex =
13971 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13972
13973 if (IsChartQuiltableRef(new_dbIndex)) {
13974 ToggleCanvasQuiltMode();
13975 SelectQuiltRefdbChart(new_dbIndex);
13976 m_bpersistent_quilt = false;
13977 }
13978 } else {
13979 SelectChartFromStack(current_stack_index + direction);
13980 }
13981 } else {
13982 std::vector<int> piano_chart_index_array =
13983 GetQuiltExtendedStackdbIndexArray();
13984 int refdb = GetQuiltRefChartdbIndex();
13985
13986 // Find the ref chart in the stack
13987 int current_index = -1;
13988 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13989 if (refdb == piano_chart_index_array[i]) {
13990 current_index = i;
13991 break;
13992 }
13993 }
13994 if (current_index == -1) return;
13995
13996 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13997 int target_family = ctet.GetChartFamily();
13998
13999 int new_index = -1;
14000 int check_index = current_index + direction;
14001 bool found = false;
14002 int check_dbIndex = -1;
14003 int new_dbIndex = -1;
14004
14005 // When quilted. switch within the same chart family
14006 while (!found &&
14007 (unsigned int)check_index < piano_chart_index_array.size() &&
14008 (check_index >= 0)) {
14009 check_dbIndex = piano_chart_index_array[check_index];
14010 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14011 if (target_family == cte.GetChartFamily()) {
14012 found = true;
14013 new_index = check_index;
14014 new_dbIndex = check_dbIndex;
14015 break;
14016 }
14017
14018 check_index += direction;
14019 }
14020
14021 if (!found) return;
14022
14023 if (!IsChartQuiltableRef(new_dbIndex)) {
14024 ToggleCanvasQuiltMode();
14025 SelectdbChart(new_dbIndex);
14026 m_bpersistent_quilt = true;
14027 } else {
14028 SelectQuiltRefChart(new_index);
14029 }
14030 }
14031
14032 // update the state of the menu items (checkmarks etc)
14033 top_frame::Get()->UpdateGlobalMenuItems();
14034 SetQuiltChartHiLiteIndex(-1);
14035
14036 ReloadVP();
14037}
14038
14039//--------------------------------------------------------------------------------------------------------
14040//
14041// Toolbar support
14042//
14043//--------------------------------------------------------------------------------------------------------
14044
14045void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14046 // Handle the per-canvas toolbar clicks here
14047
14048 switch (event.GetId()) {
14049 case ID_ZOOMIN: {
14050 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14051 break;
14052 }
14053
14054 case ID_ZOOMOUT: {
14055 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14056 break;
14057 }
14058
14059 case ID_STKUP:
14060 DoCanvasStackDelta(1);
14061 DoCanvasUpdate();
14062 break;
14063
14064 case ID_STKDN:
14065 DoCanvasStackDelta(-1);
14066 DoCanvasUpdate();
14067 break;
14068
14069 case ID_FOLLOW: {
14070 TogglebFollow();
14071 break;
14072 }
14073
14074 case ID_CURRENT: {
14075 ShowCurrents(!GetbShowCurrent());
14076 ReloadVP();
14077 Refresh(false);
14078 break;
14079 }
14080
14081 case ID_TIDE: {
14082 ShowTides(!GetbShowTide());
14083 ReloadVP();
14084 Refresh(false);
14085 break;
14086 }
14087
14088 case ID_ROUTE: {
14089 if (0 == m_routeState) {
14090 StartRoute();
14091 } else {
14092 FinishRoute();
14093 }
14094
14095#ifdef __ANDROID__
14096 androidSetRouteAnnunciator(m_routeState == 1);
14097#endif
14098 break;
14099 }
14100
14101 case ID_AIS: {
14102 SetAISCanvasDisplayStyle(-1);
14103 break;
14104 }
14105
14106 default:
14107 break;
14108 }
14109
14110 // And then let gFrame handle the rest....
14111 event.Skip();
14112}
14113
14114void ChartCanvas::SetShowAIS(bool show) {
14115 m_bShowAIS = show;
14116 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14117 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14118}
14119
14120void ChartCanvas::SetAttenAIS(bool show) {
14121 m_bShowAISScaled = show;
14122 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14123 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14124}
14125
14126void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14127 // make some arrays to hold the dfferences between cycle steps
14128 // show all, scaled, hide all
14129 bool bShowAIS_Array[3] = {true, true, false};
14130 bool bShowScaled_Array[3] = {false, true, true};
14131 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14132 _("Attenuate less critical AIS targets"),
14133 _("Hide AIS Targets")};
14134 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14135 int ArraySize = 3;
14136 int AIS_Toolbar_Switch = 0;
14137 if (StyleIndx == -1) { // -1 means coming from toolbar button
14138 // find current state of switch
14139 for (int i = 1; i < ArraySize; i++) {
14140 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14141 (bShowScaled_Array[i] == m_bShowAISScaled))
14142 AIS_Toolbar_Switch = i;
14143 }
14144 AIS_Toolbar_Switch++; // we did click so continu with next item
14145 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14146 AIS_Toolbar_Switch++;
14147
14148 } else { // coming from menu bar.
14149 AIS_Toolbar_Switch = StyleIndx;
14150 }
14151 // make sure we are not above array
14152 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14153
14154 int AIS_Toolbar_Switch_Next =
14155 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14156 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14157 AIS_Toolbar_Switch_Next++;
14158 if (AIS_Toolbar_Switch_Next >= ArraySize)
14159 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14160
14161 // Set found values to global and member variables
14162 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14163 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14164}
14165
14166void ChartCanvas::TouchAISToolActive() {}
14167
14168void ChartCanvas::UpdateAISTBTool() {}
14169
14170//---------------------------------------------------------------------------------
14171//
14172// Compass/GPS status icon support
14173//
14174//---------------------------------------------------------------------------------
14175
14176void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14177 // Look for change in overlap or positions
14178 bool b_update = false;
14179 int cc1_edge_comp = 2;
14180 wxRect rect = m_Compass->GetRect();
14181 wxSize parent_size = GetSize();
14182
14183 parent_size *= m_displayScale;
14184
14185 // check to see if it would overlap if it was in its home position (upper
14186 // right)
14187 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14188 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14189 wxRect compass_rect(compass_pt, rect.GetSize());
14190
14191 m_Compass->Move(compass_pt);
14192
14193 if (m_Compass && m_Compass->IsShown())
14194 m_Compass->UpdateStatus(b_force_new | b_update);
14195
14196 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14197 scaler = wxMax(scaler, 1.0);
14198 wxPoint note_point = wxPoint(
14199 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14200 if (m_notification_button) {
14201 m_notification_button->Move(note_point);
14202 m_notification_button->UpdateStatus();
14203 }
14204
14205 if (b_force_new | b_update) Refresh();
14206}
14207
14208void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14209 ChartTypeEnum New_Type,
14210 ChartFamilyEnum New_Family) {
14211 if (!GetpCurrentStack()) return;
14212 if (!ChartData) return;
14213
14214 if (index < GetpCurrentStack()->nEntry) {
14215 // Open the new chart
14216 ChartBase *pTentative_Chart;
14217 pTentative_Chart = ChartData->OpenStackChartConditional(
14218 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14219
14220 if (pTentative_Chart) {
14221 if (m_singleChart) m_singleChart->Deactivate();
14222
14223 m_singleChart = pTentative_Chart;
14224 m_singleChart->Activate();
14225
14226 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14227 GetpCurrentStack(), m_singleChart->GetFullPath());
14228 }
14229
14230 // Setup the view
14231 double zLat, zLon;
14232 if (m_bFollow) {
14233 zLat = gLat;
14234 zLon = gLon;
14235 } else {
14236 zLat = m_vLat;
14237 zLon = m_vLon;
14238 }
14239
14240 double best_scale_ppm = GetBestVPScale(m_singleChart);
14241 double rotation = GetVPRotation();
14242 double oldskew = GetVPSkew();
14243 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14244
14245 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14246 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14247 if (fabs(newskew) > 0.0001) rotation = newskew;
14248 }
14249
14250 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14251
14252 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14253 }
14254
14255 // refresh Piano
14256 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14257 if (idx < 0) return;
14258
14259 std::vector<int> piano_active_chart_index_array;
14260 piano_active_chart_index_array.push_back(
14261 GetpCurrentStack()->GetCurrentEntrydbIndex());
14262 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14263}
14264
14265void ChartCanvas::SelectdbChart(int dbindex) {
14266 if (!GetpCurrentStack()) return;
14267 if (!ChartData) return;
14268
14269 if (dbindex >= 0) {
14270 // Open the new chart
14271 ChartBase *pTentative_Chart;
14272 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14273
14274 if (pTentative_Chart) {
14275 if (m_singleChart) m_singleChart->Deactivate();
14276
14277 m_singleChart = pTentative_Chart;
14278 m_singleChart->Activate();
14279
14280 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14281 GetpCurrentStack(), m_singleChart->GetFullPath());
14282 }
14283
14284 // Setup the view
14285 double zLat, zLon;
14286 if (m_bFollow) {
14287 zLat = gLat;
14288 zLon = gLon;
14289 } else {
14290 zLat = m_vLat;
14291 zLon = m_vLon;
14292 }
14293
14294 double best_scale_ppm = GetBestVPScale(m_singleChart);
14295
14296 if (m_singleChart)
14297 SetViewPoint(zLat, zLon, best_scale_ppm,
14298 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14299
14300 // SetChartUpdatePeriod( );
14301
14302 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14303 }
14304
14305 // TODO refresh_Piano();
14306}
14307
14308void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14309 double target_scale = GetVP().view_scale_ppm;
14310
14311 if (!GetQuiltMode()) {
14312 if (GetpCurrentStack()) {
14313 int stack_index = -1;
14314 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14315 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14316 if (check_dbIndex < 0) continue;
14317 const ChartTableEntry &cte =
14318 ChartData->GetChartTableEntry(check_dbIndex);
14319 if (type == cte.GetChartType()) {
14320 stack_index = i;
14321 break;
14322 } else if (family == cte.GetChartFamily()) {
14323 stack_index = i;
14324 break;
14325 }
14326 }
14327
14328 if (stack_index >= 0) {
14329 SelectChartFromStack(stack_index);
14330 }
14331 }
14332 } else {
14333 int sel_dbIndex = -1;
14334 std::vector<int> piano_chart_index_array =
14335 GetQuiltExtendedStackdbIndexArray();
14336 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14337 int check_dbIndex = piano_chart_index_array[i];
14338 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14339 if (type == cte.GetChartType()) {
14340 if (IsChartQuiltableRef(check_dbIndex)) {
14341 sel_dbIndex = check_dbIndex;
14342 break;
14343 }
14344 } else if (family == cte.GetChartFamily()) {
14345 if (IsChartQuiltableRef(check_dbIndex)) {
14346 sel_dbIndex = check_dbIndex;
14347 break;
14348 }
14349 }
14350 }
14351
14352 if (sel_dbIndex >= 0) {
14353 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14354 // Re-qualify the quilt reference chart selection
14355 AdjustQuiltRefChart();
14356 }
14357
14358 // Now reset the scale to the target...
14359 SetVPScale(target_scale);
14360 }
14361
14362 SetQuiltChartHiLiteIndex(-1);
14363
14364 ReloadVP();
14365}
14366
14367bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14368 return std::find(m_tile_yesshow_index_array.begin(),
14369 m_tile_yesshow_index_array.end(),
14370 index) != m_tile_yesshow_index_array.end();
14371}
14372
14373bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14374 return std::find(m_tile_noshow_index_array.begin(),
14375 m_tile_noshow_index_array.end(),
14376 index) != m_tile_noshow_index_array.end();
14377}
14378
14379void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14380 if (std::find(m_tile_noshow_index_array.begin(),
14381 m_tile_noshow_index_array.end(),
14382 index) == m_tile_noshow_index_array.end()) {
14383 m_tile_noshow_index_array.push_back(index);
14384 }
14385}
14386
14387//-------------------------------------------------------------------------------------------------------
14388//
14389// Piano support
14390//
14391//-------------------------------------------------------------------------------------------------------
14392
14393void ChartCanvas::HandlePianoClick(
14394 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14395 if (g_options && g_options->IsShown())
14396 return; // Piano might be invalid due to chartset updates.
14397 if (!m_pCurrentStack) return;
14398 if (!ChartData) return;
14399
14400 // stop movement or on slow computer we may get something like :
14401 // zoom out with the wheel (timer is set)
14402 // quickly click and display a chart, which may zoom in
14403 // but the delayed timer fires first and it zooms out again!
14404 StopMovement();
14405
14406 // When switching by piano key click, we may appoint the new target chart to
14407 // be any chart in the composite array.
14408 // As an improvement to UX, find the chart that is "closest" to the current
14409 // vp,
14410 // and select that chart. This will cause a jump to the centroid of that
14411 // chart
14412
14413 double distance = 25000; // RTW
14414 int closest_index = -1;
14415 for (int chart_index : selected_dbIndex_array) {
14416 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14417 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14418 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14419
14420 // measure distance as Manhattan style
14421 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14422 if (test_distance < distance) {
14423 distance = test_distance;
14424 closest_index = chart_index;
14425 }
14426 }
14427
14428 int selected_dbIndex = selected_dbIndex_array[0];
14429 if (closest_index >= 0) selected_dbIndex = closest_index;
14430
14431 if (!GetQuiltMode()) {
14432 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14433 if (IsChartQuiltableRef(selected_dbIndex)) {
14434 ToggleCanvasQuiltMode();
14435 SelectQuiltRefdbChart(selected_dbIndex);
14436 m_bpersistent_quilt = false;
14437 } else {
14438 SelectChartFromStack(selected_index);
14439 }
14440 } else {
14441 SelectChartFromStack(selected_index);
14442 g_sticky_chart = selected_dbIndex;
14443 }
14444
14445 if (m_singleChart)
14446 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14447 } else {
14448 // Handle MBTiles overlays first
14449 // Left click simply toggles the noshow array index entry
14450 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14451 bool bfound = false;
14452 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14453 if (m_tile_noshow_index_array[i] ==
14454 selected_dbIndex) { // chart is in the noshow list
14455 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14456 i); // erase it
14457 bfound = true;
14458 break;
14459 }
14460 }
14461 if (!bfound) {
14462 m_tile_noshow_index_array.push_back(selected_dbIndex);
14463 }
14464
14465 // If not already present, add this tileset to the "yes_show" array.
14466 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14467 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14468 }
14469
14470 else {
14471 if (IsChartQuiltableRef(selected_dbIndex)) {
14472 // if( ChartData ) ChartData->PurgeCache();
14473
14474 // If the chart is a vector chart, and of very large scale,
14475 // then we had better set the new scale directly to avoid excessive
14476 // underzoom on, eg, Inland ENCs
14477 bool set_scale = false;
14478 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14479 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14480 set_scale = true;
14481 }
14482 }
14483
14484 if (!set_scale) {
14485 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14486 } else {
14487 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14488
14489 // Adjust scale so that the selected chart is underzoomed/overzoomed
14490 // by a controlled amount
14491 ChartBase *pc =
14492 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14493 if (pc) {
14494 double proposed_scale_onscreen =
14496
14497 if (g_bPreserveScaleOnX) {
14498 proposed_scale_onscreen =
14499 wxMin(proposed_scale_onscreen,
14500 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14501 GetCanvasWidth()));
14502 } else {
14503 proposed_scale_onscreen =
14504 wxMin(proposed_scale_onscreen,
14505 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14506 GetCanvasWidth()));
14507
14508 proposed_scale_onscreen =
14509 wxMax(proposed_scale_onscreen,
14510 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14512 }
14513
14514 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14515 }
14516 }
14517 } else {
14518 ToggleCanvasQuiltMode();
14519 SelectdbChart(selected_dbIndex);
14520 m_bpersistent_quilt = true;
14521 }
14522 }
14523 }
14524
14525 SetQuiltChartHiLiteIndex(-1);
14526 // update the state of the menu items (checkmarks etc)
14527 top_frame::Get()->UpdateGlobalMenuItems();
14528 HideChartInfoWindow();
14529 DoCanvasUpdate();
14530 ReloadVP(); // Pick up the new selections
14531}
14532
14533void ChartCanvas::HandlePianoRClick(
14534 int x, int y, int selected_index,
14535 const std::vector<int> &selected_dbIndex_array) {
14536 if (g_options && g_options->IsShown())
14537 return; // Piano might be invalid due to chartset updates.
14538 if (!GetpCurrentStack()) return;
14539
14540 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14541 UpdateCanvasControlBar();
14542
14543 SetQuiltChartHiLiteIndex(-1);
14544}
14545
14546void ChartCanvas::HandlePianoRollover(
14547 int selected_index, const std::vector<int> &selected_dbIndex_array,
14548 int n_charts, int scale) {
14549 if (g_options && g_options->IsShown())
14550 return; // Piano might be invalid due to chartset updates.
14551 if (!GetpCurrentStack()) return;
14552 if (!ChartData) return;
14553
14554 if (ChartData->IsBusy()) return;
14555
14556 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14557
14558 if (!GetQuiltMode()) {
14559 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14560 } else {
14561 // Select the correct vector
14562 std::vector<int> piano_chart_index_array;
14563 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14564 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14565 if ((GetpCurrentStack()->nEntry > 1) ||
14566 (piano_chart_index_array.size() >= 1)) {
14567 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14568
14569 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14570 ReloadVP(false); // no VP adjustment allowed
14571 } else if (GetpCurrentStack()->nEntry == 1) {
14572 const ChartTableEntry &cte =
14573 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14574 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14575 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14576 ReloadVP(false);
14577 } else if ((-1 == selected_index) &&
14578 (0 == selected_dbIndex_array.size())) {
14579 ShowChartInfoWindow(key_location.x, -1);
14580 }
14581 }
14582 } else {
14583 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14584
14585 if ((GetpCurrentStack()->nEntry > 1) ||
14586 (piano_chart_index_array.size() >= 1)) {
14587 if (n_charts > 1)
14588 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14589 selected_dbIndex_array);
14590 else if (n_charts == 1)
14591 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14592
14593 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14594 ReloadVP(false); // no VP adjustment allowed
14595 }
14596 }
14597 }
14598}
14599
14600void ChartCanvas::ClearPianoRollover() {
14601 ClearQuiltChartHiLiteIndexArray();
14602 ShowChartInfoWindow(0, -1);
14603 std::vector<int> vec;
14604 ShowCompositeInfoWindow(0, 0, 0, vec);
14605 ReloadVP(false);
14606}
14607
14608void ChartCanvas::UpdateCanvasControlBar() {
14609 if (m_pianoFrozen) return;
14610
14611 if (!GetpCurrentStack()) return;
14612 if (!ChartData) return;
14613 if (!g_bShowChartBar) return;
14614
14615 int sel_type = -1;
14616 int sel_family = -1;
14617
14618 std::vector<int> piano_chart_index_array;
14619 std::vector<int> empty_piano_chart_index_array;
14620
14621 wxString old_hash = m_Piano->GetStoredHash();
14622
14623 if (GetQuiltMode()) {
14624 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14625 GetQuiltFullScreendbIndexArray());
14626
14627 std::vector<int> piano_active_chart_index_array =
14628 GetQuiltCandidatedbIndexArray();
14629 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14630
14631 std::vector<int> piano_eclipsed_chart_index_array =
14632 GetQuiltEclipsedStackdbIndexArray();
14633 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14634
14635 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14636 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14637
14638 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14639 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14640 } else {
14641 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14642 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14643 // TODO refresh_Piano();
14644
14645 if (m_singleChart) {
14646 sel_type = m_singleChart->GetChartType();
14647 sel_family = m_singleChart->GetChartFamily();
14648 }
14649 }
14650
14651 // Set up the TMerc and Skew arrays
14652 std::vector<int> piano_skew_chart_index_array;
14653 std::vector<int> piano_tmerc_chart_index_array;
14654 std::vector<int> piano_poly_chart_index_array;
14655
14656 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14657 const ChartTableEntry &ctei =
14658 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14659 double skew_norm = ctei.GetChartSkew();
14660 if (skew_norm > 180.) skew_norm -= 360.;
14661
14662 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14663 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14664
14665 // Polyconic skewed charts should show as skewed
14666 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14667 if (fabs(skew_norm) > 1.)
14668 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14669 else
14670 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14671 } else if (fabs(skew_norm) > 1.)
14672 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14673 }
14674 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14675 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14676 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14677
14678 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14679 if (new_hash != old_hash) {
14680 m_Piano->FormatKeys();
14681 HideChartInfoWindow();
14682 m_Piano->ResetRollover();
14683 SetQuiltChartHiLiteIndex(-1);
14684 m_brepaint_piano = true;
14685 }
14686
14687 // Create a bitmask int that describes what Family/Type of charts are shown in
14688 // the bar, and notify the platform.
14689 int mask = 0;
14690 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14691 const ChartTableEntry &ctei =
14692 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14693 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14694 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14695 if (e == CHART_FAMILY_RASTER) mask |= 1;
14696 if (e == CHART_FAMILY_VECTOR) {
14697 if (t == CHART_TYPE_CM93COMP)
14698 mask |= 4;
14699 else
14700 mask |= 2;
14701 }
14702 }
14703
14704 wxString s_indicated;
14705 if (sel_type == CHART_TYPE_CM93COMP)
14706 s_indicated = "cm93";
14707 else {
14708 if (sel_family == CHART_FAMILY_RASTER)
14709 s_indicated = "raster";
14710 else if (sel_family == CHART_FAMILY_VECTOR)
14711 s_indicated = "vector";
14712 }
14713
14714 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14715}
14716
14717void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14718
14719void ChartCanvas::PianoPopupMenu(
14720 int x, int y, int selected_index,
14721 const std::vector<int> &selected_dbIndex_array) {
14722 if (!GetpCurrentStack()) return;
14723
14724 // No context menu if quilting is disabled
14725 if (!GetQuiltMode()) return;
14726
14727 m_piano_ctx_menu = new wxMenu();
14728
14729 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14730 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14731 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14732 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14733 } else {
14734 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14735 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14736 // wxEVT_COMMAND_MENU_SELECTED,
14737 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14738
14739 menu_selected_dbIndex = selected_dbIndex_array[0];
14740 menu_selected_index = selected_index;
14741
14742 // Search the no-show array
14743 bool b_is_in_noshow = false;
14744 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14745 if (m_quilt_noshow_index_array[i] ==
14746 menu_selected_dbIndex) // chart is in the noshow list
14747 {
14748 b_is_in_noshow = true;
14749 break;
14750 }
14751 }
14752
14753 if (b_is_in_noshow) {
14754 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14755 _("Show This Chart"));
14756 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14757 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14758 } else if (GetpCurrentStack()->nEntry > 1) {
14759 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14760 _("Hide This Chart"));
14761 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14762 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14763 }
14764 }
14765
14766 wxPoint pos = wxPoint(x, y - 30);
14767
14768 // Invoke the drop-down menu
14769 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14770 PopupMenu(m_piano_ctx_menu, pos);
14771
14772 delete m_piano_ctx_menu;
14773 m_piano_ctx_menu = NULL;
14774
14775 HideChartInfoWindow();
14776 m_Piano->ResetRollover();
14777
14778 SetQuiltChartHiLiteIndex(-1);
14779 ClearQuiltChartHiLiteIndexArray();
14780
14781 ReloadVP();
14782}
14783
14784void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14785 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14786 if (m_quilt_noshow_index_array[i] ==
14787 menu_selected_dbIndex) // chart is in the noshow list
14788 {
14789 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14790 break;
14791 }
14792 }
14793}
14794
14795void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14796 if (!GetpCurrentStack()) return;
14797 if (!ChartData) return;
14798
14799 RemoveChartFromQuilt(menu_selected_dbIndex);
14800
14801 // It could happen that the chart being disabled is the reference
14802 // chart....
14803 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14804 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14805
14806 int i = menu_selected_index + 1; // select next smaller scale chart
14807 bool b_success = false;
14808 while (i < GetpCurrentStack()->nEntry - 1) {
14809 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14810 if (type == ChartData->GetDBChartType(dbIndex)) {
14811 SelectQuiltRefChart(i);
14812 b_success = true;
14813 break;
14814 }
14815 i++;
14816 }
14817
14818 // If that did not work, try to select the next larger scale compatible
14819 // chart
14820 if (!b_success) {
14821 i = menu_selected_index - 1;
14822 while (i > 0) {
14823 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14824 if (type == ChartData->GetDBChartType(dbIndex)) {
14825 SelectQuiltRefChart(i);
14826 b_success = true;
14827 break;
14828 }
14829 i--;
14830 }
14831 }
14832 }
14833}
14834
14835void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14836 // Remove the item from the list (if it appears) to avoid multiple addition
14837 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14838 if (m_quilt_noshow_index_array[i] ==
14839 dbIndex) // chart is already in the noshow list
14840 {
14841 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14842 break;
14843 }
14844 }
14845
14846 m_quilt_noshow_index_array.push_back(dbIndex);
14847}
14848
14849bool ChartCanvas::UpdateS52State() {
14850 bool retval = false;
14851
14852 if (ps52plib) {
14853 ps52plib->SetShowS57Text(m_encShowText);
14854 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14855 ps52plib->m_bShowSoundg = m_encShowDepth;
14856 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14857 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14858
14859 // Lights
14860 if (!m_encShowLights) // On, going off
14861 ps52plib->AddObjNoshow("LIGHTS");
14862 else // Off, going on
14863 ps52plib->RemoveObjNoshow("LIGHTS");
14864 ps52plib->SetLightsOff(!m_encShowLights);
14865 ps52plib->m_bExtendLightSectors = true;
14866
14867 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14868 ps52plib->SetAnchorOn(m_encShowAnchor);
14869 ps52plib->SetQualityOfData(m_encShowDataQual);
14870 }
14871
14872 return retval;
14873}
14874
14875void ChartCanvas::SetShowENCDataQual(bool show) {
14876 m_encShowDataQual = show;
14877 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14878 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14879
14880 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14881}
14882
14883void ChartCanvas::SetShowENCText(bool show) {
14884 m_encShowText = show;
14885 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14886 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14887
14888 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14889}
14890
14891void ChartCanvas::SetENCDisplayCategory(int category) {
14892 m_encDisplayCategory = category;
14893 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14894}
14895
14896void ChartCanvas::SetShowENCDepth(bool show) {
14897 m_encShowDepth = show;
14898 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14899 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14900
14901 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14902}
14903
14904void ChartCanvas::SetShowENCLightDesc(bool show) {
14905 m_encShowLightDesc = show;
14906 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14907 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14908
14909 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14910}
14911
14912void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14913 m_encShowBuoyLabels = show;
14914 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14915}
14916
14917void ChartCanvas::SetShowENCLights(bool show) {
14918 m_encShowLights = show;
14919 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14920 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14921
14922 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14923}
14924
14925void ChartCanvas::SetShowENCAnchor(bool show) {
14926 m_encShowAnchor = show;
14927 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14928 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14929
14930 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14931}
14932
14933wxRect ChartCanvas::GetMUIBarRect() {
14934 wxRect rv;
14935 if (m_muiBar) {
14936 rv = m_muiBar->GetRect();
14937 }
14938
14939 return rv;
14940}
14941
14942void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14943 if (!GetAlertString().IsEmpty()) {
14944 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14945 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14946
14947 dc.SetFont(*pfont);
14948 dc.SetPen(*wxTRANSPARENT_PEN);
14949
14950 dc.SetBrush(wxColour(243, 229, 47));
14951 int w, h;
14952 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14953 h += 2;
14954 // int yp = vp.pix_height - 20 - h;
14955
14956 wxRect sbr = GetScaleBarRect();
14957 int xp = sbr.x + sbr.width + 10;
14958 int yp = (sbr.y + sbr.height) - h;
14959
14960 int wdraw = w + 10;
14961 dc.DrawRectangle(xp, yp, wdraw, h);
14962 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14963 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14964 }
14965}
14966
14967//--------------------------------------------------------------------------------------------------------
14968// Screen Brightness Control Support Routines
14969//
14970//--------------------------------------------------------------------------------------------------------
14971
14972#ifdef __UNIX__
14973#define BRIGHT_XCALIB
14974#define __OPCPN_USEICC__
14975#endif
14976
14977#ifdef __OPCPN_USEICC__
14978int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14979 double co_green, double co_blue);
14980
14981wxString temp_file_name;
14982#endif
14983
14984#if 0
14985class ocpnCurtain: public wxDialog
14986{
14987 DECLARE_CLASS( ocpnCurtain )
14988 DECLARE_EVENT_TABLE()
14989
14990public:
14991 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14992 ~ocpnCurtain( );
14993 bool ProcessEvent(wxEvent& event);
14994
14995};
14996
14997IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14998
14999BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15000END_EVENT_TABLE()
15001
15002ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15003{
15004 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15005}
15006
15007ocpnCurtain::~ocpnCurtain()
15008{
15009}
15010
15011bool ocpnCurtain::ProcessEvent(wxEvent& event)
15012{
15013 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15014 return GetParent()->GetEventHandler()->ProcessEvent(event);
15015}
15016#endif
15017
15018#ifdef _WIN32
15019#include <windows.h>
15020
15021HMODULE hGDI32DLL;
15022typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15023typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15024SetDeviceGammaRamp_ptr_type
15025 g_pSetDeviceGammaRamp; // the API entry points in the dll
15026GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15027
15028WORD *g_pSavedGammaMap;
15029
15030#endif
15031
15032int InitScreenBrightness() {
15033#ifdef _WIN32
15034#ifdef ocpnUSE_GL
15035 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15036 HDC hDC;
15037 BOOL bbr;
15038
15039 if (NULL == hGDI32DLL) {
15040 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15041
15042 if (NULL != hGDI32DLL) {
15043 // Get the entry points of the required functions
15044 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15045 hGDI32DLL, "SetDeviceGammaRamp");
15046 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15047 hGDI32DLL, "GetDeviceGammaRamp");
15048
15049 // If the functions are not found, unload the DLL and return false
15050 if ((NULL == g_pSetDeviceGammaRamp) ||
15051 (NULL == g_pGetDeviceGammaRamp)) {
15052 FreeLibrary(hGDI32DLL);
15053 hGDI32DLL = NULL;
15054 return 0;
15055 }
15056 }
15057 }
15058
15059 // Interface is ready, so....
15060 // Get some storage
15061 if (!g_pSavedGammaMap) {
15062 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15063
15064 hDC = GetDC(NULL); // Get the full screen DC
15065 bbr = g_pGetDeviceGammaRamp(
15066 hDC, g_pSavedGammaMap); // Get the existing ramp table
15067 ReleaseDC(NULL, hDC); // Release the DC
15068 }
15069
15070 // On Windows hosts, try to adjust the registry to allow full range
15071 // setting of Gamma table This is an undocumented Windows hack.....
15072 wxRegKey *pRegKey = new wxRegKey(
15073 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15074 "NT\\CurrentVersion\\ICM");
15075 if (!pRegKey->Exists()) pRegKey->Create();
15076 pRegKey->SetValue("GdiIcmGammaRange", 256);
15077
15078 g_brightness_init = true;
15079 return 1;
15080 }
15081#endif
15082
15083 {
15084 if (NULL == g_pcurtain) {
15085 if (top_frame::Get()->CanSetTransparent()) {
15086 // Build the curtain window
15087 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15088 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15089 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15090 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15091
15092 // g_pcurtain = new ocpnCurtain(gFrame,
15093 // wxPoint(0,0),::wxGetDisplaySize(),
15094 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15095 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15096
15097 g_pcurtain->Hide();
15098
15099 HWND hWnd = GetHwndOf(g_pcurtain);
15100 SetWindowLong(hWnd, GWL_EXSTYLE,
15101 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15102 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15103 g_pcurtain->SetTransparent(0);
15104
15105 g_pcurtain->Maximize();
15106 g_pcurtain->Show();
15107
15108 // All of this is obtuse, but necessary for Windows...
15109 g_pcurtain->Enable();
15110 g_pcurtain->Disable();
15111
15112 top_frame::Get()->Disable();
15113 top_frame::Get()->Enable();
15114 // SetFocus();
15115 }
15116 }
15117 g_brightness_init = true;
15118
15119 return 1;
15120 }
15121#else
15122 // Look for "xcalib" application
15123 wxString cmd("xcalib -version");
15124
15125 wxArrayString output;
15126 long r = wxExecute(cmd, output);
15127 if (0 != r)
15128 wxLogMessage(
15129 " External application \"xcalib\" not found. Screen brightness "
15130 "not changed.");
15131
15132 g_brightness_init = true;
15133 return 0;
15134#endif
15135}
15136
15137int RestoreScreenBrightness() {
15138#ifdef _WIN32
15139
15140 if (g_pSavedGammaMap) {
15141 HDC hDC = GetDC(NULL); // Get the full screen DC
15142 g_pSetDeviceGammaRamp(hDC,
15143 g_pSavedGammaMap); // Restore the saved ramp table
15144 ReleaseDC(NULL, hDC); // Release the DC
15145
15146 free(g_pSavedGammaMap);
15147 g_pSavedGammaMap = NULL;
15148 }
15149
15150 if (g_pcurtain) {
15151 g_pcurtain->Close();
15152 g_pcurtain->Destroy();
15153 g_pcurtain = NULL;
15154 }
15155
15156 g_brightness_init = false;
15157 return 1;
15158
15159#endif
15160
15161#ifdef BRIGHT_XCALIB
15162 if (g_brightness_init) {
15163 wxString cmd;
15164 cmd = "xcalib -clear";
15165 wxExecute(cmd, wxEXEC_ASYNC);
15166 g_brightness_init = false;
15167 }
15168
15169 return 1;
15170#endif
15171
15172 return 0;
15173}
15174
15175// Set brightness. [0..100]
15176int SetScreenBrightness(int brightness) {
15177#ifdef _WIN32
15178
15179 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15180 // some (most modern?) versions of gdi32.dll Load the required library dll,
15181 // if not already in place
15182#ifdef ocpnUSE_GL
15183 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15184 if (g_pcurtain) {
15185 g_pcurtain->Close();
15186 g_pcurtain->Destroy();
15187 g_pcurtain = NULL;
15188 }
15189
15190 InitScreenBrightness();
15191
15192 if (NULL == hGDI32DLL) {
15193 // Unicode stuff.....
15194 wchar_t wdll_name[80];
15195 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15196 LPCWSTR cstr = wdll_name;
15197
15198 hGDI32DLL = LoadLibrary(cstr);
15199
15200 if (NULL != hGDI32DLL) {
15201 // Get the entry points of the required functions
15202 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15203 hGDI32DLL, "SetDeviceGammaRamp");
15204 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15205 hGDI32DLL, "GetDeviceGammaRamp");
15206
15207 // If the functions are not found, unload the DLL and return false
15208 if ((NULL == g_pSetDeviceGammaRamp) ||
15209 (NULL == g_pGetDeviceGammaRamp)) {
15210 FreeLibrary(hGDI32DLL);
15211 hGDI32DLL = NULL;
15212 return 0;
15213 }
15214 }
15215 }
15216
15217 HDC hDC = GetDC(NULL); // Get the full screen DC
15218
15219 /*
15220 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15221 if (cmcap != CM_GAMMA_RAMP)
15222 {
15223 wxLogMessage(" Video hardware does not support brightness control by
15224 gamma ramp adjustment."); return false;
15225 }
15226 */
15227
15228 int increment = brightness * 256 / 100;
15229
15230 // Build the Gamma Ramp table
15231 WORD GammaTable[3][256];
15232
15233 int table_val = 0;
15234 for (int i = 0; i < 256; i++) {
15235 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15236 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15237 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15238
15239 table_val += increment;
15240
15241 if (table_val > 65535) table_val = 65535;
15242 }
15243
15244 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15245 ReleaseDC(NULL, hDC); // Release the DC
15246
15247 return 1;
15248 }
15249#endif
15250
15251 {
15252 if (g_pSavedGammaMap) {
15253 HDC hDC = GetDC(NULL); // Get the full screen DC
15254 g_pSetDeviceGammaRamp(hDC,
15255 g_pSavedGammaMap); // Restore the saved ramp table
15256 ReleaseDC(NULL, hDC); // Release the DC
15257 }
15258
15259 if (brightness < 100) {
15260 if (NULL == g_pcurtain) InitScreenBrightness();
15261
15262 if (g_pcurtain) {
15263 int sbrite = wxMax(1, brightness);
15264 sbrite = wxMin(100, sbrite);
15265
15266 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15267 }
15268 } else {
15269 if (g_pcurtain) {
15270 g_pcurtain->Close();
15271 g_pcurtain->Destroy();
15272 g_pcurtain = NULL;
15273 }
15274 }
15275
15276 return 1;
15277 }
15278
15279#endif
15280
15281#ifdef BRIGHT_XCALIB
15282
15283 if (!g_brightness_init) {
15284 last_brightness = 100;
15285 g_brightness_init = true;
15286 temp_file_name = wxFileName::CreateTempFileName("");
15287 InitScreenBrightness();
15288 }
15289
15290#ifdef __OPCPN_USEICC__
15291 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15292 // desired, and then activate this temporary profile using xcalib <filename>
15293 if (!CreateSimpleICCProfileFile(
15294 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15295 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15296 wxString cmd("xcalib ");
15297 cmd += temp_file_name;
15298
15299 wxExecute(cmd, wxEXEC_ASYNC);
15300 }
15301
15302#else
15303 // Or, use "xcalib -co" to set overall contrast value
15304 // This is not as nice, since the -co parameter wants to be a fraction of
15305 // the current contrast, and values greater than 100 are not allowed. As a
15306 // result, increases of contrast must do a "-clear" step first, which
15307 // produces objectionable flashing.
15308 if (brightness > last_brightness) {
15309 wxString cmd;
15310 cmd = "xcalib -clear";
15311 wxExecute(cmd, wxEXEC_ASYNC);
15312
15313 ::wxMilliSleep(10);
15314
15315 int brite_adj = wxMax(1, brightness);
15316 cmd.Printf("xcalib -co %2d -a", brite_adj);
15317 wxExecute(cmd, wxEXEC_ASYNC);
15318 } else {
15319 int brite_adj = wxMax(1, brightness);
15320 int factor = (brite_adj * 100) / last_brightness;
15321 factor = wxMax(1, factor);
15322 wxString cmd;
15323 cmd.Printf("xcalib -co %2d -a", factor);
15324 wxExecute(cmd, wxEXEC_ASYNC);
15325 }
15326
15327#endif
15328
15329 last_brightness = brightness;
15330
15331#endif
15332
15333 return 0;
15334}
15335
15336#ifdef __OPCPN_USEICC__
15337
15338#define MLUT_TAG 0x6d4c5554L
15339#define VCGT_TAG 0x76636774L
15340
15341int GetIntEndian(unsigned char *s) {
15342 int ret;
15343 unsigned char *p;
15344 int i;
15345
15346 p = (unsigned char *)&ret;
15347
15348 if (1)
15349 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15350 else
15351 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15352
15353 return ret;
15354}
15355
15356unsigned short GetShortEndian(unsigned char *s) {
15357 unsigned short ret;
15358 unsigned char *p;
15359 int i;
15360
15361 p = (unsigned char *)&ret;
15362
15363 if (1)
15364 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15365 else
15366 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15367
15368 return ret;
15369}
15370
15371// Create a very simple Gamma correction file readable by xcalib
15372int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15373 double co_green, double co_blue) {
15374 FILE *fp;
15375
15376 if (file_name) {
15377 fp = fopen(file_name, "wb");
15378 if (!fp) return -1; /* file can not be created */
15379 } else
15380 return -1; /* filename char pointer not valid */
15381
15382 // Write header
15383 char header[128];
15384 for (int i = 0; i < 128; i++) header[i] = 0;
15385
15386 fwrite(header, 128, 1, fp);
15387
15388 // Num tags
15389 int numTags0 = 1;
15390 int numTags = GetIntEndian((unsigned char *)&numTags0);
15391 fwrite(&numTags, 1, 4, fp);
15392
15393 int tagName0 = VCGT_TAG;
15394 int tagName = GetIntEndian((unsigned char *)&tagName0);
15395 fwrite(&tagName, 1, 4, fp);
15396
15397 int tagOffset0 = 128 + 4 * sizeof(int);
15398 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15399 fwrite(&tagOffset, 1, 4, fp);
15400
15401 int tagSize0 = 1;
15402 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15403 fwrite(&tagSize, 1, 4, fp);
15404
15405 fwrite(&tagName, 1, 4, fp); // another copy of tag
15406
15407 fwrite(&tagName, 1, 4, fp); // dummy
15408
15409 // Table type
15410
15411 /* VideoCardGammaTable (The simplest type) */
15412 int gammatype0 = 0;
15413 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15414 fwrite(&gammatype, 1, 4, fp);
15415
15416 int numChannels0 = 3;
15417 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15418 fwrite(&numChannels, 1, 2, fp);
15419
15420 int numEntries0 = 256;
15421 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15422 fwrite(&numEntries, 1, 2, fp);
15423
15424 int entrySize0 = 1;
15425 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15426 fwrite(&entrySize, 1, 2, fp);
15427
15428 unsigned char ramp[256];
15429
15430 // Red ramp
15431 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15432 fwrite(ramp, 256, 1, fp);
15433
15434 // Green ramp
15435 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15436 fwrite(ramp, 256, 1, fp);
15437
15438 // Blue ramp
15439 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15440 fwrite(ramp, 256, 1, fp);
15441
15442 fclose(fp);
15443
15444 return 0;
15445}
15446#endif // __OPCPN_USEICC__
Select * pSelectAIS
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
AIS target definitions.
Class AISTargetQueryDialog.
std::unique_ptr< HostApi > GetHostApi()
HostApi factory,.
Definition api_121.cpp:833
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:61
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1315
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1314
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1315
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1314
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:173
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13809
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:4567
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11853
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4563
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3659
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13768
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:4513
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:805
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2359
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8017
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7826
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5095
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:896
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4644
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5375
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:183
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:789
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4588
bool IsTideDialogOpen() const
Definition chcanv.cpp:13807
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:4650
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13764
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4508
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5394
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10262
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:485
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
char IDX_station_name[MAXNAMELEN]
Name of the tidal or current station.
Definition idx_entry.h:62
int station_tz_offset
Offset in seconds to convert from harmonic data (epochs) to the station time zone.
Definition idx_entry.h:93
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:60
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:201
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:59
Data for a loaded plugin, including dl-loaded library.
int m_cap_flag
PlugIn Capabilities descriptor.
PluginLoader is a backend module without any direct GUI functionality.
const ArrayOfPlugIns * GetPlugInArray()
Return list of currently loaded plugins.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1782
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h:46
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:77
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:36
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Display utilities.
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:76
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Miscellaneous utilities, many of which string related.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:183
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:187
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.