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) > 0) { // 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 new route.
8751 m_pMouseRoute->AddPoint(pMousePoint);
8752 } else {
8753 if (m_pMouseRoute->m_NextLegGreatCircle) {
8754 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8755 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8756 &rhumbBearing, &rhumbDist);
8757 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8758 rlat, &gcDist, &gcBearing, NULL);
8759 double gcDistNM = gcDist / 1852.0;
8760
8761 // Empirically found expression to get reasonable route segments.
8762 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8763 pow(rhumbDist - gcDistNM - 1, 0.5);
8764
8765 wxString msg;
8766 msg << _("For this leg the Great Circle route is ")
8767 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8768 << _(" shorter than rhumbline.\n\n")
8769 << _("Would you like include the Great Circle routing points "
8770 "for this leg?");
8771
8772 m_FinishRouteOnKillFocus = false;
8773 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8774 // does not fully capture mouse
8775
8776 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8777 wxYES_NO | wxNO_DEFAULT);
8778
8779 m_disable_edge_pan = false;
8780 m_FinishRouteOnKillFocus = true;
8781
8782 if (answer == wxID_YES) {
8783 RoutePoint *gcPoint;
8784 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8785 wxRealPoint gcCoord;
8786
8787 for (int i = 1; i <= segmentCount; i++) {
8788 double fraction = (double)i * (1.0 / (double)segmentCount);
8789 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8790 gcDist * fraction, gcBearing,
8791 &gcCoord.x, &gcCoord.y, NULL);
8792
8793 if (i < segmentCount) {
8794 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8795 wxEmptyString);
8796 gcPoint->SetNameShown(false);
8797 // pConfig->AddNewWayPoint(gcPoint, -1);
8798 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8799
8800 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8801 gcPoint);
8802 } else {
8803 gcPoint = pMousePoint; // Last point, previously exsisting!
8804 }
8805
8806 m_pMouseRoute->AddPoint(gcPoint);
8807 pSelect->AddSelectableRouteSegment(
8808 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8809 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8810 prevGcPoint = gcPoint;
8811 }
8812
8813 undo->CancelUndoableAction(true);
8814
8815 } else {
8816 m_pMouseRoute->AddPoint(pMousePoint);
8817 pSelect->AddSelectableRouteSegment(
8818 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8819 pMousePoint, m_pMouseRoute);
8820 undo->AfterUndoableAction(m_pMouseRoute);
8821 }
8822 } else {
8823 // Ordinary rhumblinesegment.
8824 m_pMouseRoute->AddPoint(pMousePoint);
8825 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8826 rlon, m_prev_pMousePoint,
8827 pMousePoint, m_pMouseRoute);
8828 undo->AfterUndoableAction(m_pMouseRoute);
8829 }
8830 }
8831 }
8832 m_prev_rlat = rlat;
8833 m_prev_rlon = rlon;
8834 m_prev_pMousePoint = pMousePoint;
8835 if (m_pMouseRoute)
8836 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8837
8838 m_routeState++;
8839
8840 if (appending ||
8841 inserting) { // Appending a route or making a new route
8842 int connect = tail->GetIndexOf(pMousePoint);
8843 if (connect == 0) {
8844 inserting = false; // there is nothing to insert
8845 appending = true; // so append
8846 }
8847 int length = tail->GetnPoints();
8848
8849 int i;
8850 int start, stop;
8851 if (appending) {
8852 start = connect + 1;
8853 stop = length;
8854 } else { // inserting
8855 start = 1;
8856 stop = connect + 1;
8857 // Remove the first and only point of the new route
8858 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8859 }
8860 for (i = start; i <= stop; i++) {
8861 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8862 if (m_pMouseRoute)
8863 m_pMouseRoute->m_lastMousePointIndex =
8864 m_pMouseRoute->GetnPoints();
8865 m_routeState++;
8866 top_frame::Get()->RefreshAllCanvas();
8867 ret = true;
8868 }
8869 m_prev_rlat =
8870 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8871 m_prev_rlon =
8872 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8873 m_pMouseRoute->FinalizeForRendering();
8874 }
8875 top_frame::Get()->RefreshAllCanvas();
8876 ret = true;
8877 }
8878
8879 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8880 {
8881 SetCursor(*pCursorPencil);
8882
8883 if (!m_pMeasureRoute) {
8884 m_pMeasureRoute = new Route();
8885 pRouteList->push_back(m_pMeasureRoute);
8886 }
8887
8888 if (m_nMeasureState == 1) {
8889 r_rband.x = x;
8890 r_rband.y = y;
8891 }
8892
8893 RoutePoint *pMousePoint =
8894 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8895 wxEmptyString, wxEmptyString);
8896 pMousePoint->m_bShowName = false;
8897 pMousePoint->SetShowWaypointRangeRings(false);
8898
8899 m_pMeasureRoute->AddPoint(pMousePoint);
8900
8901 m_prev_rlat = m_cursor_lat;
8902 m_prev_rlon = m_cursor_lon;
8903 m_prev_pMousePoint = pMousePoint;
8904 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8905
8906 m_nMeasureState++;
8907 top_frame::Get()->RefreshAllCanvas();
8908 ret = true;
8909 }
8910
8911 else {
8912 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8913 }
8914 } // !g_btouch
8915 else { // g_btouch
8916 m_last_touch_down_pos = event.GetPosition();
8917
8918 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8919 // if near screen edge, pan with injection
8920 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8921 // return;
8922 // }
8923 }
8924 }
8925
8926 if (ret) return true;
8927 }
8928
8929 if (event.Dragging()) {
8930 // in touch screen mode ensure the finger/cursor is on the selected point's
8931 // radius to allow dragging
8932 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8933 if (g_btouch) {
8934 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8935 SelectItem *pFind = NULL;
8936 SelectableItemList SelList = pSelect->FindSelectionList(
8937 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8938 for (SelectItem *pFind : SelList) {
8939 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8940 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8941 }
8942 }
8943
8944 // Check for use of dragHandle
8945 if (m_pRoutePointEditTarget &&
8946 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8947 SelectItem *pFind = NULL;
8948 SelectableItemList SelList = pSelect->FindSelectionList(
8949 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8950 for (SelectItem *pFind : SelList) {
8951 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8952 if (m_pRoutePointEditTarget == frp) {
8953 m_bIsInRadius = true;
8954 break;
8955 }
8956 }
8957
8958 if (!m_dragoffsetSet) {
8959 RoutePointGui(*m_pRoutePointEditTarget)
8960 .PresetDragOffset(this, mouse_x, mouse_y);
8961 m_dragoffsetSet = true;
8962 }
8963 }
8964 }
8965
8966 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8967 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8968
8969 if (NULL == g_pMarkInfoDialog) {
8970 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8971 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8972 DraggingAllowed = false;
8973
8974 if (m_pRoutePointEditTarget &&
8975 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8976 DraggingAllowed = false;
8977
8978 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8979
8980 if (DraggingAllowed) {
8981 if (!undo->InUndoableAction()) {
8982 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8983 Undo_NeedsCopy, m_pFoundPoint);
8984 }
8985
8986 // Get the update rectangle for the union of the un-edited routes
8987 wxRect pre_rect;
8988
8989 if (!g_bopengl && m_pEditRouteArray) {
8990 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8991 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8992 // Need to validate route pointer
8993 // Route may be gone due to drgging close to ownship with
8994 // "Delete On Arrival" state set, as in the case of
8995 // navigating to an isolated waypoint on a temporary route
8996 if (g_pRouteMan->IsRouteValid(pr)) {
8997 wxRect route_rect;
8998 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8999 pre_rect.Union(route_rect);
9000 }
9001 }
9002 }
9003
9004 double new_cursor_lat = m_cursor_lat;
9005 double new_cursor_lon = m_cursor_lon;
9006
9007 if (CheckEdgePan(x, y, true, 5, 2))
9008 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9009
9010 // update the point itself
9011 if (g_btouch) {
9012 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9013 // new_cursor_lat, new_cursor_lon);
9014 RoutePointGui(*m_pRoutePointEditTarget)
9015 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9016 // update the Drag Handle entry in the pSelect list
9017 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9018 m_pRoutePointEditTarget,
9019 SELTYPE_DRAGHANDLE);
9020 m_pFoundPoint->m_slat =
9021 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9022 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9023 } else {
9024 m_pRoutePointEditTarget->m_lat =
9025 new_cursor_lat; // update the RoutePoint entry
9026 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9027 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9028 m_pFoundPoint->m_slat =
9029 new_cursor_lat; // update the SelectList entry
9030 m_pFoundPoint->m_slon = new_cursor_lon;
9031 }
9032
9033 // Update the MarkProperties Dialog, if currently shown
9034 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9035 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9036 g_pMarkInfoDialog->UpdateProperties(true);
9037 }
9038
9039 if (g_bopengl) {
9040 // InvalidateGL();
9041 Refresh(false);
9042 } else {
9043 // Get the update rectangle for the edited route
9044 wxRect post_rect;
9045
9046 if (m_pEditRouteArray) {
9047 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9048 ir++) {
9049 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9050 if (g_pRouteMan->IsRouteValid(pr)) {
9051 wxRect route_rect;
9052 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9053 post_rect.Union(route_rect);
9054 }
9055 }
9056 }
9057
9058 // Invalidate the union region
9059 pre_rect.Union(post_rect);
9060 RefreshRect(pre_rect, false);
9061 }
9062 top_frame::Get()->RefreshCanvasOther(this);
9063 m_bRoutePoinDragging = true;
9064 }
9065 ret = true;
9066 } // if Route Editing
9067
9068 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9069 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9070
9071 if (NULL == g_pMarkInfoDialog) {
9072 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9073 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9074 DraggingAllowed = false;
9075
9076 if (m_pRoutePointEditTarget &&
9077 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9078 DraggingAllowed = false;
9079
9080 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9081
9082 if (DraggingAllowed) {
9083 if (!undo->InUndoableAction()) {
9084 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9085 Undo_NeedsCopy, m_pFoundPoint);
9086 }
9087
9088 // The mark may be an anchorwatch
9089 double lpp1 = 0.;
9090 double lpp2 = 0.;
9091 double lppmax;
9092
9093 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9094 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9095 }
9096 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9097 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9098 }
9099 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9100
9101 // Get the update rectangle for the un-edited mark
9102 wxRect pre_rect;
9103 if (!g_bopengl) {
9104 RoutePointGui(*m_pRoutePointEditTarget)
9105 .CalculateDCRect(m_dc_route, this, &pre_rect);
9106 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9107 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9108 (int)(lppmax - (pre_rect.height / 2)));
9109 }
9110
9111 // update the point itself
9112 if (g_btouch) {
9113 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9114 // m_cursor_lat, m_cursor_lon);
9115 RoutePointGui(*m_pRoutePointEditTarget)
9116 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9117 // update the Drag Handle entry in the pSelect list
9118 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9119 m_pRoutePointEditTarget,
9120 SELTYPE_DRAGHANDLE);
9121 m_pFoundPoint->m_slat =
9122 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9123 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9124 } else {
9125 m_pRoutePointEditTarget->m_lat =
9126 m_cursor_lat; // update the RoutePoint entry
9127 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9128 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9129 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9130 m_pFoundPoint->m_slon = m_cursor_lon;
9131 }
9132
9133 // Update the MarkProperties Dialog, if currently shown
9134 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9135 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9136 g_pMarkInfoDialog->UpdateProperties(true);
9137 }
9138
9139 // Invalidate the union region
9140 if (g_bopengl) {
9141 if (!g_btouch) InvalidateGL();
9142 Refresh(false);
9143 } else {
9144 // Get the update rectangle for the edited mark
9145 wxRect post_rect;
9146 RoutePointGui(*m_pRoutePointEditTarget)
9147 .CalculateDCRect(m_dc_route, this, &post_rect);
9148 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9149 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9150 (int)(lppmax - (post_rect.height / 2)));
9151
9152 // Invalidate the union region
9153 pre_rect.Union(post_rect);
9154 RefreshRect(pre_rect, false);
9155 }
9156 top_frame::Get()->RefreshCanvasOther(this);
9157 m_bRoutePoinDragging = true;
9158 }
9159 ret = g_btouch ? m_bRoutePoinDragging : true;
9160 }
9161
9162 if (ret) return true;
9163 } // dragging
9164
9165 if (event.LeftUp()) {
9166 bool b_startedit_route = false;
9167 m_dragoffsetSet = false;
9168
9169 if (g_btouch) {
9170 m_bChartDragging = false;
9171 m_bIsInRadius = false;
9172
9173 if (m_routeState) // creating route?
9174 {
9175 if (m_ignore_next_leftup) {
9176 m_ignore_next_leftup = false;
9177 return false;
9178 }
9179
9180 if (m_bedge_pan) {
9181 m_bedge_pan = false;
9182 return false;
9183 }
9184
9185 double rlat, rlon;
9186 bool appending = false;
9187 bool inserting = false;
9188 Route *tail = 0;
9189
9190 rlat = m_cursor_lat;
9191 rlon = m_cursor_lon;
9192
9193 if (m_pRoutePointEditTarget) {
9194 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9195 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9196 if (!g_bopengl) {
9197 wxRect wp_rect;
9198 RoutePointGui(*m_pRoutePointEditTarget)
9199 .CalculateDCRect(m_dc_route, this, &wp_rect);
9200 RefreshRect(wp_rect, true);
9201 }
9202 m_pRoutePointEditTarget = NULL;
9203 }
9204 m_bRouteEditing = true;
9205
9206 if (m_routeState == 1) {
9207 m_pMouseRoute = new Route();
9208 m_pMouseRoute->SetHiLite(50);
9209 pRouteList->push_back(m_pMouseRoute);
9210 r_rband.x = x;
9211 r_rband.y = y;
9212 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9213 }
9214
9215 // Check to see if there is a nearby point which may be reused
9216 RoutePoint *pMousePoint = NULL;
9217
9218 // Calculate meaningful SelectRadius
9219 double nearby_radius_meters =
9220 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9221
9222 RoutePoint *pNearbyPoint =
9223 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9224 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9225 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9226 int dlg_return;
9227#ifndef __WXOSX__
9228 m_FinishRouteOnKillFocus =
9229 false; // Avoid route finish on focus change for message dialog
9230 dlg_return = OCPNMessageBox(
9231 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9232 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9233 m_FinishRouteOnKillFocus = true;
9234#else
9235 dlg_return = wxID_YES;
9236#endif
9237 if (dlg_return == wxID_YES) {
9238 pMousePoint = pNearbyPoint;
9239
9240 // Using existing waypoint, so nothing to delete for undo.
9241 if (m_routeState > 1)
9242 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9243 Undo_HasParent, NULL);
9244 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9245
9246 bool procede = false;
9247 if (tail) {
9248 procede = true;
9249 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9250 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9251 procede = false;
9252 }
9253
9254 if (procede) {
9255 int dlg_return;
9256 m_FinishRouteOnKillFocus = false;
9257 if (m_routeState == 1) { // first point in new route, preceeding
9258 // route to be added? touch case
9259
9260 wxString dmsg =
9261 _("Insert first part of this route in the new route?");
9262 if (tail->GetIndexOf(pMousePoint) ==
9263 tail->GetnPoints()) // Starting on last point of another
9264 // route?
9265 dmsg = _("Insert this route in the new route?");
9266
9267 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9268 dlg_return =
9269 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9270 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9271 m_FinishRouteOnKillFocus = true;
9272
9273 if (dlg_return == wxID_YES) {
9274 inserting = true; // part of the other route will be
9275 // preceeding the new route
9276 }
9277 }
9278 } else {
9279 wxString dmsg =
9280 _("Append last part of this route to the new route?");
9281 if (tail->GetIndexOf(pMousePoint) == 1)
9282 dmsg = _(
9283 "Append this route to the new route?"); // Picking the
9284 // first point of
9285 // another route?
9286
9287 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9288 dlg_return =
9289 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9290 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9291 m_FinishRouteOnKillFocus = true;
9292
9293 if (dlg_return == wxID_YES) {
9294 appending = true; // part of the other route will be
9295 // appended to the new route
9296 }
9297 }
9298 }
9299 }
9300
9301 // check all other routes to see if this point appears in any other
9302 // route If it appears in NO other route, then it should e
9303 // considered an isolated mark
9304 if (!FindRouteContainingWaypoint(pMousePoint))
9305 pMousePoint->SetShared(true);
9306 }
9307 }
9308
9309 if (NULL == pMousePoint) { // need a new point
9310 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9311 "", wxEmptyString);
9312 pMousePoint->SetNameShown(false);
9313
9314 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9315
9316 if (m_routeState > 1)
9317 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9318 Undo_IsOrphanded, NULL);
9319 }
9320
9321 if (m_routeState == 1) {
9322 // First point in the route.
9323 m_pMouseRoute->AddPoint(pMousePoint);
9324 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9325
9326 } else {
9327 if (m_pMouseRoute->m_NextLegGreatCircle) {
9328 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9329 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9330 &rhumbBearing, &rhumbDist);
9331 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9332 &gcDist, &gcBearing, NULL);
9333 double gcDistNM = gcDist / 1852.0;
9334
9335 // Empirically found expression to get reasonable route segments.
9336 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9337 pow(rhumbDist - gcDistNM - 1, 0.5);
9338
9339 wxString msg;
9340 msg << _("For this leg the Great Circle route is ")
9341 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9342 << _(" shorter than rhumbline.\n\n")
9343 << _("Would you like include the Great Circle routing points "
9344 "for this leg?");
9345
9346#ifndef __WXOSX__
9347 m_FinishRouteOnKillFocus = false;
9348 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9349 wxYES_NO | wxNO_DEFAULT);
9350 m_FinishRouteOnKillFocus = true;
9351#else
9352 int answer = wxID_NO;
9353#endif
9354
9355 if (answer == wxID_YES) {
9356 RoutePoint *gcPoint;
9357 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9358 wxRealPoint gcCoord;
9359
9360 for (int i = 1; i <= segmentCount; i++) {
9361 double fraction = (double)i * (1.0 / (double)segmentCount);
9362 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9363 gcDist * fraction, gcBearing,
9364 &gcCoord.x, &gcCoord.y, NULL);
9365
9366 if (i < segmentCount) {
9367 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9368 wxEmptyString);
9369 gcPoint->SetNameShown(false);
9370 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9371 gcPoint);
9372 } else {
9373 gcPoint = pMousePoint; // Last point, previously exsisting!
9374 }
9375
9376 m_pMouseRoute->AddPoint(gcPoint);
9377 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9378
9379 pSelect->AddSelectableRouteSegment(
9380 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9381 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9382 prevGcPoint = gcPoint;
9383 }
9384
9385 undo->CancelUndoableAction(true);
9386
9387 } else {
9388 m_pMouseRoute->AddPoint(pMousePoint);
9389 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9390 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9391 rlon, m_prev_pMousePoint,
9392 pMousePoint, m_pMouseRoute);
9393 undo->AfterUndoableAction(m_pMouseRoute);
9394 }
9395 } else {
9396 // Ordinary rhumblinesegment.
9397 m_pMouseRoute->AddPoint(pMousePoint);
9398 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9399
9400 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9401 rlon, m_prev_pMousePoint,
9402 pMousePoint, m_pMouseRoute);
9403 undo->AfterUndoableAction(m_pMouseRoute);
9404 }
9405 }
9406
9407 m_prev_rlat = rlat;
9408 m_prev_rlon = rlon;
9409 m_prev_pMousePoint = pMousePoint;
9410 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9411
9412 m_routeState++;
9413
9414 if (appending ||
9415 inserting) { // Appending a route or making a new route
9416 int connect = tail->GetIndexOf(pMousePoint);
9417 if (connect == 1) {
9418 inserting = false; // there is nothing to insert
9419 appending = true; // so append
9420 }
9421 int length = tail->GetnPoints();
9422
9423 int i;
9424 int start, stop;
9425 if (appending) {
9426 start = connect + 1;
9427 stop = length;
9428 } else { // inserting
9429 start = 1;
9430 stop = connect;
9431 m_pMouseRoute->RemovePoint(
9432 m_pMouseRoute
9433 ->GetLastPoint()); // Remove the first and only point
9434 }
9435 for (i = start; i <= stop; i++) {
9436 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9437 if (m_pMouseRoute)
9438 m_pMouseRoute->m_lastMousePointIndex =
9439 m_pMouseRoute->GetnPoints();
9440 m_routeState++;
9441 top_frame::Get()->RefreshAllCanvas();
9442 ret = true;
9443 }
9444 m_prev_rlat =
9445 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9446 m_prev_rlon =
9447 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9448 m_pMouseRoute->FinalizeForRendering();
9449 }
9450
9451 Refresh(true);
9452 ret = true;
9453 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9454 {
9455 if (m_bedge_pan) {
9456 m_bedge_pan = false;
9457 return false;
9458 }
9459
9460 if (m_ignore_next_leftup) {
9461 m_ignore_next_leftup = false;
9462 return false;
9463 }
9464
9465 if (m_nMeasureState == 1) {
9466 m_pMeasureRoute = new Route();
9467 pRouteList->push_back(m_pMeasureRoute);
9468 r_rband.x = x;
9469 r_rband.y = y;
9470 }
9471
9472 if (m_pMeasureRoute) {
9473 RoutePoint *pMousePoint =
9474 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9475 wxEmptyString, wxEmptyString);
9476 pMousePoint->m_bShowName = false;
9477
9478 m_pMeasureRoute->AddPoint(pMousePoint);
9479
9480 m_prev_rlat = m_cursor_lat;
9481 m_prev_rlon = m_cursor_lon;
9482 m_prev_pMousePoint = pMousePoint;
9483 m_pMeasureRoute->m_lastMousePointIndex =
9484 m_pMeasureRoute->GetnPoints();
9485
9486 m_nMeasureState++;
9487 } else {
9488 CancelMeasureRoute();
9489 }
9490
9491 Refresh(true);
9492 ret = true;
9493 } else {
9494 bool bSelectAllowed = true;
9495 if (NULL == g_pMarkInfoDialog) {
9496 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9497 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9498 bSelectAllowed = false;
9499
9500 // Avoid accidental selection of routepoint if last touchdown started
9501 // a significant chart drag operation
9502 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9503 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9504 significant_drag) ||
9505 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9506 significant_drag)) {
9507 bSelectAllowed = false;
9508 }
9509
9510 /*if this left up happens at the end of a route point dragging and if
9511 the cursor/thumb is on the draghandle icon, not on the point iself a new
9512 selection will select nothing and the drag will never be ended, so the
9513 legs around this point never selectable. At this step we don't need a
9514 new selection, just keep the previoulsly selected and dragged point */
9515 if (m_bRoutePoinDragging) bSelectAllowed = false;
9516
9517 if (bSelectAllowed) {
9518 bool b_was_editing_mark = m_bMarkEditing;
9519 bool b_was_editing_route = m_bRouteEditing;
9520 FindRoutePointsAtCursor(SelectRadius,
9521 true); // Possibly selecting a point in a
9522 // route for later dragging
9523
9524 /*route and a mark points in layer can't be dragged so should't be
9525 * selected and no draghandle icon*/
9526 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9527 m_pRoutePointEditTarget = NULL;
9528
9529 if (!b_was_editing_route) {
9530 if (m_pEditRouteArray) {
9531 b_startedit_route = true;
9532
9533 // Hide the track and route rollover during route point edit, not
9534 // needed, and may be confusing
9535 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9536 m_pTrackRolloverWin->IsActive(false);
9537 }
9538 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9539 m_pRouteRolloverWin->IsActive(false);
9540 }
9541
9542 wxRect pre_rect;
9543 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9544 ir++) {
9545 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9546 // Need to validate route pointer
9547 // Route may be gone due to drgging close to ownship with
9548 // "Delete On Arrival" state set, as in the case of
9549 // navigating to an isolated waypoint on a temporary route
9550 if (g_pRouteMan->IsRouteValid(pr)) {
9551 // pr->SetHiLite(50);
9552 wxRect route_rect;
9553 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9554 pre_rect.Union(route_rect);
9555 }
9556 }
9557 RefreshRect(pre_rect, true);
9558 }
9559 } else {
9560 b_startedit_route = false;
9561 }
9562
9563 // Mark editing in touch mode, left-up event.
9564 if (m_pRoutePointEditTarget) {
9565 if (b_was_editing_mark ||
9566 b_was_editing_route) { // kill previous hilight
9567 if (m_lastRoutePointEditTarget) {
9568 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9569 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9570 RoutePointGui(*m_lastRoutePointEditTarget)
9571 .EnableDragHandle(false);
9572 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9573 SELTYPE_DRAGHANDLE);
9574 }
9575 }
9576
9577 if (m_pRoutePointEditTarget) {
9578 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9579 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9580 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9581 wxPoint2DDouble dragHandlePoint =
9582 RoutePointGui(*m_pRoutePointEditTarget)
9583 .GetDragHandlePoint(this);
9584 pSelect->AddSelectablePoint(
9585 dragHandlePoint.m_y, dragHandlePoint.m_x,
9586 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9587 }
9588 } else { // Deselect everything
9589 if (m_lastRoutePointEditTarget) {
9590 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9591 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9592 RoutePointGui(*m_lastRoutePointEditTarget)
9593 .EnableDragHandle(false);
9594 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9595 SELTYPE_DRAGHANDLE);
9596
9597 // Clear any routes being edited, probably orphans
9598 wxArrayPtrVoid *lastEditRouteArray =
9600 m_lastRoutePointEditTarget);
9601 if (lastEditRouteArray) {
9602 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9603 ir++) {
9604 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9605 if (g_pRouteMan->IsRouteValid(pr)) {
9606 pr->m_bIsBeingEdited = false;
9607 }
9608 }
9609 delete lastEditRouteArray;
9610 }
9611 }
9612 }
9613
9614 // Do the refresh
9615
9616 if (g_bopengl) {
9617 InvalidateGL();
9618 Refresh(false);
9619 } else {
9620 if (m_lastRoutePointEditTarget) {
9621 wxRect wp_rect;
9622 RoutePointGui(*m_lastRoutePointEditTarget)
9623 .CalculateDCRect(m_dc_route, this, &wp_rect);
9624 RefreshRect(wp_rect, true);
9625 }
9626
9627 if (m_pRoutePointEditTarget) {
9628 wxRect wp_rect;
9629 RoutePointGui(*m_pRoutePointEditTarget)
9630 .CalculateDCRect(m_dc_route, this, &wp_rect);
9631 RefreshRect(wp_rect, true);
9632 }
9633 }
9634 }
9635 } // bSelectAllowed
9636
9637 // Check to see if there is a route or AIS target under the cursor
9638 // If so, start the rollover timer which creates the popup
9639 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9640 bool b_start_rollover = false;
9641 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9642 SelectItem *pFind = pSelectAIS->FindSelection(
9643 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9644 if (pFind) b_start_rollover = true;
9645 }
9646
9647 if (!b_start_rollover && !b_startedit_route) {
9648 SelectableItemList SelList = pSelect->FindSelectionList(
9649 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9650 for (SelectItem *pFindSel : SelList) {
9651 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9652 if (pr && pr->IsVisible()) {
9653 b_start_rollover = true;
9654 break;
9655 }
9656 } // while
9657 }
9658
9659 if (!b_start_rollover && !b_startedit_route) {
9660 SelectableItemList SelList = pSelect->FindSelectionList(
9661 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9662 for (SelectItem *pFindSel : SelList) {
9663 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9664 if (tr && tr->IsVisible()) {
9665 b_start_rollover = true;
9666 break;
9667 }
9668 } // while
9669 }
9670
9671 if (b_start_rollover)
9672 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9673 wxTIMER_ONE_SHOT);
9674 Route *tail = 0;
9675 Route *current = 0;
9676 bool appending = false;
9677 bool inserting = false;
9678 int connect = 0;
9679 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9680 // drag
9681 if (m_pRoutePointEditTarget) {
9682 // Check to see if there is a nearby point which may replace the
9683 // dragged one
9684 RoutePoint *pMousePoint = NULL;
9685
9686 int index_last;
9687 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9688 double nearby_radius_meters =
9689 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9690 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9691 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9692 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9693 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9694 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9695 bool duplicate =
9696 false; // ensure we won't create duplicate point in routes
9697 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9698 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9699 ir++) {
9700 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9701 if (pr && pr->pRoutePointList) {
9702 auto *list = pr->pRoutePointList;
9703 auto pos =
9704 std::find(list->begin(), list->end(), pNearbyPoint);
9705 if (pos != list->end()) {
9706 duplicate = true;
9707 break;
9708 }
9709 }
9710 }
9711 }
9712
9713 // Special case:
9714 // Allow "re-use" of a route's waypoints iff it is a simple
9715 // isolated route. This allows, for instance, creation of a closed
9716 // polygon route
9717 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9718
9719 if (!duplicate) {
9720 int dlg_return;
9721 dlg_return =
9722 OCPNMessageBox(this,
9723 _("Replace this RoutePoint by the nearby "
9724 "Waypoint?"),
9725 _("OpenCPN RoutePoint change"),
9726 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9727 if (dlg_return == wxID_YES) {
9728 /*double confirmation if the dragged point has been manually
9729 * created which can be important and could be deleted
9730 * unintentionally*/
9731
9732 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9733 pNearbyPoint);
9734 current =
9735 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9736
9737 if (tail && current && (tail != current)) {
9738 int dlg_return1;
9739 connect = tail->GetIndexOf(pNearbyPoint);
9740 int index_current_route =
9741 current->GetIndexOf(m_pRoutePointEditTarget);
9742 index_last = current->GetIndexOf(current->GetLastPoint());
9743 dlg_return1 = wxID_NO;
9744 if (index_last ==
9745 index_current_route) { // we are dragging the last
9746 // point of the route
9747 if (connect != tail->GetnPoints()) { // anything to do?
9748
9749 wxString dmsg(
9750 _("Last part of route to be appended to dragged "
9751 "route?"));
9752 if (connect == 1)
9753 dmsg =
9754 _("Full route to be appended to dragged route?");
9755
9756 dlg_return1 = OCPNMessageBox(
9757 this, dmsg, _("OpenCPN Route Create"),
9758 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9759 if (dlg_return1 == wxID_YES) {
9760 appending = true;
9761 }
9762 }
9763 } else if (index_current_route ==
9764 1) { // dragging the first point of the route
9765 if (connect != 1) { // anything to do?
9766
9767 wxString dmsg(
9768 _("First part of route to be inserted into dragged "
9769 "route?"));
9770 if (connect == tail->GetnPoints())
9771 dmsg = _(
9772 "Full route to be inserted into dragged route?");
9773
9774 dlg_return1 = OCPNMessageBox(
9775 this, dmsg, _("OpenCPN Route Create"),
9776 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9777 if (dlg_return1 == wxID_YES) {
9778 inserting = true;
9779 }
9780 }
9781 }
9782 }
9783
9784 if (m_pRoutePointEditTarget->IsShared()) {
9785 // dlg_return = wxID_NO;
9786 dlg_return = OCPNMessageBox(
9787 this,
9788 _("Do you really want to delete and replace this "
9789 "WayPoint") +
9790 "\n" + _("which has been created manually?"),
9791 ("OpenCPN RoutePoint warning"),
9792 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9793 }
9794 }
9795 if (dlg_return == wxID_YES) {
9796 pMousePoint = pNearbyPoint;
9797 if (pMousePoint->m_bIsolatedMark) {
9798 pMousePoint->SetShared(true);
9799 }
9800 pMousePoint->m_bIsolatedMark =
9801 false; // definitely no longer isolated
9802 pMousePoint->m_bIsInRoute = true;
9803 }
9804 }
9805 }
9806 }
9807 if (!pMousePoint)
9808 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9809
9810 if (m_pEditRouteArray) {
9811 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9812 ir++) {
9813 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9814 if (g_pRouteMan->IsRouteValid(pr)) {
9815 if (pMousePoint) { // remove the dragged point and insert the
9816 // nearby
9817 auto *list = pr->pRoutePointList;
9818 auto pos = std::find(list->begin(), list->end(),
9819 m_pRoutePointEditTarget);
9820
9821 pSelect->DeleteAllSelectableRoutePoints(pr);
9822 pSelect->DeleteAllSelectableRouteSegments(pr);
9823
9824 pr->pRoutePointList->insert(pos, pMousePoint);
9825 pos = std::find(list->begin(), list->end(),
9826 m_pRoutePointEditTarget);
9827 pr->pRoutePointList->erase(pos);
9828
9829 pSelect->AddAllSelectableRouteSegments(pr);
9830 pSelect->AddAllSelectableRoutePoints(pr);
9831 }
9832 pr->FinalizeForRendering();
9833 pr->UpdateSegmentDistances();
9834 if (m_bRoutePoinDragging) {
9835 // pConfig->UpdateRoute(pr);
9836 NavObj_dB::GetInstance().UpdateRoute(pr);
9837 }
9838 }
9839 }
9840 }
9841
9842 // Update the RouteProperties Dialog, if currently shown
9843 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9844 if (m_pEditRouteArray) {
9845 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9846 ir++) {
9847 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9848 if (g_pRouteMan->IsRouteValid(pr)) {
9849 if (pRoutePropDialog->GetRoute() == pr) {
9850 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9851 }
9852 /* cannot edit track points anyway
9853 else if ( ( NULL !=
9854 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9855 pTrackPropDialog->m_pTrack == pr ) {
9856 pTrackPropDialog->SetTrackAndUpdate(
9857 pr );
9858 }
9859 */
9860 }
9861 }
9862 }
9863 }
9864 if (pMousePoint) { // clear all about the dragged point
9865 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9866 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9867 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9868 // Hide mark properties dialog if open on the replaced point
9869 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9870 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9871 g_pMarkInfoDialog->Hide();
9872
9873 delete m_pRoutePointEditTarget;
9874 m_lastRoutePointEditTarget = NULL;
9875 m_pRoutePointEditTarget = NULL;
9876 undo->AfterUndoableAction(pMousePoint);
9877 undo->InvalidateUndo();
9878 }
9879 }
9880 }
9881
9882 else if (m_bMarkEditing) { // End of way point drag
9883 if (m_pRoutePointEditTarget)
9884 if (m_bRoutePoinDragging) {
9885 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9886 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9887 }
9888 }
9889
9890 if (m_pRoutePointEditTarget)
9891 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9892
9893 if (!m_pRoutePointEditTarget) {
9894 delete m_pEditRouteArray;
9895 m_pEditRouteArray = NULL;
9896 m_bRouteEditing = false;
9897 }
9898 m_bRoutePoinDragging = false;
9899
9900 if (appending) { // Appending to the route of which the last point is
9901 // dragged onto another route
9902
9903 // copy tail from connect until length to end of current after dragging
9904
9905 int length = tail->GetnPoints();
9906 for (int i = connect + 1; i <= length; i++) {
9907 current->AddPointAndSegment(tail->GetPoint(i), false);
9908 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9909 m_routeState++;
9910 top_frame::Get()->RefreshAllCanvas();
9911 ret = true;
9912 }
9913 current->FinalizeForRendering();
9914 current->m_bIsBeingEdited = false;
9915 FinishRoute();
9916 g_pRouteMan->DeleteRoute(tail);
9917 }
9918 if (inserting) {
9919 pSelect->DeleteAllSelectableRoutePoints(current);
9920 pSelect->DeleteAllSelectableRouteSegments(current);
9921 for (int i = 1; i < connect; i++) { // numbering in the tail route
9922 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9923 }
9924 pSelect->AddAllSelectableRouteSegments(current);
9925 pSelect->AddAllSelectableRoutePoints(current);
9926 current->FinalizeForRendering();
9927 current->m_bIsBeingEdited = false;
9928 g_pRouteMan->DeleteRoute(tail);
9929 }
9930
9931 // Update the RouteProperties Dialog, if currently shown
9932 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9933 if (m_pEditRouteArray) {
9934 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9935 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9936 if (g_pRouteMan->IsRouteValid(pr)) {
9937 if (pRoutePropDialog->GetRoute() == pr) {
9938 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9939 }
9940 }
9941 }
9942 }
9943 }
9944
9945 } // g_btouch
9946
9947 else { // !g_btouch
9948 if (m_bRouteEditing) { // End of RoutePoint drag
9949 Route *tail = 0;
9950 Route *current = 0;
9951 bool appending = false;
9952 bool inserting = false;
9953 int connect = 0;
9954 int index_last;
9955 if (m_pRoutePointEditTarget) {
9956 m_pRoutePointEditTarget->m_bBlink = false;
9957 // Check to see if there is a nearby point which may replace the
9958 // dragged one
9959 RoutePoint *pMousePoint = NULL;
9960 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9961 double nearby_radius_meters =
9962 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9963 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9964 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9965 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9966 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9967 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9968 bool duplicate = false; // don't create duplicate point in routes
9969 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9970 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9971 ir++) {
9972 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9973 if (pr && pr->pRoutePointList) {
9974 auto *list = pr->pRoutePointList;
9975 auto pos =
9976 std::find(list->begin(), list->end(), pNearbyPoint);
9977 if (pos != list->end()) {
9978 duplicate = true;
9979 break;
9980 }
9981 }
9982 }
9983 }
9984
9985 // Special case:
9986 // Allow "re-use" of a route's waypoints iff it is a simple
9987 // isolated route. This allows, for instance, creation of a closed
9988 // polygon route
9989 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9990
9991 if (!duplicate) {
9992 int dlg_return;
9993 dlg_return =
9994 OCPNMessageBox(this,
9995 _("Replace this RoutePoint by the nearby "
9996 "Waypoint?"),
9997 _("OpenCPN RoutePoint change"),
9998 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9999 if (dlg_return == wxID_YES) {
10000 /*double confirmation if the dragged point has been manually
10001 * created which can be important and could be deleted
10002 * unintentionally*/
10003 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10004 pNearbyPoint);
10005 current =
10006 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10007
10008 if (tail && current && (tail != current)) {
10009 int dlg_return1;
10010 connect = tail->GetIndexOf(pNearbyPoint);
10011 int index_current_route =
10012 current->GetIndexOf(m_pRoutePointEditTarget);
10013 index_last = current->GetIndexOf(current->GetLastPoint());
10014 dlg_return1 = wxID_NO;
10015 if (index_last ==
10016 index_current_route) { // we are dragging the last
10017 // point of the route
10018 if (connect != tail->GetnPoints()) { // anything to do?
10019
10020 wxString dmsg(
10021 _("Last part of route to be appended to dragged "
10022 "route?"));
10023 if (connect == 1)
10024 dmsg =
10025 _("Full route to be appended to dragged route?");
10026
10027 dlg_return1 = OCPNMessageBox(
10028 this, dmsg, _("OpenCPN Route Create"),
10029 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10030 if (dlg_return1 == wxID_YES) {
10031 appending = true;
10032 }
10033 }
10034 } else if (index_current_route ==
10035 1) { // dragging the first point of the route
10036 if (connect != 1) { // anything to do?
10037
10038 wxString dmsg(
10039 _("First part of route to be inserted into dragged "
10040 "route?"));
10041 if (connect == tail->GetnPoints())
10042 dmsg = _(
10043 "Full route to be inserted into dragged route?");
10044
10045 dlg_return1 = OCPNMessageBox(
10046 this, dmsg, _("OpenCPN Route Create"),
10047 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10048 if (dlg_return1 == wxID_YES) {
10049 inserting = true;
10050 }
10051 }
10052 }
10053 }
10054
10055 if (m_pRoutePointEditTarget->IsShared()) {
10056 dlg_return = wxID_NO;
10057 dlg_return = OCPNMessageBox(
10058 this,
10059 _("Do you really want to delete and replace this "
10060 "WayPoint") +
10061 "\n" + _("which has been created manually?"),
10062 ("OpenCPN RoutePoint warning"),
10063 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10064 }
10065 }
10066 if (dlg_return == wxID_YES) {
10067 pMousePoint = pNearbyPoint;
10068 if (pMousePoint->m_bIsolatedMark) {
10069 pMousePoint->SetShared(true);
10070 }
10071 pMousePoint->m_bIsolatedMark =
10072 false; // definitely no longer isolated
10073 pMousePoint->m_bIsInRoute = true;
10074 }
10075 }
10076 }
10077 }
10078 if (!pMousePoint)
10079 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10080
10081 if (m_pEditRouteArray) {
10082 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10083 ir++) {
10084 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10085 if (g_pRouteMan->IsRouteValid(pr)) {
10086 if (pMousePoint) { // replace dragged point by nearby one
10087 auto *list = pr->pRoutePointList;
10088 auto pos = std::find(list->begin(), list->end(),
10089 m_pRoutePointEditTarget);
10090
10091 pSelect->DeleteAllSelectableRoutePoints(pr);
10092 pSelect->DeleteAllSelectableRouteSegments(pr);
10093
10094 pr->pRoutePointList->insert(pos, pMousePoint);
10095 pos = std::find(list->begin(), list->end(),
10096 m_pRoutePointEditTarget);
10097 if (pos != list->end()) list->erase(pos);
10098 // pr->pRoutePointList->erase(pos + 1);
10099
10100 pSelect->AddAllSelectableRouteSegments(pr);
10101 pSelect->AddAllSelectableRoutePoints(pr);
10102 }
10103 pr->FinalizeForRendering();
10104 pr->UpdateSegmentDistances();
10105 pr->m_bIsBeingEdited = false;
10106
10107 if (m_bRoutePoinDragging) {
10108 // Special case optimization.
10109 // Dragging a single point of a route
10110 // without any point additions or re-ordering
10111 if (!pMousePoint)
10112 NavObj_dB::GetInstance().UpdateRoutePoint(
10113 m_pRoutePointEditTarget);
10114 else
10115 NavObj_dB::GetInstance().UpdateRoute(pr);
10116 }
10117 pr->SetHiLite(0);
10118 }
10119 }
10120 Refresh(false);
10121 }
10122
10123 if (appending) {
10124 // copy tail from connect until length to end of current after
10125 // dragging
10126
10127 int length = tail->GetnPoints();
10128 for (int i = connect + 1; i <= length; i++) {
10129 current->AddPointAndSegment(tail->GetPoint(i), false);
10130 if (current)
10131 current->m_lastMousePointIndex = current->GetnPoints();
10132 m_routeState++;
10133 top_frame::Get()->RefreshAllCanvas();
10134 ret = true;
10135 }
10136 current->FinalizeForRendering();
10137 current->m_bIsBeingEdited = false;
10138 FinishRoute();
10139 g_pRouteMan->DeleteRoute(tail);
10140 }
10141 if (inserting) {
10142 pSelect->DeleteAllSelectableRoutePoints(current);
10143 pSelect->DeleteAllSelectableRouteSegments(current);
10144 for (int i = 1; i < connect; i++) { // numbering in the tail route
10145 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10146 }
10147 pSelect->AddAllSelectableRouteSegments(current);
10148 pSelect->AddAllSelectableRoutePoints(current);
10149 current->FinalizeForRendering();
10150 current->m_bIsBeingEdited = false;
10151 g_pRouteMan->DeleteRoute(tail);
10152 }
10153
10154 // Update the RouteProperties Dialog, if currently shown
10155 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10156 if (m_pEditRouteArray) {
10157 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10158 ir++) {
10159 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10160 if (g_pRouteMan->IsRouteValid(pr)) {
10161 if (pRoutePropDialog->GetRoute() == pr) {
10162 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10163 }
10164 }
10165 }
10166 }
10167 }
10168
10169 if (pMousePoint) {
10170 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10171 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10172 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10173 // Hide mark properties dialog if open on the replaced point
10174 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10175 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10176 g_pMarkInfoDialog->Hide();
10177
10178 delete m_pRoutePointEditTarget;
10179 m_lastRoutePointEditTarget = NULL;
10180 undo->AfterUndoableAction(pMousePoint);
10181 undo->InvalidateUndo();
10182 } else {
10183 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10184 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10185
10186 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10187 }
10188
10189 delete m_pEditRouteArray;
10190 m_pEditRouteArray = NULL;
10191 }
10192
10193 InvalidateGL();
10194 m_bRouteEditing = false;
10195 m_pRoutePointEditTarget = NULL;
10196
10197 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10198 ret = true;
10199 }
10200
10201 else if (m_bMarkEditing) { // end of Waypoint drag
10202 if (m_pRoutePointEditTarget) {
10203 if (m_bRoutePoinDragging) {
10204 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10205 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10206 }
10207 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10208 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10209 if (!g_bopengl) {
10210 wxRect wp_rect;
10211 RoutePointGui(*m_pRoutePointEditTarget)
10212 .CalculateDCRect(m_dc_route, this, &wp_rect);
10213 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10214 RefreshRect(wp_rect, true);
10215 }
10216 }
10217 m_pRoutePointEditTarget = NULL;
10218 m_bMarkEditing = false;
10219 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10220 ret = true;
10221 }
10222
10223 else if (leftIsDown) { // left click for chart center
10224 leftIsDown = false;
10225 ret = false;
10226
10227 if (!g_btouch) {
10228 if (!m_bChartDragging && !m_bMeasure_Active) {
10229 } else {
10230 m_bChartDragging = false;
10231 }
10232 }
10233 }
10234 m_bRoutePoinDragging = false;
10235 } // !btouch
10236
10237 if (ret) return true;
10238 } // left up
10239
10240 if (event.RightDown()) {
10241 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10242 last_drag.x = mx;
10243 last_drag.y = my;
10244
10245 if (g_btouch) {
10246 // if( m_pRoutePointEditTarget )
10247 // return false;
10248 }
10249
10250 ret = true;
10251 m_FinishRouteOnKillFocus = false;
10252 CallPopupMenu(mx, my);
10253 m_FinishRouteOnKillFocus = true;
10254 } // Right down
10255
10256 return ret;
10257}
10258
10259bool panleftIsDown;
10260bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10261 // Skip all mouse processing if shift is held.
10262 // This allows plugins to implement shift+drag behaviors.
10263 if (event.ShiftDown()) {
10264 return false;
10265 }
10266 int x, y;
10267 event.GetPosition(&x, &y);
10268
10269 x *= m_displayScale;
10270 y *= m_displayScale;
10271
10272 // Check for wheel rotation
10273 // ideally, should be just longer than the time between
10274 // processing accumulated mouse events from the event queue
10275 // as would happen during screen redraws.
10276 int wheel_dir = event.GetWheelRotation();
10277
10278 if (wheel_dir) {
10279 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10280 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10281
10282 double factor = g_mouse_zoom_sensitivity;
10283 if (wheel_dir < 0) factor = 1 / factor;
10284
10285 if (g_bsmoothpanzoom) {
10286 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10287 if (wheel_dir == m_last_wheel_dir) {
10288 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10289 // m_zoom_target /= factor;
10290 } else
10291 StopMovement();
10292 } else {
10293 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10294 m_wheelstopwatch.Start(0);
10295 // m_zoom_target = VPoint.chart_scale / factor;
10296 }
10297 }
10298
10299 m_last_wheel_dir = wheel_dir;
10300
10301 ZoomCanvas(factor, true, false);
10302 }
10303
10304 if (event.LeftDown()) {
10305 // Skip the first left click if it will cause a canvas focus shift
10306 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10307 return false;
10308 }
10309
10310 last_drag.x = x, last_drag.y = y;
10311 panleftIsDown = true;
10312 }
10313
10314 if (event.LeftUp()) {
10315 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10316 // seen here.
10317 panleftIsDown = false;
10318
10319 if (!g_btouch) {
10320 if (!m_bChartDragging && !m_bMeasure_Active) {
10321 switch (cursor_region) {
10322 case MID_RIGHT: {
10323 PanCanvas(100, 0);
10324 break;
10325 }
10326
10327 case MID_LEFT: {
10328 PanCanvas(-100, 0);
10329 break;
10330 }
10331
10332 case MID_TOP: {
10333 PanCanvas(0, 100);
10334 break;
10335 }
10336
10337 case MID_BOT: {
10338 PanCanvas(0, -100);
10339 break;
10340 }
10341
10342 case CENTER: {
10343 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10344 break;
10345 }
10346 }
10347 } else {
10348 m_bChartDragging = false;
10349 }
10350 }
10351 }
10352 }
10353
10354 if (event.Dragging() && event.LeftIsDown()) {
10355 /*
10356 * fixed dragging.
10357 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10358 * before the drag event. Hence, as there is no mouse down event, last_drag
10359 * is not reset before the drag. And that results in one single drag
10360 * session, meaning you cannot drag the map a few miles north, lift your
10361 * finger, and the go even further north. Instead, the map resets itself
10362 * always to the very first drag start (since there is not reset of
10363 * last_drag).
10364 *
10365 * Besides, should not left down and dragging be enough of a situation to
10366 * start a drag procedure?
10367 *
10368 * Anyways, guarded it to be active in touch situations only.
10369 */
10370 if (g_btouch && !m_inPinch) {
10371 struct timespec now;
10372 clock_gettime(CLOCK_MONOTONIC, &now);
10373 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10374
10375 bool trigger_hold = false;
10376 if (false == m_bChartDragging) {
10377 if (m_DragTrigger < 0) {
10378 // printf("\ntrigger1\n");
10379 m_DragTrigger = 0;
10380 m_DragTriggerStartTime = tnow;
10381 trigger_hold = true;
10382 } else {
10383 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10384 m_DragTrigger = -1; // Reset trigger
10385 // printf("trigger fired\n");
10386 }
10387 }
10388 }
10389 if (trigger_hold) return true;
10390
10391 if (false == m_bChartDragging) {
10392 // printf("starting drag\n");
10393 // Reset drag calculation members
10394 last_drag.x = x - 1, last_drag.y = y - 1;
10395 m_bChartDragging = true;
10396 m_chart_drag_total_time = 0;
10397 m_chart_drag_total_x = 0;
10398 m_chart_drag_total_y = 0;
10399 m_inertia_last_drag_x = x;
10400 m_inertia_last_drag_y = y;
10401 m_drag_vec_x.clear();
10402 m_drag_vec_y.clear();
10403 m_drag_vec_t.clear();
10404 m_last_drag_time = tnow;
10405 }
10406
10407 // Calculate and store drag dynamics.
10408 uint64_t delta_t = tnow - m_last_drag_time;
10409 double delta_tf = delta_t / 1e9;
10410
10411 m_chart_drag_total_time += delta_tf;
10412 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10413 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10414
10415 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10416 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10417 m_drag_vec_t.push_back(delta_tf);
10418
10419 m_inertia_last_drag_x = x;
10420 m_inertia_last_drag_y = y;
10421 m_last_drag_time = tnow;
10422
10423 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10424 m_bChartDragging = true;
10425 StartTimedMovement();
10426 m_pan_drag.x += last_drag.x - x;
10427 m_pan_drag.y += last_drag.y - y;
10428 last_drag.x = x, last_drag.y = y;
10429 }
10430 } else if (!g_btouch) {
10431 if ((last_drag.x != x) || (last_drag.y != y)) {
10432 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10433 // dragging on route create.
10434 // github #2994
10435 m_bChartDragging = true;
10436 StartTimedMovement();
10437 m_pan_drag.x += last_drag.x - x;
10438 m_pan_drag.y += last_drag.y - y;
10439 last_drag.x = x, last_drag.y = y;
10440 }
10441 }
10442 }
10443
10444 // Handle some special cases
10445 if (g_btouch) {
10446 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10447 // deactivate next LeftUp to ovoid creating an unexpected point
10448 m_ignore_next_leftup = true;
10449 m_DoubleClickTimer->Start();
10450 singleClickEventIsValid = false;
10451 }
10452 }
10453 }
10454
10455 return true;
10456}
10457
10458void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10459 if (MouseEventOverlayWindows(event)) return;
10460
10461 if (MouseEventSetup(event)) return; // handled, no further action required
10462
10463 bool nm = MouseEventProcessObjects(event);
10464 if (!nm) MouseEventProcessCanvas(event);
10465}
10466
10467void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10468 // Switch to the appropriate cursor on mouse movement
10469
10470 wxCursor *ptarget_cursor = pCursorArrow;
10471 if (!pPlugIn_Cursor) {
10472 ptarget_cursor = pCursorArrow;
10473 if ((!m_routeState) &&
10474 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10475 if (cursor_region == MID_RIGHT) {
10476 ptarget_cursor = pCursorRight;
10477 } else if (cursor_region == MID_LEFT) {
10478 ptarget_cursor = pCursorLeft;
10479 } else if (cursor_region == MID_TOP) {
10480 ptarget_cursor = pCursorDown;
10481 } else if (cursor_region == MID_BOT) {
10482 ptarget_cursor = pCursorUp;
10483 } else {
10484 ptarget_cursor = pCursorArrow;
10485 }
10486 } else if (m_bMeasure_Active ||
10487 m_routeState) // If Measure tool use Pencil Cursor
10488 ptarget_cursor = pCursorPencil;
10489 } else {
10490 ptarget_cursor = pPlugIn_Cursor;
10491 }
10492
10493 SetCursor(*ptarget_cursor);
10494}
10495
10496void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10497 SetCursor(*pCursorArrow);
10498}
10499
10500void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10501 ChartPlugInWrapper *target_plugin_chart = NULL;
10502 s57chart *Chs57 = NULL;
10503 wxFileName file;
10504 wxArrayString files;
10505
10506 ChartBase *target_chart = GetChartAtCursor();
10507 if (target_chart) {
10508 file.Assign(target_chart->GetFullPath());
10509 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10510 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10511 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10512 else
10513 Chs57 = dynamic_cast<s57chart *>(target_chart);
10514 } else { // target_chart = null, might be mbtiles
10515 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10516 unsigned int im = stackIndexArray.size();
10517 int scale = 2147483647; // max 32b integer
10518 if (VPoint.b_quilt && im > 0) {
10519 for (unsigned int is = 0; is < im; is++) {
10520 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10521 CHART_TYPE_MBTILES) {
10522 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10523 double lat, lon;
10524 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10525 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10526 .GetBBox()
10527 .Contains(lat, lon)) {
10528 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10529 scale) {
10530 scale =
10531 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10532 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10533 }
10534 }
10535 }
10536 }
10537 }
10538 }
10539
10540 std::vector<Ais8_001_22 *> area_notices;
10541
10542 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10543 float vp_scale = GetVPScale();
10544
10545 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10546 auto target_data = target.second;
10547 if (!target_data->area_notices.empty()) {
10548 for (auto &ani : target_data->area_notices) {
10549 Ais8_001_22 &area_notice = ani.second;
10550
10551 BoundingBox bbox;
10552
10553 for (Ais8_001_22_SubAreaList::iterator sa =
10554 area_notice.sub_areas.begin();
10555 sa != area_notice.sub_areas.end(); ++sa) {
10556 switch (sa->shape) {
10557 case AIS8_001_22_SHAPE_CIRCLE: {
10558 wxPoint target_point;
10559 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10560 bbox.Expand(target_point);
10561 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10562 break;
10563 }
10564 case AIS8_001_22_SHAPE_RECT: {
10565 wxPoint target_point;
10566 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10567 bbox.Expand(target_point);
10568 if (sa->e_dim_m > sa->n_dim_m)
10569 bbox.EnLarge(sa->e_dim_m * vp_scale);
10570 else
10571 bbox.EnLarge(sa->n_dim_m * vp_scale);
10572 break;
10573 }
10574 case AIS8_001_22_SHAPE_POLYGON:
10575 case AIS8_001_22_SHAPE_POLYLINE: {
10576 for (int i = 0; i < 4; ++i) {
10577 double lat = sa->latitude;
10578 double lon = sa->longitude;
10579 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10580 &lat, &lon);
10581 wxPoint target_point;
10582 GetCanvasPointPix(lat, lon, &target_point);
10583 bbox.Expand(target_point);
10584 }
10585 break;
10586 }
10587 case AIS8_001_22_SHAPE_SECTOR: {
10588 double lat1 = sa->latitude;
10589 double lon1 = sa->longitude;
10590 double lat, lon;
10591 wxPoint target_point;
10592 GetCanvasPointPix(lat1, lon1, &target_point);
10593 bbox.Expand(target_point);
10594 for (int i = 0; i < 18; ++i) {
10595 ll_gc_ll(
10596 lat1, lon1,
10597 sa->left_bound_deg +
10598 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10599 sa->radius_m / 1852.0, &lat, &lon);
10600 GetCanvasPointPix(lat, lon, &target_point);
10601 bbox.Expand(target_point);
10602 }
10603 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10604 &lat, &lon);
10605 GetCanvasPointPix(lat, lon, &target_point);
10606 bbox.Expand(target_point);
10607 break;
10608 }
10609 }
10610 }
10611
10612 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10613 area_notices.push_back(&area_notice);
10614 }
10615 }
10616 }
10617 }
10618 }
10619
10620 if (target_chart || !area_notices.empty() || file.HasName()) {
10621 // Go get the array of all objects at the cursor lat/lon
10622 int sel_rad_pix = 5;
10623 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10624
10625 // Make sure we always get the lights from an object, even if we are
10626 // currently not displaying lights on the chart.
10627
10628 SetCursor(wxCURSOR_WAIT);
10629 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10630 if (!lightsVis) SetShowENCLights(true);
10631 ;
10632
10633 ListOfObjRazRules *rule_list = NULL;
10634 ListOfPI_S57Obj *pi_rule_list = NULL;
10635 if (Chs57)
10636 rule_list =
10637 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10638 else if (target_plugin_chart)
10639 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10640 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10641
10642 ListOfObjRazRules *overlay_rule_list = NULL;
10643 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10644 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10645
10646 if (CHs57_Overlay) {
10647 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10648 zlat, zlon, SelectRadius, &GetVP());
10649 }
10650
10651 if (!lightsVis) SetShowENCLights(false);
10652
10653 wxString objText;
10654 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10655 wxString face = dFont->GetFaceName();
10656
10657 if (NULL == g_pObjectQueryDialog) {
10659 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10660 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10661 }
10662
10663 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10664 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10665
10666#ifdef __WXOSX__
10667 // Auto Adjustment for dark mode
10668 fg = g_pObjectQueryDialog->GetForegroundColour();
10669#endif
10670
10671 objText.Printf(
10672 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10673 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10674
10675#ifdef __WXOSX__
10676 int points = dFont->GetPointSize();
10677#else
10678 int points = dFont->GetPointSize() + 1;
10679#endif
10680
10681 int sizes[7];
10682 for (int i = -2; i < 5; i++) {
10683 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10684 }
10685 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10686
10687 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10688
10689 if (overlay_rule_list && CHs57_Overlay) {
10690 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10691 objText << "<hr noshade>";
10692 }
10693
10694 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10695 an != area_notices.end(); ++an) {
10696 objText << "<b>AIS Area Notice:</b> ";
10697 objText << ais8_001_22_notice_names[(*an)->notice_type];
10698 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10699 (*an)->sub_areas.begin();
10700 sa != (*an)->sub_areas.end(); ++sa)
10701 if (!sa->text.empty()) objText << sa->text;
10702 objText << "<br>expires: " << (*an)->expiry_time.Format();
10703 objText << "<hr noshade>";
10704 }
10705
10706 if (Chs57)
10707 objText << Chs57->CreateObjDescriptions(rule_list);
10708 else if (target_plugin_chart)
10709 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10710 pi_rule_list);
10711
10712 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10713
10714 // Add the additional info files
10715 wxString AddFiles, filenameOK;
10716 int filecount = 0;
10717 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10718 // plugin
10719
10720 AddFiles = wxString::Format(
10721 "<hr noshade><br><b>Additional info files attached to: </b> "
10722 "<font "
10723 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10724 "cellpadding=3>",
10725 file.GetFullName());
10726 file.Normalize();
10727 file.Assign(file.GetPath(), "");
10728 wxDir dir(file.GetFullPath());
10729 wxString filename;
10730 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10731 while (cont) {
10732 file.Assign(dir.GetNameWithSep().append(filename));
10733 wxString FormatString =
10734 "<td valign=top><font size=-2><a "
10735 "href=\"%s\">%s</a></font></td>";
10736 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10737 filenameOK = file.GetFullPath(); // remember last valid name
10738 // we are making a 3 columns table. New row only every third file
10739 if (3 * ((int)filecount / 3) == filecount)
10740 FormatString.Prepend("<tr>"); // new row
10741 else
10742 FormatString.Prepend(
10743 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10744 // spacer column
10745
10746 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10747 file.GetFullName());
10748 filecount++;
10749 }
10750 cont = dir.GetNext(&filename);
10751 }
10752 objText << AddFiles << "</table>";
10753 }
10754 objText << "</font>";
10755 objText << "</body></html>";
10756
10757 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10758 g_pObjectQueryDialog->SetHTMLPage(objText);
10759 g_pObjectQueryDialog->Show();
10760 }
10761 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10762 // generate an event to avoid double code
10763 wxHtmlLinkInfo hli(filenameOK);
10764 wxHtmlLinkEvent hle(1, hli);
10765 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10766 }
10767
10768 if (rule_list) rule_list->Clear();
10769 delete rule_list;
10770
10771 if (overlay_rule_list) overlay_rule_list->Clear();
10772 delete overlay_rule_list;
10773
10774 if (pi_rule_list) pi_rule_list->Clear();
10775 delete pi_rule_list;
10776
10777 SetCursor(wxCURSOR_ARROW);
10778 }
10779}
10780
10781void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10782 bool bNew = false;
10783 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10784 // Dialog
10785 g_pMarkInfoDialog = new MarkInfoDlg(this);
10786 bNew = true;
10787 }
10788
10789 if (1 /*g_bresponsive*/) {
10790 wxSize canvas_size = GetSize();
10791
10792 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10793 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10794
10795 g_pMarkInfoDialog->Layout();
10796
10797 wxPoint canvas_pos = GetPosition();
10798 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10799
10800 bool newFit = false;
10801 if (canvas_size.x < fitted_size.x) {
10802 fitted_size.x = canvas_size.x - 40;
10803 if (canvas_size.y < fitted_size.y)
10804 fitted_size.y -= 40; // scrollbar added
10805 }
10806 if (canvas_size.y < fitted_size.y) {
10807 fitted_size.y = canvas_size.y - 40;
10808 if (canvas_size.x < fitted_size.x)
10809 fitted_size.x -= 40; // scrollbar added
10810 }
10811
10812 if (newFit) {
10813 g_pMarkInfoDialog->SetSize(fitted_size);
10814 g_pMarkInfoDialog->Centre();
10815 }
10816 }
10817
10818 markPoint->m_bRPIsBeingEdited = false;
10819
10820 wxString title_base = _("Mark Properties");
10821 if (markPoint->m_bIsInRoute) {
10822 title_base = _("Waypoint Properties");
10823 }
10824 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10825 g_pMarkInfoDialog->UpdateProperties();
10826 if (markPoint->m_bIsInLayer) {
10827 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10828 GetLayerName(markPoint->m_LayerID)));
10829 g_pMarkInfoDialog->SetDialogTitle(caption);
10830 } else
10831 g_pMarkInfoDialog->SetDialogTitle(title_base);
10832
10833 g_pMarkInfoDialog->Show();
10834 g_pMarkInfoDialog->Raise();
10835 g_pMarkInfoDialog->InitialFocus();
10836 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10837}
10838
10839void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10840 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10841 pRoutePropDialog->SetRouteAndUpdate(selected);
10842 // pNew->UpdateProperties();
10843 pRoutePropDialog->Show();
10844 pRoutePropDialog->Raise();
10845 return;
10846 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10847 this); // There is one global instance of the RouteProp Dialog
10848
10849 if (g_bresponsive) {
10850 wxSize canvas_size = GetSize();
10851 wxPoint canvas_pos = GetPosition();
10852 wxSize fitted_size = pRoutePropDialog->GetSize();
10853 ;
10854
10855 if (canvas_size.x < fitted_size.x) {
10856 fitted_size.x = canvas_size.x;
10857 if (canvas_size.y < fitted_size.y)
10858 fitted_size.y -= 20; // scrollbar added
10859 }
10860 if (canvas_size.y < fitted_size.y) {
10861 fitted_size.y = canvas_size.y;
10862 if (canvas_size.x < fitted_size.x)
10863 fitted_size.x -= 20; // scrollbar added
10864 }
10865
10866 pRoutePropDialog->SetSize(fitted_size);
10867 pRoutePropDialog->Centre();
10868
10869 // int xp = (canvas_size.x - fitted_size.x)/2;
10870 // int yp = (canvas_size.y - fitted_size.y)/2;
10871
10872 wxPoint xxp = ClientToScreen(canvas_pos);
10873 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10874 }
10875
10876 pRoutePropDialog->SetRouteAndUpdate(selected);
10877
10878 pRoutePropDialog->Show();
10879
10880 Refresh(false);
10881}
10882
10883void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10884 pTrackPropDialog = TrackPropDlg::getInstance(
10885 this); // There is one global instance of the RouteProp Dialog
10886
10887 pTrackPropDialog->SetTrackAndUpdate(selected);
10889
10890 pTrackPropDialog->Show();
10891
10892 Refresh(false);
10893}
10894
10895void pupHandler_PasteWaypoint() {
10896 Kml kml;
10897
10898 int pasteBuffer = kml.ParsePasteBuffer();
10899 RoutePoint *pasted = kml.GetParsedRoutePoint();
10900 if (!pasted) return;
10901
10902 double nearby_radius_meters =
10903 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10904
10905 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10906 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10907
10908 int answer = wxID_NO;
10909 if (nearPoint && !nearPoint->m_bIsInLayer) {
10910 wxString msg;
10911 msg << _(
10912 "There is an existing waypoint at the same location as the one you are "
10913 "pasting. Would you like to merge the pasted data with it?\n\n");
10914 msg << _("Answering 'No' will create a new waypoint at the same location.");
10915 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10916 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10917 }
10918
10919 if (answer == wxID_YES) {
10920 nearPoint->SetName(pasted->GetName());
10921 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10922 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10923 pRouteManagerDialog->UpdateWptListCtrl();
10924 }
10925
10926 if (answer == wxID_NO) {
10927 RoutePoint *newPoint = new RoutePoint(pasted);
10928 newPoint->m_bIsolatedMark = true;
10929 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10930 newPoint);
10931 // pConfig->AddNewWayPoint(newPoint, -1);
10932 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10933
10934 pWayPointMan->AddRoutePoint(newPoint);
10935 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10936 pRouteManagerDialog->UpdateWptListCtrl();
10937 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10938 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10939 }
10940
10941 top_frame::Get()->InvalidateAllGL();
10942 top_frame::Get()->RefreshAllCanvas(false);
10943}
10944
10945void pupHandler_PasteRoute() {
10946 Kml kml;
10947
10948 int pasteBuffer = kml.ParsePasteBuffer();
10949 Route *pasted = kml.GetParsedRoute();
10950 if (!pasted) return;
10951
10952 double nearby_radius_meters =
10953 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10954
10955 RoutePoint *curPoint;
10956 RoutePoint *nearPoint;
10957 RoutePoint *prevPoint = NULL;
10958
10959 bool mergepoints = false;
10960 bool createNewRoute = true;
10961 int existingWaypointCounter = 0;
10962
10963 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10964 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10965 nearPoint = pWayPointMan->GetNearbyWaypoint(
10966 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10967 if (nearPoint) {
10968 mergepoints = true;
10969 existingWaypointCounter++;
10970 // Small hack here to avoid both extending RoutePoint and repeating all
10971 // the GetNearbyWaypoint calculations. Use existin data field in
10972 // RoutePoint as temporary storage.
10973 curPoint->m_bPtIsSelected = true;
10974 }
10975 }
10976
10977 int answer = wxID_NO;
10978 if (mergepoints) {
10979 wxString msg;
10980 msg << _(
10981 "There are existing waypoints at the same location as some of the ones "
10982 "you are pasting. Would you like to just merge the pasted data into "
10983 "them?\n\n");
10984 msg << _("Answering 'No' will create all new waypoints for this route.");
10985 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10986 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10987
10988 if (answer == wxID_CANCEL) {
10989 return;
10990 }
10991 }
10992
10993 // If all waypoints exist since before, and a route with the same name, we
10994 // don't create a new route.
10995 if (mergepoints && answer == wxID_YES &&
10996 existingWaypointCounter == pasted->GetnPoints()) {
10997 for (Route *proute : *pRouteList) {
10998 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10999 createNewRoute = false;
11000 break;
11001 }
11002 }
11003 }
11004
11005 Route *newRoute = 0;
11006 RoutePoint *newPoint = 0;
11007
11008 if (createNewRoute) {
11009 newRoute = new Route();
11010 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11011 }
11012
11013 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11014 curPoint = pasted->GetPoint(i);
11015 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11016 curPoint->m_bPtIsSelected = false;
11017 newPoint = pWayPointMan->GetNearbyWaypoint(
11018 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11019 newPoint->SetName(curPoint->GetName());
11020 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11021
11022 if (createNewRoute) newRoute->AddPoint(newPoint);
11023 } else {
11024 curPoint->m_bPtIsSelected = false;
11025
11026 newPoint = new RoutePoint(curPoint);
11027 newPoint->m_bIsolatedMark = false;
11028 newPoint->SetIconName("circle");
11029 newPoint->m_bIsVisible = true;
11030 newPoint->m_bShowName = false;
11031 newPoint->SetShared(false);
11032
11033 newRoute->AddPoint(newPoint);
11034 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11035 newPoint);
11036 // pConfig->AddNewWayPoint(newPoint, -1);
11037 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11038 pWayPointMan->AddRoutePoint(newPoint);
11039 }
11040 if (i > 1 && createNewRoute)
11041 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11042 curPoint->m_lat, curPoint->m_lon,
11043 prevPoint, newPoint, newRoute);
11044 prevPoint = newPoint;
11045 }
11046
11047 if (createNewRoute) {
11048 pRouteList->push_back(newRoute);
11049 // pConfig->AddNewRoute(newRoute); // use auto next num
11050 NavObj_dB::GetInstance().InsertRoute(newRoute);
11051
11052 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11053 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11054 }
11055
11056 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11057 pRouteManagerDialog->UpdateRouteListCtrl();
11058 pRouteManagerDialog->UpdateWptListCtrl();
11059 }
11060 top_frame::Get()->InvalidateAllGL();
11061 top_frame::Get()->RefreshAllCanvas(false);
11062 }
11063 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11064 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11065}
11066
11067void pupHandler_PasteTrack() {
11068 Kml kml;
11069
11070 int pasteBuffer = kml.ParsePasteBuffer();
11071 Track *pasted = kml.GetParsedTrack();
11072 if (!pasted) return;
11073
11074 TrackPoint *curPoint;
11075
11076 Track *newTrack = new Track();
11077 TrackPoint *newPoint;
11078 TrackPoint *prevPoint = NULL;
11079
11080 newTrack->SetName(pasted->GetName());
11081
11082 for (int i = 0; i < pasted->GetnPoints(); i++) {
11083 curPoint = pasted->GetPoint(i);
11084
11085 newPoint = new TrackPoint(curPoint);
11086
11087 wxDateTime now = wxDateTime::Now();
11088 newPoint->SetCreateTime(curPoint->GetCreateTime());
11089
11090 newTrack->AddPoint(newPoint);
11091
11092 if (prevPoint)
11093 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11094 newPoint->m_lat, newPoint->m_lon,
11095 prevPoint, newPoint, newTrack);
11096
11097 prevPoint = newPoint;
11098 }
11099
11100 g_TrackList.push_back(newTrack);
11101 // pConfig->AddNewTrack(newTrack);
11102 NavObj_dB::GetInstance().InsertTrack(newTrack);
11103
11104 top_frame::Get()->InvalidateAllGL();
11105 top_frame::Get()->RefreshAllCanvas(false);
11106}
11107
11108bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11109 wxJSONValue v;
11110 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11111 v["CursorPosition_x"] = x;
11112 v["CursorPosition_y"] = y;
11113 // Send a limited set of selection types depending on what is
11114 // found under the mouse point.
11115 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11116 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11117 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11118
11119 wxJSONWriter w;
11120 wxString out;
11121 w.Write(v, out);
11122 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11123
11124 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11125
11126#if 0
11127#define SELTYPE_UNKNOWN 0x0001
11128#define SELTYPE_ROUTEPOINT 0x0002
11129#define SELTYPE_ROUTESEGMENT 0x0004
11130#define SELTYPE_TIDEPOINT 0x0008
11131#define SELTYPE_CURRENTPOINT 0x0010
11132#define SELTYPE_ROUTECREATE 0x0020
11133#define SELTYPE_AISTARGET 0x0040
11134#define SELTYPE_MARKPOINT 0x0080
11135#define SELTYPE_TRACKSEGMENT 0x0100
11136#define SELTYPE_DRAGHANDLE 0x0200
11137#endif
11138
11139 if (g_bhide_context_menus) return true;
11140 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11141 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11142 m_pIDXCandidate, m_nmea_log);
11143
11144 Connect(
11145 wxEVT_COMMAND_MENU_SELECTED,
11146 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11147
11148#ifdef __WXGTK__
11149 // Funny requirement here for gtk, to clear the menu trigger event
11150 // TODO
11151 // Causes a slight "flasH" of the menu,
11152 if (m_inLongPress) {
11153 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11154 m_inLongPress = false;
11155 }
11156#endif
11157
11158 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11159
11160 Disconnect(
11161 wxEVT_COMMAND_MENU_SELECTED,
11162 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11163
11164 delete m_canvasMenu;
11165 m_canvasMenu = NULL;
11166
11167#ifdef __WXQT__
11168 // gFrame->SurfaceToolbar();
11169 // g_MainToolbar->Raise();
11170#endif
11171
11172 return true;
11173}
11174
11175void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11176 // Pass menu events from the canvas to the menu handler
11177 // This is necessarily in ChartCanvas since that is the menu's parent.
11178 if (m_canvasMenu) {
11179 m_canvasMenu->PopupMenuHandler(event);
11180 }
11181 return;
11182}
11183
11184void ChartCanvas::StartRoute() {
11185 // Do not allow more than one canvas to create a route at one time.
11186 if (g_brouteCreating) return;
11187
11188 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11189
11190 g_brouteCreating = true;
11191 m_routeState = 1;
11192 m_bDrawingRoute = false;
11193 SetCursor(*pCursorPencil);
11194 // SetCanvasToolbarItemState(ID_ROUTE, true);
11195 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11196
11197 HideGlobalToolbar();
11198
11199#ifdef __ANDROID__
11200 androidSetRouteAnnunciator(true);
11201#endif
11202}
11203
11204wxString ChartCanvas::FinishRoute() {
11205 m_routeState = 0;
11206 m_prev_pMousePoint = NULL;
11207 m_bDrawingRoute = false;
11208 wxString rv = "";
11209 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11210
11211 // SetCanvasToolbarItemState(ID_ROUTE, false);
11212 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11213#ifdef __ANDROID__
11214 androidSetRouteAnnunciator(false);
11215#endif
11216
11217 SetCursor(*pCursorArrow);
11218
11219 if (m_pMouseRoute) {
11220 if (m_bAppendingRoute) {
11221 // pConfig->UpdateRoute(m_pMouseRoute);
11222 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11223 } else {
11224 if (m_pMouseRoute->GetnPoints() > 1) {
11225 // pConfig->AddNewRoute(m_pMouseRoute);
11226 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11227 } else {
11228 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11229 m_pMouseRoute = NULL;
11230 }
11231 }
11232 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11233
11234 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11235 (pRoutePropDialog->IsShown())) {
11236 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11237 }
11238
11239 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11240 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11241 pRouteManagerDialog->UpdateRouteListCtrl();
11242 }
11243 }
11244 m_bAppendingRoute = false;
11245 m_pMouseRoute = NULL;
11246
11247 m_pSelectedRoute = NULL;
11248
11249 undo->InvalidateUndo();
11250 top_frame::Get()->RefreshAllCanvas(true);
11251
11252 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11253
11254 ShowGlobalToolbar();
11255
11256 g_brouteCreating = false;
11257
11258 return rv;
11259}
11260
11261void ChartCanvas::HideGlobalToolbar() {
11262 if (m_canvasIndex == 0) {
11263 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11264 }
11265}
11266
11267void ChartCanvas::ShowGlobalToolbar() {
11268 if (m_canvasIndex == 0) {
11269 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11270 }
11271}
11272
11273void ChartCanvas::ShowAISTargetList() {
11274 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11275 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11276 }
11277
11278 g_pAISTargetList->UpdateAISTargetList();
11279}
11280
11281void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11282 if (!m_bShowOutlines) return;
11283
11284 if (!ChartData) return;
11285
11286 int nEntry = ChartData->GetChartTableEntries();
11287
11288 for (int i = 0; i < nEntry; i++) {
11289 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11290
11291 // Check to see if the candidate chart is in the currently active group
11292 bool b_group_draw = false;
11293 if (m_groupIndex > 0) {
11294 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11295 int index = pt->GetGroupArray()[ig];
11296 if (m_groupIndex == index) {
11297 b_group_draw = true;
11298 break;
11299 }
11300 }
11301 } else
11302 b_group_draw = true;
11303
11304 if (b_group_draw) RenderChartOutline(dc, i, vp);
11305 }
11306
11307 // On CM93 Composite Charts, draw the outlines of the next smaller
11308 // scale cell
11309 cm93compchart *pcm93 = NULL;
11310 if (VPoint.b_quilt) {
11311 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11312 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11313 pcm93 = (cm93compchart *)pch;
11314 break;
11315 }
11316 } else if (m_singleChart &&
11317 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11318 pcm93 = (cm93compchart *)m_singleChart;
11319
11320 if (pcm93) {
11321 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11322 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11323
11324 if (zoom_factor > 8.0) {
11325 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11326 dc.SetPen(mPen);
11327 } else {
11328 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11329 dc.SetPen(mPen);
11330 }
11331
11332 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11333 }
11334}
11335
11336void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11337#ifdef ocpnUSE_GL
11338 if (g_bopengl && m_glcc) {
11339 /* opengl version specially optimized */
11340 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11341 return;
11342 }
11343#endif
11344
11345 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11346 if (!ChartData->IsChartAvailable(dbIndex)) return;
11347 }
11348
11349 float plylat, plylon;
11350 float plylat1, plylon1;
11351
11352 int pixx, pixy, pixx1, pixy1;
11353
11354 LLBBox box;
11355 ChartData->GetDBBoundingBox(dbIndex, box);
11356
11357 // Don't draw an outline in the case where the chart covers the entire world
11358 // */
11359 if (box.GetLonRange() == 360) return;
11360
11361 double lon_bias = 0;
11362 // chart is outside of viewport lat/lon bounding box
11363 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11364
11365 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11366
11367 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11368 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11369
11370 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11371 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11372
11373 else
11374 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11375
11376 // Are there any aux ply entries?
11377 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11378 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11379 {
11380 wxPoint r, r1;
11381
11382 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11383 plylon += lon_bias;
11384
11385 GetCanvasPointPix(plylat, plylon, &r);
11386 pixx = r.x;
11387 pixy = r.y;
11388
11389 for (int i = 0; i < nPly - 1; i++) {
11390 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11391 plylon1 += lon_bias;
11392
11393 GetCanvasPointPix(plylat1, plylon1, &r1);
11394 pixx1 = r1.x;
11395 pixy1 = r1.y;
11396
11397 int pixxs1 = pixx1;
11398 int pixys1 = pixy1;
11399
11400 bool b_skip = false;
11401
11402 if (vp.chart_scale > 5e7) {
11403 // calculate projected distance between these two points in meters
11404 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11405 pow((double)(pixy1 - pixy), 2)) /
11406 vp.view_scale_ppm;
11407
11408 if (dist > 0.0) {
11409 // calculate GC distance between these two points in meters
11410 double distgc =
11411 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11412
11413 // If the distances are nonsense, it means that the scale is very
11414 // small and the segment wrapped the world So skip it....
11415 // TODO improve this to draw two segments
11416 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11417 b_skip = true;
11418 } else
11419 b_skip = true;
11420 }
11421
11422 ClipResult res = cohen_sutherland_line_clip_i(
11423 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11424 if (res != Invisible && !b_skip)
11425 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11426
11427 plylat = plylat1;
11428 plylon = plylon1;
11429 pixx = pixxs1;
11430 pixy = pixys1;
11431 }
11432
11433 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11434 plylon1 += lon_bias;
11435
11436 GetCanvasPointPix(plylat1, plylon1, &r1);
11437 pixx1 = r1.x;
11438 pixy1 = r1.y;
11439
11440 ClipResult res = cohen_sutherland_line_clip_i(
11441 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11442 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11443 }
11444
11445 else // Use Aux PlyPoints
11446 {
11447 wxPoint r, r1;
11448
11449 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11450 for (int j = 0; j < nAuxPlyEntries; j++) {
11451 int nAuxPly =
11452 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11453 GetCanvasPointPix(plylat, plylon, &r);
11454 pixx = r.x;
11455 pixy = r.y;
11456
11457 for (int i = 0; i < nAuxPly - 1; i++) {
11458 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11459
11460 GetCanvasPointPix(plylat1, plylon1, &r1);
11461 pixx1 = r1.x;
11462 pixy1 = r1.y;
11463
11464 int pixxs1 = pixx1;
11465 int pixys1 = pixy1;
11466
11467 bool b_skip = false;
11468
11469 if (vp.chart_scale > 5e7) {
11470 // calculate projected distance between these two points in meters
11471 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11472 ((pixy1 - pixy) * (pixy1 - pixy))) /
11473 vp.view_scale_ppm;
11474 if (dist > 0.0) {
11475 // calculate GC distance between these two points in meters
11476 double distgc =
11477 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11478
11479 // If the distances are nonsense, it means that the scale is very
11480 // small and the segment wrapped the world So skip it....
11481 // TODO improve this to draw two segments
11482 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11483 b_skip = true;
11484 } else
11485 b_skip = true;
11486 }
11487
11488 ClipResult res = cohen_sutherland_line_clip_i(
11489 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11490 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11491
11492 plylat = plylat1;
11493 plylon = plylon1;
11494 pixx = pixxs1;
11495 pixy = pixys1;
11496 }
11497
11498 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11499 GetCanvasPointPix(plylat1, plylon1, &r1);
11500 pixx1 = r1.x;
11501 pixy1 = r1.y;
11502
11503 ClipResult res = cohen_sutherland_line_clip_i(
11504 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11505 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11506 }
11507 }
11508}
11509
11510static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11511 const wxArrayString &legend) {
11512 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11513
11514 int pointsize = dFont->GetPointSize();
11515 pointsize /= OCPN_GetWinDIPScaleFactor();
11516
11517 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11518 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11519 false, dFont->GetFaceName());
11520
11521 dc.SetFont(*psRLI_font);
11522
11523 int h = 0;
11524 int w = 0;
11525 int hl, wl;
11526
11527 int xp, yp;
11528 int hilite_offset = 3;
11529
11530 for (wxString line : legend) {
11531#ifdef __WXMAC__
11532 wxScreenDC sdc;
11533 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11534#else
11535 dc.GetTextExtent(line, &wl, &hl);
11536 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11537 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11538#endif
11539 h += hl;
11540 w = wxMax(w, wl);
11541 }
11542 w += (hl / 2); // Add a little right pad
11543
11544 xp = ref_point.x - w;
11545 yp = ref_point.y;
11546 yp += hilite_offset;
11547
11548 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11549
11550 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11551 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11552
11553 for (wxString line : legend) {
11554 dc.DrawText(line, xp, yp);
11555 yp += hl;
11556 }
11557}
11558
11559void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11560 if (!g_bAllowShipToActive) return;
11561
11562 Route *rt = g_pRouteMan->GetpActiveRoute();
11563 if (!rt) return;
11564
11565 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11566 wxPoint2DDouble pa, pb;
11568 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11569
11570 // set pen
11571 int width =
11572 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11573 if (rt->m_width != wxPENSTYLE_INVALID)
11574 width = rt->m_width; // set route pen style if any
11575 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11576 g_shipToActiveStyle, 5)]; // get setting pen style
11577 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11578 wxColour color =
11579 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11580 : // set setting route pen color
11581 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11582 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11583
11584 dc.SetPen(*mypen);
11585 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11586
11587 if (!Use_Opengl)
11588 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11589 (int)pb.m_y, GetVP(), true);
11590
11591#ifdef ocpnUSE_GL
11592 else {
11593#ifdef USE_ANDROID_GLES2
11594 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11595#else
11596 if (style != wxPENSTYLE_SOLID) {
11597 if (glChartCanvas::dash_map.find(style) !=
11598 glChartCanvas::dash_map.end()) {
11599 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11600 dc.SetPen(*mypen);
11601 }
11602 }
11603 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11604#endif
11605
11606 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11607 (int)pb.m_x, (int)pb.m_y, GetVP());
11608 }
11609#endif
11610 }
11611}
11612
11613void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11614 Route *route = 0;
11615 if (m_routeState >= 2) route = m_pMouseRoute;
11616 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11617 route = m_pMeasureRoute;
11618
11619 if (!route) return;
11620
11621 // Validate route pointer
11622 if (!g_pRouteMan->IsRouteValid(route)) return;
11623
11624 double render_lat = m_cursor_lat;
11625 double render_lon = m_cursor_lon;
11626
11627 int np = route->GetnPoints();
11628 if (np) {
11629 if (g_btouch && (np > 1)) np--;
11630 RoutePoint rp = route->GetPoint(np);
11631 render_lat = rp.m_lat;
11632 render_lon = rp.m_lon;
11633 }
11634
11635 double rhumbBearing, rhumbDist;
11636 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11637 &rhumbBearing, &rhumbDist);
11638 double brg = rhumbBearing;
11639 double dist = rhumbDist;
11640
11641 // Skip GreatCircle rubberbanding on touch devices.
11642 if (!g_btouch) {
11643 double gcBearing, gcBearing2, gcDist;
11644 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11645 m_cursor_lat, &gcDist, &gcBearing,
11646 &gcBearing2);
11647 double gcDistm = gcDist / 1852.0;
11648
11649 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11650 rhumbBearing = 90.;
11651
11652 wxPoint destPoint, lastPoint;
11653
11654 route->m_NextLegGreatCircle = false;
11655 int milesDiff = rhumbDist - gcDistm;
11656 if (milesDiff > 1) {
11657 brg = gcBearing;
11658 dist = gcDistm;
11659 route->m_NextLegGreatCircle = true;
11660 }
11661
11662 // FIXME (MacOS, the first segment is rendered wrong)
11663 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11664 &lastPoint);
11665
11666 if (route->m_NextLegGreatCircle) {
11667 for (int i = 1; i <= milesDiff; i++) {
11668 double p = (double)i * (1.0 / (double)milesDiff);
11669 double pLat, pLon;
11670 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11671 &pLon, &pLat, &gcBearing2);
11672 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11673 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11674 false);
11675 lastPoint = destPoint;
11676 }
11677 } else {
11678 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11679 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11680 false);
11681 if (m_bMeasure_DistCircle) {
11682 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11683 powf((float)(r_rband.y - lastPoint.y), 2));
11684
11685 dc.SetPen(*g_pRouteMan->GetRoutePen());
11686 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11687 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11688 }
11689 }
11690 }
11691 }
11692
11693 wxString routeInfo;
11694 wxArrayString infoArray;
11695 double varBrg = 0;
11696 if (g_bShowTrue)
11697 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11698 0x00B0);
11699
11700 if (g_bShowMag) {
11701 double latAverage = (m_cursor_lat + render_lat) / 2;
11702 double lonAverage = (m_cursor_lon + render_lon) / 2;
11703 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11704
11705 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11706 (int)varBrg, 0x00B0);
11707 }
11708 routeInfo << " " << FormatDistanceAdaptive(dist);
11709 infoArray.Add(routeInfo);
11710 routeInfo.Clear();
11711
11712 // To make it easier to use a route as a bearing on a charted object add for
11713 // the first leg also the reverse bearing.
11714 if (np == 1) {
11715 routeInfo << "Reverse: ";
11716 if (g_bShowTrue)
11717 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11718 (int)(brg + 180.) % 360, 0x00B0);
11719 if (g_bShowMag)
11720 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11721 (int)(varBrg + 180.) % 360, 0x00B0);
11722 infoArray.Add(routeInfo);
11723 routeInfo.Clear();
11724 }
11725
11726 wxString s0;
11727 if (!route->m_bIsInLayer)
11728 s0.Append(_("Route") + ": ");
11729 else
11730 s0.Append(_("Layer Route: "));
11731
11732 double disp_length = route->m_route_length;
11733 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11734 s0 += FormatDistanceAdaptive(disp_length);
11735
11736 infoArray.Add(s0);
11737 routeInfo.Clear();
11738
11739 RouteLegInfo(dc, r_rband, infoArray);
11740
11741 m_brepaint_piano = true;
11742}
11743
11744void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11745 if (!m_bShowVisibleSectors) return;
11746
11747 if (g_bDeferredInitDone) {
11748 // need to re-evaluate sectors?
11749 double rhumbBearing, rhumbDist;
11750 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11751 &rhumbBearing, &rhumbDist);
11752
11753 if (rhumbDist > 0.05) // miles
11754 {
11755 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11756 m_sectorlegsVisible);
11757 m_sector_glat = gLat;
11758 m_sector_glon = gLon;
11759 }
11760 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11761 }
11762}
11763
11764void ChartCanvas::WarpPointerDeferred(int x, int y) {
11765 warp_x = x;
11766 warp_y = y;
11767 warp_flag = true;
11768}
11769
11770int s_msg;
11771
11772void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11773 if (!ps52plib) return;
11774
11775 if (VPoint.b_quilt) { // quilted
11776 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11777
11778 if (m_pQuilt->IsQuiltVector()) {
11779 if (ps52plib->GetStateHash() != m_s52StateHash) {
11780 UpdateS52State();
11781 m_s52StateHash = ps52plib->GetStateHash();
11782 }
11783 }
11784 } else {
11785 if (ps52plib->GetStateHash() != m_s52StateHash) {
11786 UpdateS52State();
11787 m_s52StateHash = ps52plib->GetStateHash();
11788 }
11789 }
11790
11791 // Plugin charts
11792 bool bSendPlibState = true;
11793 if (VPoint.b_quilt) { // quilted
11794 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11795 }
11796
11797 if (bSendPlibState) {
11798 wxJSONValue v;
11799 v["OpenCPN Version Major"] = VERSION_MAJOR;
11800 v["OpenCPN Version Minor"] = VERSION_MINOR;
11801 v["OpenCPN Version Patch"] = VERSION_PATCH;
11802 v["OpenCPN Version Date"] = VERSION_DATE;
11803 v["OpenCPN Version Full"] = VERSION_FULL;
11804
11805 // S52PLIB state
11806 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11807 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11808 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11809 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11810 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11811 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11812 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11813
11814 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11815
11816 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11817 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11818
11819 // Global S52 options
11820
11821 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11822 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11823 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11824 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11825 ps52plib->m_bShowS57ImportantTextOnly;
11826 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11827 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11828 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11829 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11830 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11831
11832 // Some global GUI parameters, for completeness
11833 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11834 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11835 v["OpenCPN Scale Factor Exp"] =
11836 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11837 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11838
11839 wxJSONWriter w;
11840 wxString out;
11841 w.Write(v, out);
11842
11843 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11844 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11845 g_lastS52PLIBPluginMessage = out;
11846 }
11847 }
11848}
11849int spaint;
11850int s_in_update;
11851void ChartCanvas::OnPaint(wxPaintEvent &event) {
11852 wxPaintDC dc(this);
11853
11854 // GetToolbar()->Show( m_bToolbarEnable );
11855
11856 // Paint updates may have been externally disabled (temporarily, to avoid
11857 // Yield() recursion performance loss) It is important that the wxPaintDC is
11858 // built, even if we elect to not process this paint message. Otherwise, the
11859 // paint message may not be removed from the message queue, esp on Windows.
11860 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11861
11862 if (!m_b_paint_enable) {
11863 return;
11864 }
11865
11866 // If necessary, reconfigure the S52 PLIB
11868
11869#ifdef ocpnUSE_GL
11870 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11871
11872 if (m_glcc && g_bopengl) {
11873 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11874 s_in_update++;
11875 m_glcc->Update();
11876 s_in_update--;
11877 }
11878
11879 return;
11880 }
11881#endif
11882
11883 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11884
11885 wxRegion ru = GetUpdateRegion();
11886
11887 int rx, ry, rwidth, rheight;
11888 ru.GetBox(rx, ry, rwidth, rheight);
11889
11890#ifdef ocpnUSE_DIBSECTION
11891 ocpnMemDC temp_dc;
11892#else
11893 wxMemoryDC temp_dc;
11894#endif
11895
11896 long height = GetVP().pix_height;
11897
11898#ifdef __WXMAC__
11899 // On OS X we have to explicitly extend the region for the piano area
11900 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11901 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11902 height += m_Piano->GetHeight();
11903#endif // __WXMAC__
11904 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11905
11906 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11907 if (pthumbwin) {
11908 int thumbx, thumby, thumbsx, thumbsy;
11909 pthumbwin->GetPosition(&thumbx, &thumby);
11910 pthumbwin->GetSize(&thumbsx, &thumbsy);
11911 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11912
11913 if (pthumbwin->IsShown()) {
11914 rgn_chart.Subtract(rgn_thumbwin);
11915 ru.Subtract(rgn_thumbwin);
11916 }
11917 }
11918
11919 // subtract the chart bar if it isn't transparent, and determine if we need to
11920 // paint it
11921 wxRegion rgn_blit = ru;
11922 if (g_bShowChartBar) {
11923 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11924 GetClientSize().x, m_Piano->GetHeight());
11925
11926 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11927 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11928 if (style->chartStatusWindowTransparent)
11929 m_brepaint_piano = true;
11930 else
11931 ru.Subtract(chart_bar_rect);
11932 }
11933 }
11934
11935 if (m_Compass && m_Compass->IsShown()) {
11936 wxRect compassRect = m_Compass->GetRect();
11937 if (ru.Contains(compassRect) != wxOutRegion) {
11938 ru.Subtract(compassRect);
11939 }
11940 }
11941
11942 if (m_notification_button) {
11943 wxRect noteRect = m_notification_button->GetRect();
11944 if (ru.Contains(noteRect) != wxOutRegion) {
11945 ru.Subtract(noteRect);
11946 }
11947 }
11948
11949 // Is this viewpoint the same as the previously painted one?
11950 bool b_newview = true;
11951
11952 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11953 (m_cache_vp.rotation == VPoint.rotation) &&
11954 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11955 m_cache_vp.IsValid()) {
11956 b_newview = false;
11957 }
11958
11959 // If the ViewPort is skewed or rotated, we may be able to use the cached
11960 // rotated bitmap.
11961 bool b_rcache_ok = false;
11962 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11963 b_rcache_ok = !b_newview;
11964
11965 // Make a special VP
11966 if (VPoint.b_MercatorProjectionOverride)
11967 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11968 ViewPort svp = VPoint;
11969
11970 svp.pix_width = svp.rv_rect.width;
11971 svp.pix_height = svp.rv_rect.height;
11972
11973 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11974 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11975 // VPoint.rv_rect.height);
11976
11977 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11978
11979 // If we are going to use the cached rotated image, there is no need to fetch
11980 // any chart data and this will do it...
11981 if (b_rcache_ok) chart_get_region.Clear();
11982
11983 // Blit pan acceleration
11984 if (VPoint.b_quilt) // quilted
11985 {
11986 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11987
11988 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11989
11990 bool busy = false;
11991 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11992 m_cache_vp.rotation != VPoint.rotation)) {
11993 AbstractPlatform::ShowBusySpinner();
11994 busy = true;
11995 }
11996
11997 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11998 (m_working_bm.GetHeight() != svp.pix_height))
11999 m_working_bm.Create(svp.pix_width, svp.pix_height,
12000 -1); // make sure the target is big enoug
12001
12002 if (fabs(VPoint.rotation) < 0.01) {
12003 bool b_save = true;
12004
12005 if (g_SencThreadManager) {
12006 if (g_SencThreadManager->GetJobCount()) {
12007 b_save = false;
12008 m_cache_vp.Invalidate();
12009 }
12010 }
12011
12012 // If the saved wxBitmap from last OnPaint is useable
12013 // calculate the blit parameters
12014
12015 // We can only do screen blit painting if subsequent ViewPorts differ by
12016 // whole pixels So, in small scale bFollow mode, force the full screen
12017 // render. This seems a hack....There may be better logic here.....
12018
12019 // if(m_bFollow)
12020 // b_save = false;
12021
12022 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12023 if (b_newview) {
12024 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12025 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12026
12027 int dy = c_new.y - c_old.y;
12028 int dx = c_new.x - c_old.x;
12029
12030 // printf("In OnPaint Trying Blit dx: %d
12031 // dy:%d\n\n", dx, dy);
12032
12033 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12034 if (dx || dy) {
12035 // Blit the reuseable portion of the cached wxBitmap to a working
12036 // bitmap
12037 temp_dc.SelectObject(m_working_bm);
12038
12039 wxMemoryDC cache_dc;
12040 cache_dc.SelectObject(m_cached_chart_bm);
12041
12042 if (dy > 0) {
12043 if (dx > 0) {
12044 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12045 VPoint.pix_height - dy, &cache_dc, dx, dy);
12046 } else {
12047 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12048 VPoint.pix_height - dy, &cache_dc, 0, dy);
12049 }
12050
12051 } else {
12052 if (dx > 0) {
12053 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12054 VPoint.pix_height + dy, &cache_dc, dx, 0);
12055 } else {
12056 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12057 VPoint.pix_height + dy, &cache_dc, 0, 0);
12058 }
12059 }
12060
12061 OCPNRegion update_region;
12062 if (dy) {
12063 if (dy > 0)
12064 update_region.Union(
12065 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12066 else
12067 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12068 }
12069
12070 if (dx) {
12071 if (dx > 0)
12072 update_region.Union(
12073 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12074 else
12075 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12076 }
12077
12078 // Render the new region
12079 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12080 update_region);
12081 cache_dc.SelectObject(wxNullBitmap);
12082 } else {
12083 // No sensible (dx, dy) change in the view, so use the cached
12084 // member bitmap
12085 temp_dc.SelectObject(m_cached_chart_bm);
12086 b_save = false;
12087 }
12088 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12089
12090 } else // not blitable
12091 {
12092 temp_dc.SelectObject(m_working_bm);
12093 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12094 chart_get_region);
12095 }
12096 } else {
12097 // No change in the view, so use the cached member bitmap2
12098 temp_dc.SelectObject(m_cached_chart_bm);
12099 b_save = false;
12100 }
12101 } else // cached bitmap is not yet valid
12102 {
12103 temp_dc.SelectObject(m_working_bm);
12104 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12105 chart_get_region);
12106 }
12107
12108 // Save the fully rendered quilt image as a wxBitmap member of this class
12109 if (b_save) {
12110 // if((m_cached_chart_bm.GetWidth() !=
12111 // svp.pix_width) ||
12112 // (m_cached_chart_bm.GetHeight() !=
12113 // svp.pix_height))
12114 // m_cached_chart_bm.Create(svp.pix_width,
12115 // svp.pix_height, -1); // target wxBitmap
12116 // is big enough
12117 wxMemoryDC scratch_dc_0;
12118 scratch_dc_0.SelectObject(m_cached_chart_bm);
12119 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12120
12121 scratch_dc_0.SelectObject(wxNullBitmap);
12122
12123 m_bm_cache_vp =
12124 VPoint; // save the ViewPort associated with the cached wxBitmap
12125 }
12126 }
12127
12128 else // quilted, rotated
12129 {
12130 temp_dc.SelectObject(m_working_bm);
12131 OCPNRegion chart_get_all_region(
12132 wxRect(0, 0, svp.pix_width, svp.pix_height));
12133 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12134 chart_get_all_region);
12135 }
12136
12137 AbstractPlatform::HideBusySpinner();
12138
12139 }
12140
12141 else // not quilted
12142 {
12143 if (!m_singleChart) {
12144 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12145 dc.Clear();
12146 return;
12147 }
12148
12149 if (!chart_get_region.IsEmpty()) {
12150 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12151 }
12152 }
12153
12154 if (temp_dc.IsOk()) {
12155 // Arrange to render the World Chart vector data behind the rendered
12156 // current chart so that uncovered canvas areas show at least the world
12157 // chart.
12158 OCPNRegion chartValidRegion;
12159 if (!VPoint.b_quilt) {
12160 // Make a region covering the current chart on the canvas
12161
12162 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12163 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12164 else {
12165 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12166 // require that the viewport passed here have pix_width and pix_height
12167 // set to the actual display, not the virtual (rv_rect) sizes
12168 // (the vector calculations require the virtual sizes in svp)
12169
12170 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12171 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12172 }
12173 } else
12174 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12175
12176 temp_dc.DestroyClippingRegion();
12177
12178 // Copy current chart region
12179 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12180
12181 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12182
12183 if (!backgroundRegion.IsEmpty()) {
12184 // Draw the Background Chart only in the areas NOT covered by the
12185 // current chart view
12186
12187 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12188 clipping regions with more than 1 rectangle so... */
12189 wxColour water = pWorldBackgroundChart->water;
12190 if (water.IsOk()) {
12191 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12192 temp_dc.SetBrush(wxBrush(water));
12193 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12194 while (upd.HaveRects()) {
12195 wxRect rect = upd.GetRect();
12196 temp_dc.DrawRectangle(rect);
12197 upd.NextRect();
12198 }
12199 }
12200 // Associate with temp_dc
12201 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12202 temp_dc.SetDeviceClippingRegion(*clip_region);
12203 delete clip_region;
12204
12205 ocpnDC bgdc(temp_dc);
12206 double r = VPoint.rotation;
12207 SetVPRotation(VPoint.skew);
12208
12209 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12210 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12211
12212 SetVPRotation(r);
12213 }
12214 } // temp_dc.IsOk();
12215
12216 wxMemoryDC *pChartDC = &temp_dc;
12217 wxMemoryDC rotd_dc;
12218
12219 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12220 // Can we use the current rotated image cache?
12221 if (!b_rcache_ok) {
12222#ifdef __WXMSW__
12223 wxMemoryDC tbase_dc;
12224 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12225 tbase_dc.SelectObject(bm_base);
12226 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12227 tbase_dc.SelectObject(wxNullBitmap);
12228#else
12229 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12230#endif
12231
12232 wxImage base_image;
12233 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12234
12235 // Use a local static image rotator to improve wxWidgets code profile
12236 // Especially, on GTK the wxRound and wxRealPoint functions are very
12237 // expensive.....
12238
12239 double angle = GetVP().skew - GetVP().rotation;
12240 wxImage ri;
12241 bool b_rot_ok = false;
12242 if (base_image.IsOk()) {
12243 ViewPort rot_vp = GetVP();
12244
12245 m_b_rot_hidef = false;
12246
12247 ri = Image_Rotate(
12248 base_image, angle,
12249 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12250 m_b_rot_hidef, &m_roffset);
12251
12252 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12253 (rot_vp.rotation == VPoint.rotation) &&
12254 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12255 rot_vp.IsValid() && (ri.IsOk())) {
12256 b_rot_ok = true;
12257 }
12258 }
12259
12260 if (b_rot_ok) {
12261 delete m_prot_bm;
12262 m_prot_bm = new wxBitmap(ri);
12263 }
12264
12265 m_roffset.x += VPoint.rv_rect.x;
12266 m_roffset.y += VPoint.rv_rect.y;
12267 }
12268
12269 if (m_prot_bm && m_prot_bm->IsOk()) {
12270 rotd_dc.SelectObject(*m_prot_bm);
12271 pChartDC = &rotd_dc;
12272 } else {
12273 pChartDC = &temp_dc;
12274 m_roffset = wxPoint(0, 0);
12275 }
12276 } else { // unrotated
12277 pChartDC = &temp_dc;
12278 m_roffset = wxPoint(0, 0);
12279 }
12280
12281 wxPoint offset = m_roffset;
12282
12283 // Save the PixelCache viewpoint for next time
12284 m_cache_vp = VPoint;
12285
12286 // Set up a scratch DC for overlay objects
12287 wxMemoryDC mscratch_dc;
12288 mscratch_dc.SelectObject(*pscratch_bm);
12289
12290 mscratch_dc.ResetBoundingBox();
12291 mscratch_dc.DestroyClippingRegion();
12292 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12293
12294 // Blit the externally invalidated areas of the chart onto the scratch dc
12295 wxRegionIterator upd(rgn_blit); // get the update rect list
12296 while (upd) {
12297 wxRect rect = upd.GetRect();
12298
12299 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12300 rect.x - offset.x, rect.y - offset.y);
12301 upd++;
12302 }
12303
12304 // If multi-canvas, indicate which canvas has keyboard focus
12305 // by drawing a simple blue bar at the top.
12306 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12307 if (this == wxWindow::FindFocus()) {
12308 g_focusCanvas = this;
12309
12310 wxColour colour = GetGlobalColor("BLUE4");
12311 mscratch_dc.SetPen(wxPen(colour));
12312 mscratch_dc.SetBrush(wxBrush(colour));
12313
12314 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12315 mscratch_dc.DrawRectangle(activeRect);
12316 }
12317 }
12318
12319 // Any MBtiles?
12320 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12321 unsigned int im = stackIndexArray.size();
12322 if (VPoint.b_quilt && im > 0) {
12323 std::vector<int> tiles_to_show;
12324 for (unsigned int is = 0; is < im; is++) {
12325 const ChartTableEntry &cte =
12326 ChartData->GetChartTableEntry(stackIndexArray[is]);
12327 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12328 continue;
12329 }
12330 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12331 tiles_to_show.push_back(stackIndexArray[is]);
12332 }
12333 }
12334
12335 if (tiles_to_show.size())
12336 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12337 }
12338
12339 // May get an unexpected OnPaint call while switching display modes
12340 // Guard for that.
12341 if (!g_bopengl) {
12342 ocpnDC scratch_dc(mscratch_dc);
12343 RenderAlertMessage(mscratch_dc, GetVP());
12344 }
12345
12346#if 0
12347 // quiting?
12348 if (g_bquiting) {
12349#ifdef ocpnUSE_DIBSECTION
12350 ocpnMemDC q_dc;
12351#else
12352 wxMemoryDC q_dc;
12353#endif
12354 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12355 q_dc.SelectObject(qbm);
12356
12357 // Get a copy of the screen
12358 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12359
12360 // Draw a rectangle over the screen with a stipple brush
12361 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12362 q_dc.SetBrush(qbr);
12363 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12364
12365 // Blit back into source
12366 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12367 wxCOPY);
12368
12369 q_dc.SelectObject(wxNullBitmap);
12370 }
12371#endif
12372
12373#if 0
12374 // It is possible that this two-step method may be reuired for some platforms.
12375 // So, retain in the code base to aid recovery if necessary
12376
12377 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12378 if( VPoint.b_quilt ) {
12379 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12380 ChartBase *chart = m_pQuilt->GetRefChart();
12381 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12382
12383 // Clear the text Global declutter list
12384 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12385 if(ChPI)
12386 ChPI->ClearPLIBTextList();
12387 else{
12388 if(ps52plib)
12389 ps52plib->ClearTextList();
12390 }
12391
12392 wxMemoryDC t_dc;
12393 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12394
12395 wxColor maskBackground = wxColour(1,0,0);
12396 t_dc.SelectObject( qbm );
12397 t_dc.SetBackground(wxBrush(maskBackground));
12398 t_dc.Clear();
12399
12400 // Copy the scratch DC into the new bitmap
12401 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12402
12403 // Render the text to the new bitmap
12404 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12405 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12406
12407 // Copy the new bitmap back to the scratch dc
12408 wxRegionIterator upd_final( ru );
12409 while( upd_final ) {
12410 wxRect rect = upd_final.GetRect();
12411 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12412 upd_final++;
12413 }
12414
12415 t_dc.SelectObject( wxNullBitmap );
12416 }
12417 }
12418 }
12419#endif
12420 // Direct rendering model...
12421 if (VPoint.b_quilt) {
12422 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12423 ChartBase *chart = m_pQuilt->GetRefChart();
12424 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12425 // Clear the text Global declutter list
12426 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12427 if (ChPI)
12428 ChPI->ClearPLIBTextList();
12429 else {
12430 if (ps52plib) ps52plib->ClearTextList();
12431 }
12432
12433 // Render the text directly to the scratch bitmap
12434 OCPNRegion chart_all_text_region(
12435 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12436
12437 if (g_bShowChartBar && m_Piano) {
12438 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12439 GetVP().pix_width, m_Piano->GetHeight());
12440
12441 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12442 if (!style->chartStatusWindowTransparent)
12443 chart_all_text_region.Subtract(chart_bar_rect);
12444 }
12445
12446 if (m_Compass && m_Compass->IsShown()) {
12447 wxRect compassRect = m_Compass->GetRect();
12448 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12449 chart_all_text_region.Subtract(compassRect);
12450 }
12451 }
12452
12453 mscratch_dc.DestroyClippingRegion();
12454
12455 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12456 chart_all_text_region);
12457 }
12458 }
12459 }
12460
12461 // Now that charts are fully rendered, apply the overlay objects as decals.
12462 ocpnDC scratch_dc(mscratch_dc);
12463 DrawOverlayObjects(scratch_dc, ru);
12464
12465 // And finally, blit the scratch dc onto the physical dc
12466 wxRegionIterator upd_final(rgn_blit);
12467 while (upd_final) {
12468 wxRect rect = upd_final.GetRect();
12469 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12470 rect.y);
12471 upd_final++;
12472 }
12473
12474 // Deselect the chart bitmap from the temp_dc, so that it will not be
12475 // destroyed in the temp_dc dtor
12476 temp_dc.SelectObject(wxNullBitmap);
12477 // And for the scratch bitmap
12478 mscratch_dc.SelectObject(wxNullBitmap);
12479
12480 dc.DestroyClippingRegion();
12481
12482 PaintCleanup();
12483}
12484
12485void ChartCanvas::PaintCleanup() {
12486 // Handle the current graphic window, if present
12487 if (m_inPinch) return;
12488
12489 if (pCwin) {
12490 pCwin->Show();
12491 if (m_bTCupdate) {
12492 pCwin->Refresh();
12493 pCwin->Update();
12494 }
12495 }
12496
12497 // And set flags for next time
12498 m_bTCupdate = false;
12499
12500 // Handle deferred WarpPointer
12501 if (warp_flag) {
12502 WarpPointer(warp_x, warp_y);
12503 warp_flag = false;
12504 }
12505
12506 // Start movement timers, this runs nearly immediately.
12507 // the reason we cannot simply call it directly is the
12508 // refresh events it emits may be blocked from this paint event
12509 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12510 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12511}
12512
12513#if 0
12514wxColour GetErrorGraphicColor(double val)
12515{
12516 /*
12517 double valm = wxMin(val_max, val);
12518
12519 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12520 unsigned char red = (unsigned char)(255 * (valm/val_max));
12521
12522 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12523
12524 hv.saturation = 1.0;
12525 hv.value = 1.0;
12526
12527 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12528 return wxColour(rv.red, rv.green, rv.blue);
12529 */
12530
12531 // HTML colors taken from NOAA WW3 Web representation
12532 wxColour c;
12533 if((val > 0) && (val < 1)) c.Set("#002ad9");
12534 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12535 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12536 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12537 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12538 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12539 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12540 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12541 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12542 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12543 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12544 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12545 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12546 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12547 else if((val >= 30) && (val < 36)) c.Set("#870000");
12548 else if((val >= 36) && (val < 42)) c.Set("#690000");
12549 else if((val >= 42) && (val < 48)) c.Set("#550000");
12550 else if( val >= 48) c.Set("#410000");
12551
12552 return c;
12553}
12554
12555void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12556{
12557 wxImage gr_image(vp->pix_width, vp->pix_height);
12558 gr_image.InitAlpha();
12559
12560 double maxval = -10000;
12561 double minval = 10000;
12562
12563 double rlat, rlon;
12564 double glat, glon;
12565
12566 GetCanvasPixPoint(0, 0, rlat, rlon);
12567
12568 for(int i=1; i < vp->pix_height-1; i++)
12569 {
12570 for(int j=0; j < vp->pix_width; j++)
12571 {
12572 // Reference mercator value
12573// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12574
12575 // Georef value
12576 GetCanvasPixPoint(j, i, glat, glon);
12577
12578 maxval = wxMax(maxval, (glat - rlat));
12579 minval = wxMin(minval, (glat - rlat));
12580
12581 }
12582 rlat = glat;
12583 }
12584
12585 GetCanvasPixPoint(0, 0, rlat, rlon);
12586 for(int i=1; i < vp->pix_height-1; i++)
12587 {
12588 for(int j=0; j < vp->pix_width; j++)
12589 {
12590 // Reference mercator value
12591// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12592
12593 // Georef value
12594 GetCanvasPixPoint(j, i, glat, glon);
12595
12596 double f = ((glat - rlat)-minval)/(maxval - minval);
12597
12598 double dy = (f * 40);
12599
12600 wxColour c = GetErrorGraphicColor(dy);
12601 unsigned char r = c.Red();
12602 unsigned char g = c.Green();
12603 unsigned char b = c.Blue();
12604
12605 gr_image.SetRGB(j, i, r,g,b);
12606 if((glat - rlat )!= 0)
12607 gr_image.SetAlpha(j, i, 128);
12608 else
12609 gr_image.SetAlpha(j, i, 255);
12610
12611 }
12612 rlat = glat;
12613 }
12614
12615 // Create a Bitmap
12616 wxBitmap *pbm = new wxBitmap(gr_image);
12617 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12618 pbm->SetMask(gr_mask);
12619
12620 pmdc->DrawBitmap(*pbm, 0,0);
12621
12622 delete pbm;
12623
12624}
12625
12626#endif
12627
12628void ChartCanvas::CancelMouseRoute() {
12629 m_routeState = 0;
12630 m_pMouseRoute = NULL;
12631 m_bDrawingRoute = false;
12632}
12633
12634int ChartCanvas::GetNextContextMenuId() {
12635 return CanvasMenuHandler::GetNextContextMenuId();
12636}
12637
12638bool ChartCanvas::SetCursor(const wxCursor &c) {
12639#ifdef ocpnUSE_GL
12640 if (g_bopengl && m_glcc)
12641 return m_glcc->SetCursor(c);
12642 else
12643#endif
12644 return wxWindow::SetCursor(c);
12645}
12646
12647void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12648 if (g_bquiting) return;
12649 // Keep the mouse position members up to date
12650 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12651
12652 // Retrigger the route leg popup timer
12653 // This handles the case when the chart is moving in auto-follow mode,
12654 // but no user mouse input is made. The timer handler may Hide() the
12655 // popup if the chart moved enough n.b. We use slightly longer oneshot
12656 // value to allow this method's Refresh() to complete before potentially
12657 // getting another Refresh() in the popup timer handler.
12658 if (!m_RolloverPopupTimer.IsRunning() &&
12659 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12660 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12661 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12662 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12663
12664#ifdef ocpnUSE_GL
12665 if (m_glcc && g_bopengl) {
12666 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12667 // overlay objects.
12668 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12669
12670 m_glcc->Refresh(eraseBackground,
12671 NULL); // We always are going to render the entire screen
12672 // anyway, so make
12673 // sure that the window managers understand the invalid area
12674 // is actually the entire client area.
12675
12676 // We need to selectively Refresh some child windows, if they are visible.
12677 // Note that some children are refreshed elsewhere on timer ticks, so don't
12678 // need attention here.
12679
12680 // Thumbnail chart
12681 if (pthumbwin && pthumbwin->IsShown()) {
12682 pthumbwin->Raise();
12683 pthumbwin->Refresh(false);
12684 }
12685
12686 // ChartInfo window
12687 if (m_pCIWin && m_pCIWin->IsShown()) {
12688 m_pCIWin->Raise();
12689 m_pCIWin->Refresh(false);
12690 }
12691
12692 // if(g_MainToolbar)
12693 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12694
12695 } else
12696#endif
12697 wxWindow::Refresh(eraseBackground, rect);
12698}
12699
12700void ChartCanvas::Update() {
12701 if (m_glcc && g_bopengl) {
12702#ifdef ocpnUSE_GL
12703 m_glcc->Update();
12704#endif
12705 } else
12706 wxWindow::Update();
12707}
12708
12709void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12710 if (!pemboss) return;
12711 int x = pemboss->x, y = pemboss->y;
12712 const double factor = 200;
12713
12714 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12715 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12716 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12717
12718 // Grab a snipped image out of the chart
12719 wxMemoryDC snip_dc;
12720 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12721 snip_dc.SelectObject(snip_bmp);
12722
12723 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12724 snip_dc.SelectObject(wxNullBitmap);
12725
12726 wxImage snip_img = snip_bmp.ConvertToImage();
12727
12728 // Apply Emboss map to the snip image
12729 unsigned char *pdata = snip_img.GetData();
12730 if (pdata) {
12731 for (int y = 0; y < pemboss->height; y++) {
12732 int map_index = (y * pemboss->width);
12733 for (int x = 0; x < pemboss->width; x++) {
12734 double val = (pemboss->pmap[map_index] * factor) / 256.;
12735
12736 int nred = (int)((*pdata) + val);
12737 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12738 *pdata++ = (unsigned char)nred;
12739
12740 int ngreen = (int)((*pdata) + val);
12741 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12742 *pdata++ = (unsigned char)ngreen;
12743
12744 int nblue = (int)((*pdata) + val);
12745 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12746 *pdata++ = (unsigned char)nblue;
12747
12748 map_index++;
12749 }
12750 }
12751 }
12752
12753 // Convert embossed snip to a bitmap
12754 wxBitmap emb_bmp(snip_img);
12755
12756 // Map to another memoryDC
12757 wxMemoryDC result_dc;
12758 result_dc.SelectObject(emb_bmp);
12759
12760 // Blit to target
12761 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12762
12763 result_dc.SelectObject(wxNullBitmap);
12764}
12765
12766emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12767 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12768
12769 if (GetQuiltMode()) {
12770 // disable Overzoom indicator for MBTiles
12771 int refIndex = GetQuiltRefChartdbIndex();
12772 if (refIndex >= 0) {
12773 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12774 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12775 if (current_type == CHART_TYPE_MBTILES) {
12776 ChartBase *pChart = m_pQuilt->GetRefChart();
12777 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12778 if (ptc) {
12779 zoom_factor = ptc->GetZoomFactor();
12780 }
12781 }
12782 }
12783
12784 if (zoom_factor <= 3.9) return NULL;
12785 } else {
12786 if (m_singleChart) {
12787 if (zoom_factor <= 3.9) return NULL;
12788 } else
12789 return NULL;
12790 }
12791
12792 if (m_pEM_OverZoom) {
12793 m_pEM_OverZoom->x = 4;
12794 m_pEM_OverZoom->y = 0;
12795 if (g_MainToolbar && IsPrimaryCanvas()) {
12796 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12797 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12798 }
12799 }
12800 return m_pEM_OverZoom;
12801}
12802
12803void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12804 GridDraw(dc);
12805
12806 // bool pluginOverlayRender = true;
12807 //
12808 // if(g_canvasConfig > 0){ // Multi canvas
12809 // if(IsPrimaryCanvas())
12810 // pluginOverlayRender = false;
12811 // }
12812
12813 g_overlayCanvas = this;
12814
12815 if (g_pi_manager) {
12816 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12817 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12819 }
12820
12821 AISDrawAreaNotices(dc, GetVP(), this);
12822
12823 wxDC *pdc = dc.GetDC();
12824 if (pdc) {
12825 pdc->DestroyClippingRegion();
12826 wxDCClipper(*pdc, ru);
12827 }
12828
12829 if (m_bShowNavobjects) {
12830 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12831 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12832 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12833 DrawAnchorWatchPoints(dc);
12834 } else {
12835 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12836 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12837 }
12838
12839 AISDraw(dc, GetVP(), this);
12840 ShipDraw(dc);
12841 AlertDraw(dc);
12842
12843 RenderVisibleSectorLights(dc);
12844
12845 RenderAllChartOutlines(dc, GetVP());
12846 RenderRouteLegs(dc);
12847 RenderShipToActive(dc, false);
12848 ScaleBarDraw(dc);
12849 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12850 if (g_pi_manager) {
12851 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12853 }
12854
12855 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12856 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12857
12858 if (g_pi_manager) {
12859 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12861 }
12862
12863 if (m_bShowTide) {
12864 RebuildTideSelectList(GetVP().GetBBox());
12865 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12866 }
12867
12868 if (m_bShowCurrent) {
12869 RebuildCurrentSelectList(GetVP().GetBBox());
12870 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12871 }
12872
12873 if (!g_PrintingInProgress) {
12874 if (IsPrimaryCanvas()) {
12875 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12876 }
12877
12878 if (IsPrimaryCanvas()) {
12879 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12880 }
12881
12882 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12883
12884 if (m_pTrackRolloverWin) {
12885 m_pTrackRolloverWin->Draw(dc);
12886 m_brepaint_piano = true;
12887 }
12888
12889 if (m_pRouteRolloverWin) {
12890 m_pRouteRolloverWin->Draw(dc);
12891 m_brepaint_piano = true;
12892 }
12893
12894 if (m_pAISRolloverWin) {
12895 m_pAISRolloverWin->Draw(dc);
12896 m_brepaint_piano = true;
12897 }
12898 if (m_brepaint_piano && g_bShowChartBar) {
12899 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12900 }
12901
12902 if (m_Compass) m_Compass->Paint(dc);
12903
12904 if (!g_CanvasHideNotificationIcon) {
12905 if (IsPrimaryCanvas()) {
12906 auto &noteman = NotificationManager::GetInstance();
12907 if (noteman.GetNotificationCount()) {
12908 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12909 if (m_notification_button->UpdateStatus()) Refresh();
12910 m_notification_button->Show(true);
12911 m_notification_button->Paint(dc);
12912 } else {
12913 m_notification_button->Show(false);
12914 }
12915 }
12916 }
12917 }
12918 if (g_pi_manager) {
12919 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12921 }
12922}
12923
12924emboss_data *ChartCanvas::EmbossDepthScale() {
12925 if (!m_bShowDepthUnits) return NULL;
12926
12927 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12928
12929 if (GetQuiltMode()) {
12930 wxString s = m_pQuilt->GetQuiltDepthUnit();
12931 s.MakeUpper();
12932 if (s == "FEET")
12933 depth_unit_type = DEPTH_UNIT_FEET;
12934 else if (s.StartsWith("FATHOMS"))
12935 depth_unit_type = DEPTH_UNIT_FATHOMS;
12936 else if (s.StartsWith("METERS"))
12937 depth_unit_type = DEPTH_UNIT_METERS;
12938 else if (s.StartsWith("METRES"))
12939 depth_unit_type = DEPTH_UNIT_METERS;
12940 else if (s.StartsWith("METRIC"))
12941 depth_unit_type = DEPTH_UNIT_METERS;
12942 else if (s.StartsWith("METER"))
12943 depth_unit_type = DEPTH_UNIT_METERS;
12944
12945 } else {
12946 if (m_singleChart) {
12947 depth_unit_type = m_singleChart->GetDepthUnitType();
12948 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12949 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12950 }
12951 }
12952
12953 emboss_data *ped = NULL;
12954 switch (depth_unit_type) {
12955 case DEPTH_UNIT_FEET:
12956 ped = m_pEM_Feet;
12957 break;
12958 case DEPTH_UNIT_METERS:
12959 ped = m_pEM_Meters;
12960 break;
12961 case DEPTH_UNIT_FATHOMS:
12962 ped = m_pEM_Fathoms;
12963 break;
12964 default:
12965 return NULL;
12966 }
12967
12968 ped->x = (GetVP().pix_width - ped->width);
12969
12970 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12971 wxRect r = m_Compass->GetRect();
12972 ped->y = r.y + r.height;
12973 } else {
12974 ped->y = 40;
12975 }
12976 return ped;
12977}
12978
12979void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12980 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12981 wxFont font;
12982 if (style->embossFont == wxEmptyString) {
12983 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12984 font = *dFont;
12985 font.SetPointSize(60);
12986 font.SetWeight(wxFONTWEIGHT_BOLD);
12987 } else
12988 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12989 wxFONTWEIGHT_BOLD, false, style->embossFont);
12990
12991 int emboss_width = 500;
12992 int emboss_height = 200;
12993
12994 // Free any existing emboss maps
12995 delete m_pEM_Feet;
12996 delete m_pEM_Meters;
12997 delete m_pEM_Fathoms;
12998
12999 // Create the 3 DepthUnit emboss map structures
13000 m_pEM_Feet =
13001 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13002 m_pEM_Meters =
13003 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13004 m_pEM_Fathoms =
13005 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13006}
13007
13008#define OVERZOOM_TEXT _("OverZoom")
13009
13010void ChartCanvas::SetOverzoomFont() {
13011 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13012 int w, h;
13013
13014 wxFont font;
13015 if (style->embossFont == wxEmptyString) {
13016 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13017 font = *dFont;
13018 font.SetPointSize(40);
13019 font.SetWeight(wxFONTWEIGHT_BOLD);
13020 } else
13021 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13022 wxFONTWEIGHT_BOLD, false, style->embossFont);
13023
13024 wxClientDC dc(this);
13025 dc.SetFont(font);
13026 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13027
13028 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13029 font.SetPointSize(font.GetPointSize() - 1);
13030 dc.SetFont(font);
13031 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13032 }
13033 m_overzoomFont = font;
13034 m_overzoomTextWidth = w;
13035 m_overzoomTextHeight = h;
13036}
13037
13038void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13039 delete m_pEM_OverZoom;
13040
13041 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13042 m_pEM_OverZoom =
13043 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13044 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13045}
13046
13047emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13048 int height, const wxString &str,
13049 ColorScheme cs) {
13050 int *pmap;
13051
13052 // Create a temporary bitmap
13053 wxBitmap bmp(width, height, -1);
13054
13055 // Create a memory DC
13056 wxMemoryDC temp_dc;
13057 temp_dc.SelectObject(bmp);
13058
13059 // Paint on it
13060 temp_dc.SetBackground(*wxWHITE_BRUSH);
13061 temp_dc.SetTextBackground(*wxWHITE);
13062 temp_dc.SetTextForeground(*wxBLACK);
13063
13064 temp_dc.Clear();
13065
13066 temp_dc.SetFont(font);
13067
13068 int str_w, str_h;
13069 temp_dc.GetTextExtent(str, &str_w, &str_h);
13070 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13071 temp_dc.DrawText(str, 1, 1);
13072
13073 // Deselect the bitmap
13074 temp_dc.SelectObject(wxNullBitmap);
13075
13076 // Convert bitmap the wxImage for manipulation
13077 wxImage img = bmp.ConvertToImage();
13078
13079 int image_width = str_w * 105 / 100;
13080 int image_height = str_h * 105 / 100;
13081 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13082 wxMin(image_height, img.GetHeight()));
13083 wxImage imgs = img.GetSubImage(r);
13084
13085 double val_factor;
13086 switch (cs) {
13087 case GLOBAL_COLOR_SCHEME_DAY:
13088 default:
13089 val_factor = 1;
13090 break;
13091 case GLOBAL_COLOR_SCHEME_DUSK:
13092 val_factor = .5;
13093 break;
13094 case GLOBAL_COLOR_SCHEME_NIGHT:
13095 val_factor = .25;
13096 break;
13097 }
13098
13099 int val;
13100 int index;
13101 const int w = imgs.GetWidth();
13102 const int h = imgs.GetHeight();
13103 pmap = (int *)calloc(w * h * sizeof(int), 1);
13104 // Create emboss map by differentiating the emboss image
13105 // and storing integer results in pmap
13106 // n.b. since the image is B/W, it is sufficient to check
13107 // one channel (i.e. red) only
13108 for (int y = 1; y < h - 1; y++) {
13109 for (int x = 1; x < w - 1; x++) {
13110 val =
13111 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13112 val = (int)(val * val_factor);
13113 index = (y * w) + x;
13114 pmap[index] = val;
13115 }
13116 }
13117
13118 emboss_data *pret = new emboss_data;
13119 pret->pmap = pmap;
13120 pret->width = w;
13121 pret->height = h;
13122
13123 return pret;
13124}
13125
13126void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13127 Track *active_track = NULL;
13128 for (Track *pTrackDraw : g_TrackList) {
13129 if (g_pActiveTrack == pTrackDraw) {
13130 active_track = pTrackDraw;
13131 continue;
13132 }
13133
13134 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13135 }
13136
13137 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13138}
13139
13140void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13141 Track *active_track = NULL;
13142 for (Track *pTrackDraw : g_TrackList) {
13143 if (g_pActiveTrack == pTrackDraw) {
13144 active_track = pTrackDraw;
13145 break;
13146 }
13147 }
13148 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13149}
13150
13151void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13152 Route *active_route = NULL;
13153 for (Route *pRouteDraw : *pRouteList) {
13154 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13155 active_route = pRouteDraw;
13156 continue;
13157 }
13158
13159 // if(m_canvasIndex == 1)
13160 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13161 }
13162
13163 // Draw any active or selected route (or track) last, so that is is always on
13164 // top
13165 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13166}
13167
13168void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13169 Route *active_route = NULL;
13170
13171 for (Route *pRouteDraw : *pRouteList) {
13172 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13173 active_route = pRouteDraw;
13174 break;
13175 }
13176 }
13177 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13178}
13179
13180void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13181 if (!pWayPointMan) return;
13182
13183 auto node = pWayPointMan->GetWaypointList()->begin();
13184
13185 while (node != pWayPointMan->GetWaypointList()->end()) {
13186 RoutePoint *pWP = *node;
13187 if (pWP) {
13188 if (pWP->m_bIsInRoute) {
13189 ++node;
13190 continue;
13191 }
13192
13193 /* technically incorrect... waypoint has bounding box */
13194 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13195 RoutePointGui(*pWP).Draw(dc, this, NULL);
13196 else {
13197 // Are Range Rings enabled?
13198 if (pWP->GetShowWaypointRangeRings() &&
13199 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13200 double factor = 1.00;
13201 if (pWP->GetWaypointRangeRingsStepUnits() ==
13202 1) // convert kilometers to NMi
13203 factor = 1 / 1.852;
13204
13205 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13206 pWP->GetWaypointRangeRingsStep() / 60.;
13207 radius *= 2; // Fudge factor
13208
13209 LLBBox radar_box;
13210 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13211 pWP->m_lat + radius, pWP->m_lon + radius);
13212 if (!BltBBox.IntersectOut(radar_box)) {
13213 RoutePointGui(*pWP).Draw(dc, this, NULL);
13214 }
13215 }
13216 }
13217 }
13218
13219 ++node;
13220 }
13221}
13222
13223void ChartCanvas::DrawBlinkObjects() {
13224 // All RoutePoints
13225 wxRect update_rect;
13226
13227 if (!pWayPointMan) return;
13228
13229 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13230 if (pWP) {
13231 if (pWP->m_bBlink) {
13232 update_rect.Union(pWP->CurrentRect_in_DC);
13233 }
13234 }
13235 }
13236 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13237}
13238
13239void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13240 // draw anchor watch rings, if activated
13241
13243 wxPoint r1, r2;
13244 wxPoint lAnchorPoint1, lAnchorPoint2;
13245 double lpp1 = 0.0;
13246 double lpp2 = 0.0;
13247 if (pAnchorWatchPoint1) {
13248 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13250 &lAnchorPoint1);
13251 }
13252 if (pAnchorWatchPoint2) {
13253 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13255 &lAnchorPoint2);
13256 }
13257
13258 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13259 wxPen ppPenr(GetGlobalColor("URED"), 2);
13260
13261 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13262 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13263 dc.SetBrush(*ppBrush);
13264
13265 if (lpp1 > 0) {
13266 dc.SetPen(ppPeng);
13267 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13268 }
13269
13270 if (lpp2 > 0) {
13271 dc.SetPen(ppPeng);
13272 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13273 }
13274
13275 if (lpp1 < 0) {
13276 dc.SetPen(ppPenr);
13277 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13278 }
13279
13280 if (lpp2 < 0) {
13281 dc.SetPen(ppPenr);
13282 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13283 }
13284 }
13285}
13286
13287double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13288 double lpp = 0.;
13289 wxPoint r1;
13290 wxPoint lAnchorPoint;
13291 double d1 = 0.0;
13292 double dabs;
13293 double tlat1, tlon1;
13294
13295 if (pAnchorWatchPoint) {
13296 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13297 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13298 dabs = fabs(d1 / 1852.);
13299 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13300 &tlat1, &tlon1);
13301 GetCanvasPointPix(tlat1, tlon1, &r1);
13302 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13303 &lAnchorPoint);
13304 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13305 pow((double)(lAnchorPoint.y - r1.y), 2));
13306
13307 // This is an entry watch
13308 if (d1 < 0) lpp = -lpp;
13309 }
13310 return lpp;
13311}
13312
13313//------------------------------------------------------------------------------------------
13314// Tides Support
13315//------------------------------------------------------------------------------------------
13316void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13317 if (!ptcmgr) return;
13318
13319 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13320
13321 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13322 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13323 double lon = pIDX->IDX_lon;
13324 double lat = pIDX->IDX_lat;
13325
13326 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13327 if ((type == 't') || (type == 'T')) {
13328 if (BBox.Contains(lat, lon)) {
13329 // Manage the point selection list
13330 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13331 }
13332 }
13333 }
13334}
13335
13336void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13337 if (!ptcmgr) return;
13338
13339 wxDateTime this_now = gTimeSource;
13340 bool cur_time = !gTimeSource.IsValid();
13341 if (cur_time) this_now = wxDateTime::Now();
13342 time_t t_this_now = this_now.GetTicks();
13343
13344 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13345 wxPENSTYLE_SOLID);
13346 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13347 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13348 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13349 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13350
13351 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13352 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13353 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13354 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13355 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13356 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13357
13358 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13359 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13360 int font_size = wxMax(10, dFont->GetPointSize());
13361 font_size /= g_Platform->GetDisplayDIPMult(this);
13362 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13363 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13364 false, dFont->GetFaceName());
13365
13366 dc.SetPen(*pblack_pen);
13367 dc.SetBrush(*pgreen_brush);
13368
13369 wxBitmap bm;
13370 switch (m_cs) {
13371 case GLOBAL_COLOR_SCHEME_DAY:
13372 bm = m_bmTideDay;
13373 break;
13374 case GLOBAL_COLOR_SCHEME_DUSK:
13375 bm = m_bmTideDusk;
13376 break;
13377 case GLOBAL_COLOR_SCHEME_NIGHT:
13378 bm = m_bmTideNight;
13379 break;
13380 default:
13381 bm = m_bmTideDay;
13382 break;
13383 }
13384
13385 int bmw = bm.GetWidth();
13386 int bmh = bm.GetHeight();
13387
13388 float scale_factor = 1.0;
13389
13390 // Set the onscreen size of the symbol
13391 // Compensate for various display resolutions
13392 float icon_pixelRefDim = 45;
13393
13394 // Tidal report graphic is scaled by the text size of the label in use
13395 wxScreenDC sdc;
13396 int height;
13397 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13398 height *= g_Platform->GetDisplayDIPMult(this);
13399 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13400
13401 scale_factor *= pix_factor;
13402
13403 float user_scale_factor = g_ChartScaleFactorExp;
13404 if (g_ChartScaleFactorExp > 1.0)
13405 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13406 1.2; // soften the scale factor a bit
13407
13408 scale_factor *= user_scale_factor;
13409 scale_factor *= GetContentScaleFactor();
13410
13411 {
13412 double marge = 0.05;
13413 std::vector<LLBBox> drawn_boxes;
13414 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13415 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13416
13417 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13418 if ((type == 't') || (type == 'T')) // only Tides
13419 {
13420 double lon = pIDX->IDX_lon;
13421 double lat = pIDX->IDX_lat;
13422
13423 if (BBox.ContainsMarge(lat, lon, marge)) {
13424 // Avoid drawing detailed graphic for duplicate tide stations
13425 if (GetVP().chart_scale < 500000) {
13426 bool bdrawn = false;
13427 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13428 if (drawn_boxes[i].Contains(lat, lon)) {
13429 bdrawn = true;
13430 break;
13431 }
13432 }
13433 if (bdrawn) continue; // the station loop
13434
13435 LLBBox this_box;
13436 this_box.Set(lat, lon, lat, lon);
13437 this_box.EnLarge(.005);
13438 drawn_boxes.push_back(this_box);
13439 }
13440
13441 wxPoint r;
13442 GetCanvasPointPix(lat, lon, &r);
13443 // draw standard icons
13444 if (GetVP().chart_scale > 500000) {
13445 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13446 }
13447 // draw "extended" icons
13448 else {
13449 dc.SetFont(*plabelFont);
13450 {
13451 {
13452 float val, nowlev;
13453 float ltleve = 0.;
13454 float htleve = 0.;
13455 time_t tctime;
13456 time_t lttime = 0;
13457 time_t httime = 0;
13458 bool wt;
13459 // define if flood or ebb in the last ten minutes and verify if
13460 // data are useable
13461 if (ptcmgr->GetTideFlowSens(
13462 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13463 pIDX->IDX_rec_num, nowlev, val, wt)) {
13464 // search forward the first HW or LW near "now" ( starting at
13465 // "now" - ten minutes )
13466 ptcmgr->GetHightOrLowTide(
13467 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13468 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13469 wt, pIDX->IDX_rec_num, val, tctime);
13470 if (wt) {
13471 httime = tctime;
13472 htleve = val;
13473 } else {
13474 lttime = tctime;
13475 ltleve = val;
13476 }
13477 wt = !wt;
13478
13479 // then search opposite tide near "now"
13480 if (tctime > t_this_now) // search backward
13481 ptcmgr->GetHightOrLowTide(
13482 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13483 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13484 pIDX->IDX_rec_num, val, tctime);
13485 else
13486 // or search forward
13487 ptcmgr->GetHightOrLowTide(
13488 t_this_now, FORWARD_TEN_MINUTES_STEP,
13489 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13490 val, tctime);
13491 if (wt) {
13492 httime = tctime;
13493 htleve = val;
13494 } else {
13495 lttime = tctime;
13496 ltleve = val;
13497 }
13498
13499 // draw the tide rectangle:
13500
13501 // tide icon rectangle has default pre-scaled width = 12 ,
13502 // height = 45
13503 int width = (int)(12 * scale_factor + 0.5);
13504 int height = (int)(45 * scale_factor + 0.5);
13505 int linew = wxMax(1, (int)(scale_factor));
13506 int xDraw = r.x - (width / 2);
13507 int yDraw = r.y - (height / 2);
13508
13509 // process tide state ( %height and flow sens )
13510 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13511 int hs = (httime > lttime) ? -4 : 4;
13512 hs *= (int)(scale_factor + 0.5);
13513 if (ts > 0.995 || ts < 0.005) hs = 0;
13514 int ht_y = (int)(height * ts);
13515
13516 // draw yellow tide rectangle outlined in black
13517 pblack_pen->SetWidth(linew);
13518 dc.SetPen(*pblack_pen);
13519 dc.SetBrush(*pyelo_brush);
13520 dc.DrawRectangle(xDraw, yDraw, width, height);
13521
13522 // draw blue rectangle as water height, smaller in width than
13523 // yellow rectangle
13524 dc.SetPen(*pblue_pen);
13525 dc.SetBrush(*pblue_brush);
13526 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13527 (width - (4 * linew)), height - ht_y);
13528
13529 // draw sens arrows (ensure they are not "under-drawn" by top
13530 // line of blue rectangle )
13531 int hl;
13532 wxPoint arrow[3];
13533 arrow[0].x = xDraw + 2 * linew;
13534 arrow[1].x = xDraw + width / 2;
13535 arrow[2].x = xDraw + width - 2 * linew;
13536 pyelo_pen->SetWidth(linew);
13537 pblue_pen->SetWidth(linew);
13538 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13539 {
13540 hl = (int)(height * 0.25) + yDraw;
13541 arrow[0].y = hl;
13542 arrow[1].y = hl + hs;
13543 arrow[2].y = hl;
13544 if (ts < 0.15)
13545 dc.SetPen(*pyelo_pen);
13546 else
13547 dc.SetPen(*pblue_pen);
13548 dc.DrawLines(3, arrow);
13549 }
13550 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13551 {
13552 hl = (int)(height * 0.5) + yDraw;
13553 arrow[0].y = hl;
13554 arrow[1].y = hl + hs;
13555 arrow[2].y = hl;
13556 if (ts < 0.40)
13557 dc.SetPen(*pyelo_pen);
13558 else
13559 dc.SetPen(*pblue_pen);
13560 dc.DrawLines(3, arrow);
13561 }
13562 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13563 {
13564 hl = (int)(height * 0.75) + yDraw;
13565 arrow[0].y = hl;
13566 arrow[1].y = hl + hs;
13567 arrow[2].y = hl;
13568 if (ts < 0.65)
13569 dc.SetPen(*pyelo_pen);
13570 else
13571 dc.SetPen(*pblue_pen);
13572 dc.DrawLines(3, arrow);
13573 }
13574 // draw tide level text
13575 wxString s;
13576 s.Printf("%3.1f", nowlev);
13577 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13578 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13579 int wx1;
13580 dc.GetTextExtent(s, &wx1, NULL);
13581 wx1 *= g_Platform->GetDisplayDIPMult(this);
13582 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13583 }
13584 }
13585 }
13586 }
13587 }
13588 }
13589 }
13590 }
13591}
13592
13593//------------------------------------------------------------------------------------------
13594// Currents Support
13595//------------------------------------------------------------------------------------------
13596
13597void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13598 if (!ptcmgr) return;
13599
13600 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13601
13602 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13603 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13604 double lon = pIDX->IDX_lon;
13605 double lat = pIDX->IDX_lat;
13606
13607 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13608 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13609 if ((BBox.Contains(lat, lon))) {
13610 // Manage the point selection list
13611 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13612 }
13613 }
13614 }
13615}
13616
13617void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13618 if (!ptcmgr) return;
13619
13620 float tcvalue, dir;
13621 bool bnew_val;
13622 char sbuf[20];
13623 wxFont *pTCFont;
13624 double lon_last = 0.;
13625 double lat_last = 0.;
13626 // arrow size for Raz Blanchard : 12 knots north
13627 double marge = 0.2;
13628 bool cur_time = !gTimeSource.IsValid();
13629
13630 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13631 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13632
13633 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13634 wxPENSTYLE_SOLID);
13635 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13636 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13637 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13638 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13639 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13640 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13641 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13642 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13643
13644 double skew_angle = GetVPRotation();
13645
13646 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13647 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13648 int font_size = wxMax(10, dFont->GetPointSize());
13649 font_size /= g_Platform->GetDisplayDIPMult(this);
13650 pTCFont = FontMgr::Get().FindOrCreateFont(
13651 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13652 false, dFont->GetFaceName());
13653
13654 float scale_factor = 1.0;
13655
13656 // Set the onscreen size of the symbol
13657 // Current report graphic is scaled by the text size of the label in use
13658 wxScreenDC sdc;
13659 int height;
13660 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13661 height *= g_Platform->GetDisplayDIPMult(this);
13662 float nominal_icon_size_pixels = 15;
13663 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13664
13665 scale_factor *= pix_factor;
13666
13667 float user_scale_factor = g_ChartScaleFactorExp;
13668 if (g_ChartScaleFactorExp > 1.0)
13669 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13670 1.2; // soften the scale factor a bit
13671
13672 scale_factor *= user_scale_factor;
13673
13674 scale_factor *= GetContentScaleFactor();
13675
13676 {
13677 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13678 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13679 double lon = pIDX->IDX_lon;
13680 double lat = pIDX->IDX_lat;
13681
13682 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13683 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13684 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13685 wxPoint r;
13686 GetCanvasPointPix(lat, lon, &r);
13687
13688 wxPoint d[4]; // points of a diamond at the current station location
13689 int dd = (int)(5.0 * scale_factor + 0.5);
13690 d[0].x = r.x;
13691 d[0].y = r.y + dd;
13692 d[1].x = r.x + dd;
13693 d[1].y = r.y;
13694 d[2].x = r.x;
13695 d[2].y = r.y - dd;
13696 d[3].x = r.x - dd;
13697 d[3].y = r.y;
13698
13699 if (1) {
13700 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13701 dc.SetPen(*pblack_pen);
13702 dc.SetBrush(*porange_brush);
13703 dc.DrawPolygon(4, d);
13704
13705 if (type == 'C') {
13706 dc.SetBrush(*pblack_brush);
13707 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13708 }
13709
13710 if (GetVP().chart_scale < 1000000) {
13711 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13712 continue;
13713 } else
13714 continue;
13715
13716 if (1 /*type == 'c'*/) {
13717 {
13718 // Get the display pixel location of the current station
13719 int pixxc, pixyc;
13720 pixxc = r.x;
13721 pixyc = r.y;
13722
13723 // Adjust drawing size using logarithmic scale. tcvalue is
13724 // current in knots
13725 double a1 = fabs(tcvalue) * 10.;
13726 // Current values <= 0.1 knot will have no arrow
13727 a1 = wxMax(1.0, a1);
13728 double a2 = log10(a1);
13729
13730 float cscale = scale_factor * a2 * 0.3;
13731
13732 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13733 dc.SetPen(*porange_pen);
13734 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13735 cscale);
13736 // Draw text, if enabled
13737
13738 if (bDrawCurrentValues) {
13739 dc.SetFont(*pTCFont);
13740 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13741 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13742 }
13743 }
13744 } // scale
13745 }
13746 /* This is useful for debugging the TC database
13747 else
13748 {
13749 dc.SetPen ( *porange_pen );
13750 dc.SetBrush ( *pgray_brush );
13751 dc.DrawPolygon ( 4, d );
13752 }
13753 */
13754 }
13755 lon_last = lon;
13756 lat_last = lat;
13757 }
13758 }
13759 }
13760}
13761
13762void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13763 ShowSingleTideDialog(x, y, pvIDX);
13764}
13765
13766void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13767 if (!pvIDX) return; // Validate input
13768
13769 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13770
13771 // Check if a tide dialog is already open and visible
13772 if (pCwin && pCwin->IsShown()) {
13773 // Same tide station: bring existing dialog to front (preserves user
13774 // context)
13775 if (pCwin->GetCurrentIDX() == pNewIDX) {
13776 pCwin->Raise();
13777 pCwin->SetFocus();
13778
13779 // Provide subtle visual feedback that dialog is already open
13780 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13781 return;
13782 }
13783
13784 // Different tide station: close current dialog before opening new one
13785 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13786 }
13787
13788 if (pCwin) {
13789 // This shouldn't happen but ensures clean state
13790 pCwin->Destroy();
13791 pCwin = NULL;
13792 }
13793
13794 // Create and display new tide dialog
13795 pCwin = new TCWin(this, x, y, pvIDX);
13796
13797 // Ensure the dialog is properly shown and focused
13798 if (pCwin) {
13799 pCwin->Show();
13800 pCwin->Raise();
13801 pCwin->SetFocus();
13802 }
13803}
13804
13805bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13806
13808 if (pCwin) {
13809 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13810 }
13811}
13812
13813#define NUM_CURRENT_ARROW_POINTS 9
13814static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13815 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13816 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13817 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13818
13819void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13820 double scale) {
13821 if (scale > 1e-2) {
13822 float sin_rot = sin(rot_angle * PI / 180.);
13823 float cos_rot = cos(rot_angle * PI / 180.);
13824
13825 // Move to the first point
13826
13827 float xt = CurrentArrowArray[0].x;
13828 float yt = CurrentArrowArray[0].y;
13829
13830 float xp = (xt * cos_rot) - (yt * sin_rot);
13831 float yp = (xt * sin_rot) + (yt * cos_rot);
13832 int x1 = (int)(xp * scale);
13833 int y1 = (int)(yp * scale);
13834
13835 // Walk thru the point list
13836 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13837 xt = CurrentArrowArray[ip].x;
13838 yt = CurrentArrowArray[ip].y;
13839
13840 float xp = (xt * cos_rot) - (yt * sin_rot);
13841 float yp = (xt * sin_rot) + (yt * cos_rot);
13842 int x2 = (int)(xp * scale);
13843 int y2 = (int)(yp * scale);
13844
13845 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13846
13847 x1 = x2;
13848 y1 = y2;
13849 }
13850 }
13851}
13852
13853wxString ChartCanvas::FindValidUploadPort() {
13854 wxString port;
13855 // Try to use the saved persistent upload port first
13856 if (!g_uploadConnection.IsEmpty() &&
13857 g_uploadConnection.StartsWith("Serial")) {
13858 port = g_uploadConnection;
13859 }
13860
13861 else {
13862 // If there is no persistent upload port recorded (yet)
13863 // then use the first available serial connection which has output defined.
13864 for (auto *cp : TheConnectionParams()) {
13865 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13866 port << "Serial:" << cp->Port;
13867 }
13868 }
13869 return port;
13870}
13871
13872void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13873 if (!win) return;
13874
13875 if (NULL == g_pais_query_dialog_active) {
13876 int pos_x = g_ais_query_dialog_x;
13877 int pos_y = g_ais_query_dialog_y;
13878
13879 if (g_pais_query_dialog_active) {
13880 g_pais_query_dialog_active->Destroy();
13881 g_pais_query_dialog_active = new AISTargetQueryDialog();
13882 } else {
13883 g_pais_query_dialog_active = new AISTargetQueryDialog();
13884 }
13885
13886 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13887 wxPoint(pos_x, pos_y));
13888
13889 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13890 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13891 g_pais_query_dialog_active->SetMMSI(mmsi);
13892 g_pais_query_dialog_active->UpdateText();
13893 wxSize sz = g_pais_query_dialog_active->GetSize();
13894
13895 bool b_reset_pos = false;
13896#ifdef __WXMSW__
13897 // Support MultiMonitor setups which an allow negative window positions.
13898 // If the requested window title bar does not intersect any installed
13899 // monitor, then default to simple primary monitor positioning.
13900 RECT frame_title_rect;
13901 frame_title_rect.left = pos_x;
13902 frame_title_rect.top = pos_y;
13903 frame_title_rect.right = pos_x + sz.x;
13904 frame_title_rect.bottom = pos_y + 30;
13905
13906 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13907 b_reset_pos = true;
13908#else
13909
13910 // Make sure drag bar (title bar) of window intersects wxClient Area of
13911 // screen, with a little slop...
13912 wxRect window_title_rect; // conservative estimate
13913 window_title_rect.x = pos_x;
13914 window_title_rect.y = pos_y;
13915 window_title_rect.width = sz.x;
13916 window_title_rect.height = 30;
13917
13918 wxRect ClientRect = wxGetClientDisplayRect();
13919 ClientRect.Deflate(
13920 60, 60); // Prevent the new window from being too close to the edge
13921 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13922
13923#endif
13924
13925 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13926
13927 } else {
13928 g_pais_query_dialog_active->SetMMSI(mmsi);
13929 g_pais_query_dialog_active->UpdateText();
13930 }
13931
13932 g_pais_query_dialog_active->Show();
13933}
13934
13935void ChartCanvas::ToggleCanvasQuiltMode() {
13936 bool cur_mode = GetQuiltMode();
13937
13938 if (!GetQuiltMode())
13939 SetQuiltMode(true);
13940 else if (GetQuiltMode()) {
13941 SetQuiltMode(false);
13942 g_sticky_chart = GetQuiltReferenceChartIndex();
13943 }
13944
13945 if (cur_mode != GetQuiltMode()) {
13946 SetupCanvasQuiltMode();
13947 DoCanvasUpdate();
13948 InvalidateGL();
13949 Refresh();
13950 }
13951 // TODO What to do about this?
13952 // g_bQuiltEnable = GetQuiltMode();
13953
13954 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13955 if (ps52plib) ps52plib->GenerateStateHash();
13956
13957 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13958 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13959}
13960
13961void ChartCanvas::DoCanvasStackDelta(int direction) {
13962 if (!GetQuiltMode()) {
13963 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13964 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13965 if ((current_stack_index + direction) < 0) return;
13966
13967 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13968 int new_dbIndex =
13969 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13970
13971 if (IsChartQuiltableRef(new_dbIndex)) {
13972 ToggleCanvasQuiltMode();
13973 SelectQuiltRefdbChart(new_dbIndex);
13974 m_bpersistent_quilt = false;
13975 }
13976 } else {
13977 SelectChartFromStack(current_stack_index + direction);
13978 }
13979 } else {
13980 std::vector<int> piano_chart_index_array =
13981 GetQuiltExtendedStackdbIndexArray();
13982 int refdb = GetQuiltRefChartdbIndex();
13983
13984 // Find the ref chart in the stack
13985 int current_index = -1;
13986 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13987 if (refdb == piano_chart_index_array[i]) {
13988 current_index = i;
13989 break;
13990 }
13991 }
13992 if (current_index == -1) return;
13993
13994 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13995 int target_family = ctet.GetChartFamily();
13996
13997 int new_index = -1;
13998 int check_index = current_index + direction;
13999 bool found = false;
14000 int check_dbIndex = -1;
14001 int new_dbIndex = -1;
14002
14003 // When quilted. switch within the same chart family
14004 while (!found &&
14005 (unsigned int)check_index < piano_chart_index_array.size() &&
14006 (check_index >= 0)) {
14007 check_dbIndex = piano_chart_index_array[check_index];
14008 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14009 if (target_family == cte.GetChartFamily()) {
14010 found = true;
14011 new_index = check_index;
14012 new_dbIndex = check_dbIndex;
14013 break;
14014 }
14015
14016 check_index += direction;
14017 }
14018
14019 if (!found) return;
14020
14021 if (!IsChartQuiltableRef(new_dbIndex)) {
14022 ToggleCanvasQuiltMode();
14023 SelectdbChart(new_dbIndex);
14024 m_bpersistent_quilt = true;
14025 } else {
14026 SelectQuiltRefChart(new_index);
14027 }
14028 }
14029
14030 // update the state of the menu items (checkmarks etc)
14031 top_frame::Get()->UpdateGlobalMenuItems();
14032 SetQuiltChartHiLiteIndex(-1);
14033
14034 ReloadVP();
14035}
14036
14037//--------------------------------------------------------------------------------------------------------
14038//
14039// Toolbar support
14040//
14041//--------------------------------------------------------------------------------------------------------
14042
14043void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14044 // Handle the per-canvas toolbar clicks here
14045
14046 switch (event.GetId()) {
14047 case ID_ZOOMIN: {
14048 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14049 break;
14050 }
14051
14052 case ID_ZOOMOUT: {
14053 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14054 break;
14055 }
14056
14057 case ID_STKUP:
14058 DoCanvasStackDelta(1);
14059 DoCanvasUpdate();
14060 break;
14061
14062 case ID_STKDN:
14063 DoCanvasStackDelta(-1);
14064 DoCanvasUpdate();
14065 break;
14066
14067 case ID_FOLLOW: {
14068 TogglebFollow();
14069 break;
14070 }
14071
14072 case ID_CURRENT: {
14073 ShowCurrents(!GetbShowCurrent());
14074 ReloadVP();
14075 Refresh(false);
14076 break;
14077 }
14078
14079 case ID_TIDE: {
14080 ShowTides(!GetbShowTide());
14081 ReloadVP();
14082 Refresh(false);
14083 break;
14084 }
14085
14086 case ID_ROUTE: {
14087 if (0 == m_routeState) {
14088 StartRoute();
14089 } else {
14090 FinishRoute();
14091 }
14092
14093#ifdef __ANDROID__
14094 androidSetRouteAnnunciator(m_routeState == 1);
14095#endif
14096 break;
14097 }
14098
14099 case ID_AIS: {
14100 SetAISCanvasDisplayStyle(-1);
14101 break;
14102 }
14103
14104 default:
14105 break;
14106 }
14107
14108 // And then let gFrame handle the rest....
14109 event.Skip();
14110}
14111
14112void ChartCanvas::SetShowAIS(bool show) {
14113 m_bShowAIS = show;
14114 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14115 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14116}
14117
14118void ChartCanvas::SetAttenAIS(bool show) {
14119 m_bShowAISScaled = show;
14120 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14121 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14122}
14123
14124void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14125 // make some arrays to hold the dfferences between cycle steps
14126 // show all, scaled, hide all
14127 bool bShowAIS_Array[3] = {true, true, false};
14128 bool bShowScaled_Array[3] = {false, true, true};
14129 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14130 _("Attenuate less critical AIS targets"),
14131 _("Hide AIS Targets")};
14132 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14133 int ArraySize = 3;
14134 int AIS_Toolbar_Switch = 0;
14135 if (StyleIndx == -1) { // -1 means coming from toolbar button
14136 // find current state of switch
14137 for (int i = 1; i < ArraySize; i++) {
14138 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14139 (bShowScaled_Array[i] == m_bShowAISScaled))
14140 AIS_Toolbar_Switch = i;
14141 }
14142 AIS_Toolbar_Switch++; // we did click so continu with next item
14143 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14144 AIS_Toolbar_Switch++;
14145
14146 } else { // coming from menu bar.
14147 AIS_Toolbar_Switch = StyleIndx;
14148 }
14149 // make sure we are not above array
14150 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14151
14152 int AIS_Toolbar_Switch_Next =
14153 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14154 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14155 AIS_Toolbar_Switch_Next++;
14156 if (AIS_Toolbar_Switch_Next >= ArraySize)
14157 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14158
14159 // Set found values to global and member variables
14160 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14161 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14162}
14163
14164void ChartCanvas::TouchAISToolActive() {}
14165
14166void ChartCanvas::UpdateAISTBTool() {}
14167
14168//---------------------------------------------------------------------------------
14169//
14170// Compass/GPS status icon support
14171//
14172//---------------------------------------------------------------------------------
14173
14174void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14175 // Look for change in overlap or positions
14176 bool b_update = false;
14177 int cc1_edge_comp = 2;
14178 wxRect rect = m_Compass->GetRect();
14179 wxSize parent_size = GetSize();
14180
14181 parent_size *= m_displayScale;
14182
14183 // check to see if it would overlap if it was in its home position (upper
14184 // right)
14185 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14186 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14187 wxRect compass_rect(compass_pt, rect.GetSize());
14188
14189 m_Compass->Move(compass_pt);
14190
14191 if (m_Compass && m_Compass->IsShown())
14192 m_Compass->UpdateStatus(b_force_new | b_update);
14193
14194 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14195 scaler = wxMax(scaler, 1.0);
14196 wxPoint note_point = wxPoint(
14197 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14198 if (m_notification_button) {
14199 m_notification_button->Move(note_point);
14200 m_notification_button->UpdateStatus();
14201 }
14202
14203 if (b_force_new | b_update) Refresh();
14204}
14205
14206void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14207 ChartTypeEnum New_Type,
14208 ChartFamilyEnum New_Family) {
14209 if (!GetpCurrentStack()) return;
14210 if (!ChartData) return;
14211
14212 if (index < GetpCurrentStack()->nEntry) {
14213 // Open the new chart
14214 ChartBase *pTentative_Chart;
14215 pTentative_Chart = ChartData->OpenStackChartConditional(
14216 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14217
14218 if (pTentative_Chart) {
14219 if (m_singleChart) m_singleChart->Deactivate();
14220
14221 m_singleChart = pTentative_Chart;
14222 m_singleChart->Activate();
14223
14224 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14225 GetpCurrentStack(), m_singleChart->GetFullPath());
14226 }
14227
14228 // Setup the view
14229 double zLat, zLon;
14230 if (m_bFollow) {
14231 zLat = gLat;
14232 zLon = gLon;
14233 } else {
14234 zLat = m_vLat;
14235 zLon = m_vLon;
14236 }
14237
14238 double best_scale_ppm = GetBestVPScale(m_singleChart);
14239 double rotation = GetVPRotation();
14240 double oldskew = GetVPSkew();
14241 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14242
14243 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14244 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14245 if (fabs(newskew) > 0.0001) rotation = newskew;
14246 }
14247
14248 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14249
14250 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14251 }
14252
14253 // refresh Piano
14254 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14255 if (idx < 0) return;
14256
14257 std::vector<int> piano_active_chart_index_array;
14258 piano_active_chart_index_array.push_back(
14259 GetpCurrentStack()->GetCurrentEntrydbIndex());
14260 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14261}
14262
14263void ChartCanvas::SelectdbChart(int dbindex) {
14264 if (!GetpCurrentStack()) return;
14265 if (!ChartData) return;
14266
14267 if (dbindex >= 0) {
14268 // Open the new chart
14269 ChartBase *pTentative_Chart;
14270 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14271
14272 if (pTentative_Chart) {
14273 if (m_singleChart) m_singleChart->Deactivate();
14274
14275 m_singleChart = pTentative_Chart;
14276 m_singleChart->Activate();
14277
14278 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14279 GetpCurrentStack(), m_singleChart->GetFullPath());
14280 }
14281
14282 // Setup the view
14283 double zLat, zLon;
14284 if (m_bFollow) {
14285 zLat = gLat;
14286 zLon = gLon;
14287 } else {
14288 zLat = m_vLat;
14289 zLon = m_vLon;
14290 }
14291
14292 double best_scale_ppm = GetBestVPScale(m_singleChart);
14293
14294 if (m_singleChart)
14295 SetViewPoint(zLat, zLon, best_scale_ppm,
14296 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14297
14298 // SetChartUpdatePeriod( );
14299
14300 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14301 }
14302
14303 // TODO refresh_Piano();
14304}
14305
14306void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14307 double target_scale = GetVP().view_scale_ppm;
14308
14309 if (!GetQuiltMode()) {
14310 if (GetpCurrentStack()) {
14311 int stack_index = -1;
14312 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14313 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14314 if (check_dbIndex < 0) continue;
14315 const ChartTableEntry &cte =
14316 ChartData->GetChartTableEntry(check_dbIndex);
14317 if (type == cte.GetChartType()) {
14318 stack_index = i;
14319 break;
14320 } else if (family == cte.GetChartFamily()) {
14321 stack_index = i;
14322 break;
14323 }
14324 }
14325
14326 if (stack_index >= 0) {
14327 SelectChartFromStack(stack_index);
14328 }
14329 }
14330 } else {
14331 int sel_dbIndex = -1;
14332 std::vector<int> piano_chart_index_array =
14333 GetQuiltExtendedStackdbIndexArray();
14334 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14335 int check_dbIndex = piano_chart_index_array[i];
14336 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14337 if (type == cte.GetChartType()) {
14338 if (IsChartQuiltableRef(check_dbIndex)) {
14339 sel_dbIndex = check_dbIndex;
14340 break;
14341 }
14342 } else if (family == cte.GetChartFamily()) {
14343 if (IsChartQuiltableRef(check_dbIndex)) {
14344 sel_dbIndex = check_dbIndex;
14345 break;
14346 }
14347 }
14348 }
14349
14350 if (sel_dbIndex >= 0) {
14351 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14352 // Re-qualify the quilt reference chart selection
14353 AdjustQuiltRefChart();
14354 }
14355
14356 // Now reset the scale to the target...
14357 SetVPScale(target_scale);
14358 }
14359
14360 SetQuiltChartHiLiteIndex(-1);
14361
14362 ReloadVP();
14363}
14364
14365bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14366 return std::find(m_tile_yesshow_index_array.begin(),
14367 m_tile_yesshow_index_array.end(),
14368 index) != m_tile_yesshow_index_array.end();
14369}
14370
14371bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14372 return std::find(m_tile_noshow_index_array.begin(),
14373 m_tile_noshow_index_array.end(),
14374 index) != m_tile_noshow_index_array.end();
14375}
14376
14377void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14378 if (std::find(m_tile_noshow_index_array.begin(),
14379 m_tile_noshow_index_array.end(),
14380 index) == m_tile_noshow_index_array.end()) {
14381 m_tile_noshow_index_array.push_back(index);
14382 }
14383}
14384
14385//-------------------------------------------------------------------------------------------------------
14386//
14387// Piano support
14388//
14389//-------------------------------------------------------------------------------------------------------
14390
14391void ChartCanvas::HandlePianoClick(
14392 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14393 if (g_options && g_options->IsShown())
14394 return; // Piano might be invalid due to chartset updates.
14395 if (!m_pCurrentStack) return;
14396 if (!ChartData) return;
14397
14398 // stop movement or on slow computer we may get something like :
14399 // zoom out with the wheel (timer is set)
14400 // quickly click and display a chart, which may zoom in
14401 // but the delayed timer fires first and it zooms out again!
14402 StopMovement();
14403
14404 // When switching by piano key click, we may appoint the new target chart to
14405 // be any chart in the composite array.
14406 // As an improvement to UX, find the chart that is "closest" to the current
14407 // vp,
14408 // and select that chart. This will cause a jump to the centroid of that
14409 // chart
14410
14411 double distance = 25000; // RTW
14412 int closest_index = -1;
14413 for (int chart_index : selected_dbIndex_array) {
14414 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14415 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14416 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14417
14418 // measure distance as Manhattan style
14419 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14420 if (test_distance < distance) {
14421 distance = test_distance;
14422 closest_index = chart_index;
14423 }
14424 }
14425
14426 int selected_dbIndex = selected_dbIndex_array[0];
14427 if (closest_index >= 0) selected_dbIndex = closest_index;
14428
14429 if (!GetQuiltMode()) {
14430 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14431 if (IsChartQuiltableRef(selected_dbIndex)) {
14432 ToggleCanvasQuiltMode();
14433 SelectQuiltRefdbChart(selected_dbIndex);
14434 m_bpersistent_quilt = false;
14435 } else {
14436 SelectChartFromStack(selected_index);
14437 }
14438 } else {
14439 SelectChartFromStack(selected_index);
14440 g_sticky_chart = selected_dbIndex;
14441 }
14442
14443 if (m_singleChart)
14444 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14445 } else {
14446 // Handle MBTiles overlays first
14447 // Left click simply toggles the noshow array index entry
14448 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14449 bool bfound = false;
14450 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14451 if (m_tile_noshow_index_array[i] ==
14452 selected_dbIndex) { // chart is in the noshow list
14453 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14454 i); // erase it
14455 bfound = true;
14456 break;
14457 }
14458 }
14459 if (!bfound) {
14460 m_tile_noshow_index_array.push_back(selected_dbIndex);
14461 }
14462
14463 // If not already present, add this tileset to the "yes_show" array.
14464 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14465 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14466 }
14467
14468 else {
14469 if (IsChartQuiltableRef(selected_dbIndex)) {
14470 // if( ChartData ) ChartData->PurgeCache();
14471
14472 // If the chart is a vector chart, and of very large scale,
14473 // then we had better set the new scale directly to avoid excessive
14474 // underzoom on, eg, Inland ENCs
14475 bool set_scale = false;
14476 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14477 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14478 set_scale = true;
14479 }
14480 }
14481
14482 if (!set_scale) {
14483 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14484 } else {
14485 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14486
14487 // Adjust scale so that the selected chart is underzoomed/overzoomed
14488 // by a controlled amount
14489 ChartBase *pc =
14490 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14491 if (pc) {
14492 double proposed_scale_onscreen =
14494
14495 if (g_bPreserveScaleOnX) {
14496 proposed_scale_onscreen =
14497 wxMin(proposed_scale_onscreen,
14498 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14499 GetCanvasWidth()));
14500 } else {
14501 proposed_scale_onscreen =
14502 wxMin(proposed_scale_onscreen,
14503 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14504 GetCanvasWidth()));
14505
14506 proposed_scale_onscreen =
14507 wxMax(proposed_scale_onscreen,
14508 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14510 }
14511
14512 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14513 }
14514 }
14515 } else {
14516 ToggleCanvasQuiltMode();
14517 SelectdbChart(selected_dbIndex);
14518 m_bpersistent_quilt = true;
14519 }
14520 }
14521 }
14522
14523 SetQuiltChartHiLiteIndex(-1);
14524 // update the state of the menu items (checkmarks etc)
14525 top_frame::Get()->UpdateGlobalMenuItems();
14526 HideChartInfoWindow();
14527 DoCanvasUpdate();
14528 ReloadVP(); // Pick up the new selections
14529}
14530
14531void ChartCanvas::HandlePianoRClick(
14532 int x, int y, int selected_index,
14533 const std::vector<int> &selected_dbIndex_array) {
14534 if (g_options && g_options->IsShown())
14535 return; // Piano might be invalid due to chartset updates.
14536 if (!GetpCurrentStack()) return;
14537
14538 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14539 UpdateCanvasControlBar();
14540
14541 SetQuiltChartHiLiteIndex(-1);
14542}
14543
14544void ChartCanvas::HandlePianoRollover(
14545 int selected_index, const std::vector<int> &selected_dbIndex_array,
14546 int n_charts, int scale) {
14547 if (g_options && g_options->IsShown())
14548 return; // Piano might be invalid due to chartset updates.
14549 if (!GetpCurrentStack()) return;
14550 if (!ChartData) return;
14551
14552 if (ChartData->IsBusy()) return;
14553
14554 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14555
14556 if (!GetQuiltMode()) {
14557 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14558 } else {
14559 // Select the correct vector
14560 std::vector<int> piano_chart_index_array;
14561 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14562 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14563 if ((GetpCurrentStack()->nEntry > 1) ||
14564 (piano_chart_index_array.size() >= 1)) {
14565 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14566
14567 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14568 ReloadVP(false); // no VP adjustment allowed
14569 } else if (GetpCurrentStack()->nEntry == 1) {
14570 const ChartTableEntry &cte =
14571 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14572 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14573 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14574 ReloadVP(false);
14575 } else if ((-1 == selected_index) &&
14576 (0 == selected_dbIndex_array.size())) {
14577 ShowChartInfoWindow(key_location.x, -1);
14578 }
14579 }
14580 } else {
14581 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14582
14583 if ((GetpCurrentStack()->nEntry > 1) ||
14584 (piano_chart_index_array.size() >= 1)) {
14585 if (n_charts > 1)
14586 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14587 selected_dbIndex_array);
14588 else if (n_charts == 1)
14589 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14590
14591 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14592 ReloadVP(false); // no VP adjustment allowed
14593 }
14594 }
14595 }
14596}
14597
14598void ChartCanvas::ClearPianoRollover() {
14599 ClearQuiltChartHiLiteIndexArray();
14600 ShowChartInfoWindow(0, -1);
14601 std::vector<int> vec;
14602 ShowCompositeInfoWindow(0, 0, 0, vec);
14603 ReloadVP(false);
14604}
14605
14606void ChartCanvas::UpdateCanvasControlBar() {
14607 if (m_pianoFrozen) return;
14608
14609 if (!GetpCurrentStack()) return;
14610 if (!ChartData) return;
14611 if (!g_bShowChartBar) return;
14612
14613 int sel_type = -1;
14614 int sel_family = -1;
14615
14616 std::vector<int> piano_chart_index_array;
14617 std::vector<int> empty_piano_chart_index_array;
14618
14619 wxString old_hash = m_Piano->GetStoredHash();
14620
14621 if (GetQuiltMode()) {
14622 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14623 GetQuiltFullScreendbIndexArray());
14624
14625 std::vector<int> piano_active_chart_index_array =
14626 GetQuiltCandidatedbIndexArray();
14627 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14628
14629 std::vector<int> piano_eclipsed_chart_index_array =
14630 GetQuiltEclipsedStackdbIndexArray();
14631 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14632
14633 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14634 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14635
14636 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14637 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14638 } else {
14639 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14640 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14641 // TODO refresh_Piano();
14642
14643 if (m_singleChart) {
14644 sel_type = m_singleChart->GetChartType();
14645 sel_family = m_singleChart->GetChartFamily();
14646 }
14647 }
14648
14649 // Set up the TMerc and Skew arrays
14650 std::vector<int> piano_skew_chart_index_array;
14651 std::vector<int> piano_tmerc_chart_index_array;
14652 std::vector<int> piano_poly_chart_index_array;
14653
14654 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14655 const ChartTableEntry &ctei =
14656 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14657 double skew_norm = ctei.GetChartSkew();
14658 if (skew_norm > 180.) skew_norm -= 360.;
14659
14660 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14661 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14662
14663 // Polyconic skewed charts should show as skewed
14664 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14665 if (fabs(skew_norm) > 1.)
14666 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14667 else
14668 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14669 } else if (fabs(skew_norm) > 1.)
14670 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14671 }
14672 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14673 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14674 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14675
14676 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14677 if (new_hash != old_hash) {
14678 m_Piano->FormatKeys();
14679 HideChartInfoWindow();
14680 m_Piano->ResetRollover();
14681 SetQuiltChartHiLiteIndex(-1);
14682 m_brepaint_piano = true;
14683 }
14684
14685 // Create a bitmask int that describes what Family/Type of charts are shown in
14686 // the bar, and notify the platform.
14687 int mask = 0;
14688 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14689 const ChartTableEntry &ctei =
14690 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14691 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14692 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14693 if (e == CHART_FAMILY_RASTER) mask |= 1;
14694 if (e == CHART_FAMILY_VECTOR) {
14695 if (t == CHART_TYPE_CM93COMP)
14696 mask |= 4;
14697 else
14698 mask |= 2;
14699 }
14700 }
14701
14702 wxString s_indicated;
14703 if (sel_type == CHART_TYPE_CM93COMP)
14704 s_indicated = "cm93";
14705 else {
14706 if (sel_family == CHART_FAMILY_RASTER)
14707 s_indicated = "raster";
14708 else if (sel_family == CHART_FAMILY_VECTOR)
14709 s_indicated = "vector";
14710 }
14711
14712 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14713}
14714
14715void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14716
14717void ChartCanvas::PianoPopupMenu(
14718 int x, int y, int selected_index,
14719 const std::vector<int> &selected_dbIndex_array) {
14720 if (!GetpCurrentStack()) return;
14721
14722 // No context menu if quilting is disabled
14723 if (!GetQuiltMode()) return;
14724
14725 m_piano_ctx_menu = new wxMenu();
14726
14727 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14728 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14729 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14730 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14731 } else {
14732 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14733 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14734 // wxEVT_COMMAND_MENU_SELECTED,
14735 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14736
14737 menu_selected_dbIndex = selected_dbIndex_array[0];
14738 menu_selected_index = selected_index;
14739
14740 // Search the no-show array
14741 bool b_is_in_noshow = false;
14742 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14743 if (m_quilt_noshow_index_array[i] ==
14744 menu_selected_dbIndex) // chart is in the noshow list
14745 {
14746 b_is_in_noshow = true;
14747 break;
14748 }
14749 }
14750
14751 if (b_is_in_noshow) {
14752 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14753 _("Show This Chart"));
14754 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14755 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14756 } else if (GetpCurrentStack()->nEntry > 1) {
14757 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14758 _("Hide This Chart"));
14759 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14760 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14761 }
14762 }
14763
14764 wxPoint pos = wxPoint(x, y - 30);
14765
14766 // Invoke the drop-down menu
14767 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14768 PopupMenu(m_piano_ctx_menu, pos);
14769
14770 delete m_piano_ctx_menu;
14771 m_piano_ctx_menu = NULL;
14772
14773 HideChartInfoWindow();
14774 m_Piano->ResetRollover();
14775
14776 SetQuiltChartHiLiteIndex(-1);
14777 ClearQuiltChartHiLiteIndexArray();
14778
14779 ReloadVP();
14780}
14781
14782void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14783 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14784 if (m_quilt_noshow_index_array[i] ==
14785 menu_selected_dbIndex) // chart is in the noshow list
14786 {
14787 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14788 break;
14789 }
14790 }
14791}
14792
14793void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14794 if (!GetpCurrentStack()) return;
14795 if (!ChartData) return;
14796
14797 RemoveChartFromQuilt(menu_selected_dbIndex);
14798
14799 // It could happen that the chart being disabled is the reference
14800 // chart....
14801 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14802 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14803
14804 int i = menu_selected_index + 1; // select next smaller scale chart
14805 bool b_success = false;
14806 while (i < GetpCurrentStack()->nEntry - 1) {
14807 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14808 if (type == ChartData->GetDBChartType(dbIndex)) {
14809 SelectQuiltRefChart(i);
14810 b_success = true;
14811 break;
14812 }
14813 i++;
14814 }
14815
14816 // If that did not work, try to select the next larger scale compatible
14817 // chart
14818 if (!b_success) {
14819 i = menu_selected_index - 1;
14820 while (i > 0) {
14821 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14822 if (type == ChartData->GetDBChartType(dbIndex)) {
14823 SelectQuiltRefChart(i);
14824 b_success = true;
14825 break;
14826 }
14827 i--;
14828 }
14829 }
14830 }
14831}
14832
14833void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14834 // Remove the item from the list (if it appears) to avoid multiple addition
14835 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14836 if (m_quilt_noshow_index_array[i] ==
14837 dbIndex) // chart is already in the noshow list
14838 {
14839 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14840 break;
14841 }
14842 }
14843
14844 m_quilt_noshow_index_array.push_back(dbIndex);
14845}
14846
14847bool ChartCanvas::UpdateS52State() {
14848 bool retval = false;
14849
14850 if (ps52plib) {
14851 ps52plib->SetShowS57Text(m_encShowText);
14852 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14853 ps52plib->m_bShowSoundg = m_encShowDepth;
14854 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14855 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14856
14857 // Lights
14858 if (!m_encShowLights) // On, going off
14859 ps52plib->AddObjNoshow("LIGHTS");
14860 else // Off, going on
14861 ps52plib->RemoveObjNoshow("LIGHTS");
14862 ps52plib->SetLightsOff(!m_encShowLights);
14863 ps52plib->m_bExtendLightSectors = true;
14864
14865 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14866 ps52plib->SetAnchorOn(m_encShowAnchor);
14867 ps52plib->SetQualityOfData(m_encShowDataQual);
14868 }
14869
14870 return retval;
14871}
14872
14873void ChartCanvas::SetShowENCDataQual(bool show) {
14874 m_encShowDataQual = show;
14875 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14876 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14877
14878 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14879}
14880
14881void ChartCanvas::SetShowENCText(bool show) {
14882 m_encShowText = show;
14883 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14884 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14885
14886 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14887}
14888
14889void ChartCanvas::SetENCDisplayCategory(int category) {
14890 m_encDisplayCategory = category;
14891 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14892}
14893
14894void ChartCanvas::SetShowENCDepth(bool show) {
14895 m_encShowDepth = show;
14896 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14897 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14898
14899 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14900}
14901
14902void ChartCanvas::SetShowENCLightDesc(bool show) {
14903 m_encShowLightDesc = show;
14904 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14905 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14906
14907 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14908}
14909
14910void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14911 m_encShowBuoyLabels = show;
14912 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14913}
14914
14915void ChartCanvas::SetShowENCLights(bool show) {
14916 m_encShowLights = show;
14917 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14918 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14919
14920 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14921}
14922
14923void ChartCanvas::SetShowENCAnchor(bool show) {
14924 m_encShowAnchor = show;
14925 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14926 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14927
14928 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14929}
14930
14931wxRect ChartCanvas::GetMUIBarRect() {
14932 wxRect rv;
14933 if (m_muiBar) {
14934 rv = m_muiBar->GetRect();
14935 }
14936
14937 return rv;
14938}
14939
14940void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14941 if (!GetAlertString().IsEmpty()) {
14942 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14943 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14944
14945 dc.SetFont(*pfont);
14946 dc.SetPen(*wxTRANSPARENT_PEN);
14947
14948 dc.SetBrush(wxColour(243, 229, 47));
14949 int w, h;
14950 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14951 h += 2;
14952 // int yp = vp.pix_height - 20 - h;
14953
14954 wxRect sbr = GetScaleBarRect();
14955 int xp = sbr.x + sbr.width + 10;
14956 int yp = (sbr.y + sbr.height) - h;
14957
14958 int wdraw = w + 10;
14959 dc.DrawRectangle(xp, yp, wdraw, h);
14960 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14961 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14962 }
14963}
14964
14965//--------------------------------------------------------------------------------------------------------
14966// Screen Brightness Control Support Routines
14967//
14968//--------------------------------------------------------------------------------------------------------
14969
14970#ifdef __UNIX__
14971#define BRIGHT_XCALIB
14972#define __OPCPN_USEICC__
14973#endif
14974
14975#ifdef __OPCPN_USEICC__
14976int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14977 double co_green, double co_blue);
14978
14979wxString temp_file_name;
14980#endif
14981
14982#if 0
14983class ocpnCurtain: public wxDialog
14984{
14985 DECLARE_CLASS( ocpnCurtain )
14986 DECLARE_EVENT_TABLE()
14987
14988public:
14989 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14990 ~ocpnCurtain( );
14991 bool ProcessEvent(wxEvent& event);
14992
14993};
14994
14995IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14996
14997BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14998END_EVENT_TABLE()
14999
15000ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15001{
15002 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15003}
15004
15005ocpnCurtain::~ocpnCurtain()
15006{
15007}
15008
15009bool ocpnCurtain::ProcessEvent(wxEvent& event)
15010{
15011 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15012 return GetParent()->GetEventHandler()->ProcessEvent(event);
15013}
15014#endif
15015
15016#ifdef _WIN32
15017#include <windows.h>
15018
15019HMODULE hGDI32DLL;
15020typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15021typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15022SetDeviceGammaRamp_ptr_type
15023 g_pSetDeviceGammaRamp; // the API entry points in the dll
15024GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15025
15026WORD *g_pSavedGammaMap;
15027
15028#endif
15029
15030int InitScreenBrightness() {
15031#ifdef _WIN32
15032#ifdef ocpnUSE_GL
15033 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15034 HDC hDC;
15035 BOOL bbr;
15036
15037 if (NULL == hGDI32DLL) {
15038 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15039
15040 if (NULL != hGDI32DLL) {
15041 // Get the entry points of the required functions
15042 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15043 hGDI32DLL, "SetDeviceGammaRamp");
15044 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15045 hGDI32DLL, "GetDeviceGammaRamp");
15046
15047 // If the functions are not found, unload the DLL and return false
15048 if ((NULL == g_pSetDeviceGammaRamp) ||
15049 (NULL == g_pGetDeviceGammaRamp)) {
15050 FreeLibrary(hGDI32DLL);
15051 hGDI32DLL = NULL;
15052 return 0;
15053 }
15054 }
15055 }
15056
15057 // Interface is ready, so....
15058 // Get some storage
15059 if (!g_pSavedGammaMap) {
15060 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15061
15062 hDC = GetDC(NULL); // Get the full screen DC
15063 bbr = g_pGetDeviceGammaRamp(
15064 hDC, g_pSavedGammaMap); // Get the existing ramp table
15065 ReleaseDC(NULL, hDC); // Release the DC
15066 }
15067
15068 // On Windows hosts, try to adjust the registry to allow full range
15069 // setting of Gamma table This is an undocumented Windows hack.....
15070 wxRegKey *pRegKey = new wxRegKey(
15071 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15072 "NT\\CurrentVersion\\ICM");
15073 if (!pRegKey->Exists()) pRegKey->Create();
15074 pRegKey->SetValue("GdiIcmGammaRange", 256);
15075
15076 g_brightness_init = true;
15077 return 1;
15078 }
15079#endif
15080
15081 {
15082 if (NULL == g_pcurtain) {
15083 if (top_frame::Get()->CanSetTransparent()) {
15084 // Build the curtain window
15085 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15086 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15087 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15088 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15089
15090 // g_pcurtain = new ocpnCurtain(gFrame,
15091 // wxPoint(0,0),::wxGetDisplaySize(),
15092 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15093 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15094
15095 g_pcurtain->Hide();
15096
15097 HWND hWnd = GetHwndOf(g_pcurtain);
15098 SetWindowLong(hWnd, GWL_EXSTYLE,
15099 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15100 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15101 g_pcurtain->SetTransparent(0);
15102
15103 g_pcurtain->Maximize();
15104 g_pcurtain->Show();
15105
15106 // All of this is obtuse, but necessary for Windows...
15107 g_pcurtain->Enable();
15108 g_pcurtain->Disable();
15109
15110 top_frame::Get()->Disable();
15111 top_frame::Get()->Enable();
15112 // SetFocus();
15113 }
15114 }
15115 g_brightness_init = true;
15116
15117 return 1;
15118 }
15119#else
15120 // Look for "xcalib" application
15121 wxString cmd("xcalib -version");
15122
15123 wxArrayString output;
15124 long r = wxExecute(cmd, output);
15125 if (0 != r)
15126 wxLogMessage(
15127 " External application \"xcalib\" not found. Screen brightness "
15128 "not changed.");
15129
15130 g_brightness_init = true;
15131 return 0;
15132#endif
15133}
15134
15135int RestoreScreenBrightness() {
15136#ifdef _WIN32
15137
15138 if (g_pSavedGammaMap) {
15139 HDC hDC = GetDC(NULL); // Get the full screen DC
15140 g_pSetDeviceGammaRamp(hDC,
15141 g_pSavedGammaMap); // Restore the saved ramp table
15142 ReleaseDC(NULL, hDC); // Release the DC
15143
15144 free(g_pSavedGammaMap);
15145 g_pSavedGammaMap = NULL;
15146 }
15147
15148 if (g_pcurtain) {
15149 g_pcurtain->Close();
15150 g_pcurtain->Destroy();
15151 g_pcurtain = NULL;
15152 }
15153
15154 g_brightness_init = false;
15155 return 1;
15156
15157#endif
15158
15159#ifdef BRIGHT_XCALIB
15160 if (g_brightness_init) {
15161 wxString cmd;
15162 cmd = "xcalib -clear";
15163 wxExecute(cmd, wxEXEC_ASYNC);
15164 g_brightness_init = false;
15165 }
15166
15167 return 1;
15168#endif
15169
15170 return 0;
15171}
15172
15173// Set brightness. [0..100]
15174int SetScreenBrightness(int brightness) {
15175#ifdef _WIN32
15176
15177 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15178 // some (most modern?) versions of gdi32.dll Load the required library dll,
15179 // if not already in place
15180#ifdef ocpnUSE_GL
15181 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15182 if (g_pcurtain) {
15183 g_pcurtain->Close();
15184 g_pcurtain->Destroy();
15185 g_pcurtain = NULL;
15186 }
15187
15188 InitScreenBrightness();
15189
15190 if (NULL == hGDI32DLL) {
15191 // Unicode stuff.....
15192 wchar_t wdll_name[80];
15193 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15194 LPCWSTR cstr = wdll_name;
15195
15196 hGDI32DLL = LoadLibrary(cstr);
15197
15198 if (NULL != hGDI32DLL) {
15199 // Get the entry points of the required functions
15200 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15201 hGDI32DLL, "SetDeviceGammaRamp");
15202 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15203 hGDI32DLL, "GetDeviceGammaRamp");
15204
15205 // If the functions are not found, unload the DLL and return false
15206 if ((NULL == g_pSetDeviceGammaRamp) ||
15207 (NULL == g_pGetDeviceGammaRamp)) {
15208 FreeLibrary(hGDI32DLL);
15209 hGDI32DLL = NULL;
15210 return 0;
15211 }
15212 }
15213 }
15214
15215 HDC hDC = GetDC(NULL); // Get the full screen DC
15216
15217 /*
15218 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15219 if (cmcap != CM_GAMMA_RAMP)
15220 {
15221 wxLogMessage(" Video hardware does not support brightness control by
15222 gamma ramp adjustment."); return false;
15223 }
15224 */
15225
15226 int increment = brightness * 256 / 100;
15227
15228 // Build the Gamma Ramp table
15229 WORD GammaTable[3][256];
15230
15231 int table_val = 0;
15232 for (int i = 0; i < 256; i++) {
15233 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15234 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15235 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15236
15237 table_val += increment;
15238
15239 if (table_val > 65535) table_val = 65535;
15240 }
15241
15242 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15243 ReleaseDC(NULL, hDC); // Release the DC
15244
15245 return 1;
15246 }
15247#endif
15248
15249 {
15250 if (g_pSavedGammaMap) {
15251 HDC hDC = GetDC(NULL); // Get the full screen DC
15252 g_pSetDeviceGammaRamp(hDC,
15253 g_pSavedGammaMap); // Restore the saved ramp table
15254 ReleaseDC(NULL, hDC); // Release the DC
15255 }
15256
15257 if (brightness < 100) {
15258 if (NULL == g_pcurtain) InitScreenBrightness();
15259
15260 if (g_pcurtain) {
15261 int sbrite = wxMax(1, brightness);
15262 sbrite = wxMin(100, sbrite);
15263
15264 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15265 }
15266 } else {
15267 if (g_pcurtain) {
15268 g_pcurtain->Close();
15269 g_pcurtain->Destroy();
15270 g_pcurtain = NULL;
15271 }
15272 }
15273
15274 return 1;
15275 }
15276
15277#endif
15278
15279#ifdef BRIGHT_XCALIB
15280
15281 if (!g_brightness_init) {
15282 last_brightness = 100;
15283 g_brightness_init = true;
15284 temp_file_name = wxFileName::CreateTempFileName("");
15285 InitScreenBrightness();
15286 }
15287
15288#ifdef __OPCPN_USEICC__
15289 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15290 // desired, and then activate this temporary profile using xcalib <filename>
15291 if (!CreateSimpleICCProfileFile(
15292 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15293 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15294 wxString cmd("xcalib ");
15295 cmd += temp_file_name;
15296
15297 wxExecute(cmd, wxEXEC_ASYNC);
15298 }
15299
15300#else
15301 // Or, use "xcalib -co" to set overall contrast value
15302 // This is not as nice, since the -co parameter wants to be a fraction of
15303 // the current contrast, and values greater than 100 are not allowed. As a
15304 // result, increases of contrast must do a "-clear" step first, which
15305 // produces objectionable flashing.
15306 if (brightness > last_brightness) {
15307 wxString cmd;
15308 cmd = "xcalib -clear";
15309 wxExecute(cmd, wxEXEC_ASYNC);
15310
15311 ::wxMilliSleep(10);
15312
15313 int brite_adj = wxMax(1, brightness);
15314 cmd.Printf("xcalib -co %2d -a", brite_adj);
15315 wxExecute(cmd, wxEXEC_ASYNC);
15316 } else {
15317 int brite_adj = wxMax(1, brightness);
15318 int factor = (brite_adj * 100) / last_brightness;
15319 factor = wxMax(1, factor);
15320 wxString cmd;
15321 cmd.Printf("xcalib -co %2d -a", factor);
15322 wxExecute(cmd, wxEXEC_ASYNC);
15323 }
15324
15325#endif
15326
15327 last_brightness = brightness;
15328
15329#endif
15330
15331 return 0;
15332}
15333
15334#ifdef __OPCPN_USEICC__
15335
15336#define MLUT_TAG 0x6d4c5554L
15337#define VCGT_TAG 0x76636774L
15338
15339int GetIntEndian(unsigned char *s) {
15340 int ret;
15341 unsigned char *p;
15342 int i;
15343
15344 p = (unsigned char *)&ret;
15345
15346 if (1)
15347 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15348 else
15349 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15350
15351 return ret;
15352}
15353
15354unsigned short GetShortEndian(unsigned char *s) {
15355 unsigned short ret;
15356 unsigned char *p;
15357 int i;
15358
15359 p = (unsigned char *)&ret;
15360
15361 if (1)
15362 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15363 else
15364 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15365
15366 return ret;
15367}
15368
15369// Create a very simple Gamma correction file readable by xcalib
15370int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15371 double co_green, double co_blue) {
15372 FILE *fp;
15373
15374 if (file_name) {
15375 fp = fopen(file_name, "wb");
15376 if (!fp) return -1; /* file can not be created */
15377 } else
15378 return -1; /* filename char pointer not valid */
15379
15380 // Write header
15381 char header[128];
15382 for (int i = 0; i < 128; i++) header[i] = 0;
15383
15384 fwrite(header, 128, 1, fp);
15385
15386 // Num tags
15387 int numTags0 = 1;
15388 int numTags = GetIntEndian((unsigned char *)&numTags0);
15389 fwrite(&numTags, 1, 4, fp);
15390
15391 int tagName0 = VCGT_TAG;
15392 int tagName = GetIntEndian((unsigned char *)&tagName0);
15393 fwrite(&tagName, 1, 4, fp);
15394
15395 int tagOffset0 = 128 + 4 * sizeof(int);
15396 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15397 fwrite(&tagOffset, 1, 4, fp);
15398
15399 int tagSize0 = 1;
15400 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15401 fwrite(&tagSize, 1, 4, fp);
15402
15403 fwrite(&tagName, 1, 4, fp); // another copy of tag
15404
15405 fwrite(&tagName, 1, 4, fp); // dummy
15406
15407 // Table type
15408
15409 /* VideoCardGammaTable (The simplest type) */
15410 int gammatype0 = 0;
15411 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15412 fwrite(&gammatype, 1, 4, fp);
15413
15414 int numChannels0 = 3;
15415 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15416 fwrite(&numChannels, 1, 2, fp);
15417
15418 int numEntries0 = 256;
15419 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15420 fwrite(&numEntries, 1, 2, fp);
15421
15422 int entrySize0 = 1;
15423 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15424 fwrite(&entrySize, 1, 2, fp);
15425
15426 unsigned char ramp[256];
15427
15428 // Red ramp
15429 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15430 fwrite(ramp, 256, 1, fp);
15431
15432 // Green ramp
15433 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15434 fwrite(ramp, 256, 1, fp);
15435
15436 // Blue ramp
15437 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15438 fwrite(ramp, 256, 1, fp);
15439
15440 fclose(fp);
15441
15442 return 0;
15443}
15444#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:868
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:13807
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:11851
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:13766
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:13805
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:13762
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:10260
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:765
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:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h: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.