OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/ocpn_utils.h"
58#include "model/own_ship.h"
59#include "model/plugin_comm.h"
60#include "model/route.h"
61#include "model/routeman.h"
62#include "model/select.h"
63#include "model/select_item.h"
64#include "model/track.h"
65#include "model/ocpn_utils.h"
66
67#include "ais.h"
70#include "canvas_config.h"
71#include "canvas_menu.h"
72#include "canvas_options.h"
73#include "chartdb.h"
74#include "chartimg.h"
75#include "chcanv.h"
76#include "ch_info_win.h"
77#include "cm93.h" // for chart outline draw
78#include "compass.h"
79#include "concanv.h"
80#include "detail_slider.h"
81#include "displays.h"
82#include "hotkeys_dlg.h"
83#include "font_mgr.h"
84#include "gl_texture_descr.h"
85#include "go_to_position_dlg.h"
86#include "gshhs.h"
87#include "ienc_toolbar.h"
88#include "kml.h"
89#include "line_clip.h"
90#include "mark_info.h"
91#include "mbtiles.h"
92#include "mui_bar.h"
93#include "navutil.h"
94#include "ocpn_aui_manager.h"
95#include "ocpndc.h"
96#include "ocpn_pixel.h"
97#include "ocpn_region.h"
98#include "options.h"
99#include "piano.h"
100#include "pluginmanager.h"
101#include "quilt.h"
102#include "route_gui.h"
103#include "routemanagerdialog.h"
104#include "route_point_gui.h"
105#include "route_prop_dlg_impl.h"
106#include "s52plib.h"
107#include "s52utils.h"
108#include "s57_query_dlg.h"
109#include "s57chart.h" // for ArrayOfS57Obj
110#include "senc_manager.h"
111#include "shapefile_basemap.h"
112#include "styles.h"
113#include "tcmgr.h"
114#include "tc_win.h"
115#include "thumbwin.h"
116#include "tide_time.h"
117#include "timers.h"
118#include "toolbar.h"
119#include "top_frame.h"
120#include "track_gui.h"
121#include "track_prop_dlg.h"
122#include "undo.h"
123#include "user_colors.h"
124
125#include "s57_ocpn_utils.h"
126
127#ifdef __ANDROID__
128#include "androidUTIL.h"
129#endif
130
131#ifdef ocpnUSE_GL
132#include "gl_chart_canvas.h"
135#endif
136
137#ifdef __VISUALC__
138#include <wx/msw/msvcrt.h>
139#endif
140
141#ifndef __WXMSW__
142#include <signal.h>
143#include <setjmp.h>
144#endif
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173arrayofCanvasPtr g_canvasArray;
175static bool g_bSmoothRecenter = true;
176static bool bDrawCurrentValues;
186static int mouse_x;
196static int mouse_y;
197static bool mouse_leftisdown;
198static bool g_brouteCreating;
199static int r_gamma_mult;
200static int g_gamma_mult;
201static int b_gamma_mult;
202static int gamma_state;
203static bool g_brightness_init;
204static int last_brightness;
205static wxGLContext *g_pGLcontext; // shared common context
206
207// "Curtain" mode parameters
208static wxDialog *g_pcurtain;
209
210static wxString g_lastS52PLIBPluginMessage;
211
212#define MIN_BRIGHT 10
213#define MAX_BRIGHT 100
214
215//------------------------------------------------------------------------------
216// ChartCanvas Implementation
217//------------------------------------------------------------------------------
218BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
219EVT_PAINT(ChartCanvas::OnPaint)
220EVT_ACTIVATE(ChartCanvas::OnActivate)
221EVT_SIZE(ChartCanvas::OnSize)
222#ifndef HAVE_WX_GESTURE_EVENTS
223EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
224#endif
225EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
226EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
227EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
228EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
229EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
230EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
231EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
232EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
233EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
234EVT_KEY_UP(ChartCanvas::OnKeyUp)
235EVT_CHAR(ChartCanvas::OnKeyChar)
236EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
237EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
238EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
239EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
240EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
241EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
242EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
243EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
244EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
245EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
246
247END_EVENT_TABLE()
248
249// Define a constructor for my canvas
250ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
251 : AbstractChartCanvas(frame, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
252 m_nmea_log(nmea_log) {
253 parent_frame = frame; // save a pointer to parent
254 m_canvasIndex = canvasIndex;
255
256 pscratch_bm = NULL;
257
258 SetBackgroundColour(wxColour(0, 0, 0));
259 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
260 // color scheme change
261
262 m_groupIndex = 0;
263 m_bDrawingRoute = false;
264 m_bRouteEditing = false;
265 m_bMarkEditing = false;
266 m_bRoutePoinDragging = false;
267 m_bIsInRadius = false;
268 m_bMayToggleMenuBar = true;
269
270 m_bFollow = false;
271 m_bShowNavobjects = true;
272 m_bTCupdate = false;
273 m_bAppendingRoute = false; // was true in MSW, why??
274 pThumbDIBShow = NULL;
275 m_bShowCurrent = false;
276 m_bShowTide = false;
277 bShowingCurrent = false;
278 pCwin = NULL;
279 warp_flag = false;
280 m_bzooming = false;
281 m_b_paint_enable = true;
282 m_routeState = 0;
283
284 pss_overlay_bmp = NULL;
285 pss_overlay_mask = NULL;
286 m_bChartDragging = false;
287 m_bMeasure_Active = false;
288 m_bMeasure_DistCircle = false;
289 m_pMeasureRoute = NULL;
290 m_pTrackRolloverWin = NULL;
291 m_pRouteRolloverWin = NULL;
292 m_pAISRolloverWin = NULL;
293 m_bedge_pan = false;
294 m_disable_edge_pan = false;
295 m_dragoffsetSet = false;
296 m_bautofind = false;
297 m_bFirstAuto = true;
298 m_groupIndex = 0;
299 m_singleChart = NULL;
300 m_upMode = NORTH_UP_MODE;
301 m_bShowAIS = true;
302 m_bShowAISScaled = false;
303 m_timed_move_vp_active = false;
304 m_inPinch = false;
305 m_disable_adjust_on_zoom = false;
306
307 m_vLat = 0.;
308 m_vLon = 0.;
309
310 m_pCIWin = NULL;
311
312 m_pSelectedRoute = NULL;
313 m_pSelectedTrack = NULL;
314 m_pRoutePointEditTarget = NULL;
315 m_pFoundPoint = NULL;
316 m_pMouseRoute = NULL;
317 m_prev_pMousePoint = NULL;
318 m_pEditRouteArray = NULL;
319 m_pFoundRoutePoint = NULL;
320 m_FinishRouteOnKillFocus = true;
321
322 m_pRolloverRouteSeg = NULL;
323 m_pRolloverTrackSeg = NULL;
324 m_bsectors_shown = false;
325
326 m_bbrightdir = false;
327 r_gamma_mult = 1;
328 g_gamma_mult = 1;
329 b_gamma_mult = 1;
330
331 m_pos_image_user_day = NULL;
332 m_pos_image_user_dusk = NULL;
333 m_pos_image_user_night = NULL;
334 m_pos_image_user_grey_day = NULL;
335 m_pos_image_user_grey_dusk = NULL;
336 m_pos_image_user_grey_night = NULL;
337
338 m_zoom_factor = 1;
339 m_rotation_speed = 0;
340 m_mustmove = 0;
341
342 m_OSoffsetx = 0.;
343 m_OSoffsety = 0.;
344
345 m_pos_image_user_yellow_day = NULL;
346 m_pos_image_user_yellow_dusk = NULL;
347 m_pos_image_user_yellow_night = NULL;
348
349 SetOwnShipState(SHIP_INVALID);
350
351 undo = new Undo(this);
352
353 VPoint.Invalidate();
354
355 m_glcc = NULL;
356
357 m_focus_indicator_pix = 1;
358
359 m_pCurrentStack = NULL;
360 m_bpersistent_quilt = false;
361 m_piano_ctx_menu = NULL;
362 m_Compass = NULL;
363 m_NotificationsList = NULL;
364 m_notification_button = NULL;
365
366 g_ChartNotRenderScaleFactor = 2.0;
367 m_bShowScaleInStatusBar = true;
368
369 m_muiBar = NULL;
370 m_bShowScaleInStatusBar = false;
371 m_show_focus_bar = true;
372
373 m_bShowOutlines = false;
374 m_bDisplayGrid = false;
375 m_bShowDepthUnits = true;
376 m_encDisplayCategory = (int)STANDARD;
377
378 m_encShowLights = true;
379 m_encShowAnchor = true;
380 m_encShowDataQual = false;
381 m_bShowGPS = true;
382 m_pQuilt = new Quilt(this);
383 SetQuiltMode(true);
384 SetAlertString("");
385 m_sector_glat = 0;
386 m_sector_glon = 0;
387 g_PrintingInProgress = false;
388
389#ifdef HAVE_WX_GESTURE_EVENTS
390 m_oldVPSScale = -1.0;
391 m_popupWanted = false;
392 m_leftdown = false;
393#endif /* HAVE_WX_GESTURE_EVENTS */
394 m_inLongPress = false;
395 m_sw_down_time = 0;
396 m_sw_up_time = 0;
397 m_sw_left_down.Start();
398 m_sw_left_up.Start();
399
400 SetupGlCanvas();
401
402 singleClickEventIsValid = false;
403
404 // Build the cursors
405
406 pCursorLeft = NULL;
407 pCursorRight = NULL;
408 pCursorUp = NULL;
409 pCursorDown = NULL;
410 pCursorArrow = NULL;
411 pCursorPencil = NULL;
412 pCursorCross = NULL;
413
414 RebuildCursors();
415
416 SetCursor(*pCursorArrow);
417
418 pPanTimer = new wxTimer(this, m_MouseDragging);
419 pPanTimer->Stop();
420
421 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
422 pMovementTimer->Stop();
423
424 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
425 pMovementStopTimer->Stop();
426
427 pRotDefTimer = new wxTimer(this, ROT_TIMER);
428 pRotDefTimer->Stop();
429
430 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
431 m_DoubleClickTimer->Stop();
432
433 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
434 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
435 m_chart_drag_inertia_active = false;
436
437 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
438 m_animationActive = false;
439 m_menuTimer.SetOwner(this, MENU_TIMER);
440 m_tap_timer.SetOwner(this, TAP_TIMER);
441
442 m_panx = m_pany = 0;
443 m_panspeed = 0;
444 m_panx_target_final = m_pany_target_final = 0;
445 m_panx_target_now = m_pany_target_now = 0;
446 m_DragTrigger = -1;
447
448 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
449 pCurTrackTimer->Stop();
450 m_curtrack_timer_msec = 10;
451
452 m_wheelzoom_stop_oneshot = 0;
453 m_last_wheel_dir = 0;
454
455 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
456
457 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
458
459 m_rollover_popup_timer_msec = 20;
460
461 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
462
463 m_b_rot_hidef = true;
464
465 proute_bm = NULL;
466 m_prot_bm = NULL;
467
468 m_upMode = NORTH_UP_MODE;
469 m_bLookAhead = false;
470
471 // Set some benign initial values
472
473 m_cs = GLOBAL_COLOR_SCHEME_DAY;
474 VPoint.clat = 0;
475 VPoint.clon = 0;
476 VPoint.view_scale_ppm = 1;
477 VPoint.Invalidate();
478 m_nMeasureState = 0;
479 m_ignore_next_leftup = false;
480
481 m_canvas_scale_factor = 1.;
482
483 m_canvas_width = 1000;
484
485 m_overzoomTextWidth = 0;
486 m_overzoomTextHeight = 0;
487
488 // Create the default world chart
489 pWorldBackgroundChart = new GSHHSChart;
490 gShapeBasemap.Reset();
491
492 // Create the default depth unit emboss maps
493 m_pEM_Feet = NULL;
494 m_pEM_Meters = NULL;
495 m_pEM_Fathoms = NULL;
496
497 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
498
499 m_pEM_OverZoom = NULL;
500 SetOverzoomFont();
501 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
502
503 // Build icons for tide/current points
504 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
505 m_bmTideDay =
506 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
507
508 // Dusk
509 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
510
511 // Night
512 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
513
514 // Build Dusk/Night ownship icons
515 double factor_dusk = 0.5;
516 double factor_night = 0.25;
517
518 // Red
519 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
520
521 int rimg_width = m_os_image_red_day.GetWidth();
522 int rimg_height = m_os_image_red_day.GetHeight();
523
524 m_os_image_red_dusk = m_os_image_red_day.Copy();
525 m_os_image_red_night = m_os_image_red_day.Copy();
526
527 for (int iy = 0; iy < rimg_height; iy++) {
528 for (int ix = 0; ix < rimg_width; ix++) {
529 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
530 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
531 m_os_image_red_day.GetGreen(ix, iy),
532 m_os_image_red_day.GetBlue(ix, iy));
533 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
534 hsv.value = hsv.value * factor_dusk;
535 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
536 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
537
538 hsv = wxImage::RGBtoHSV(rgb);
539 hsv.value = hsv.value * factor_night;
540 nrgb = wxImage::HSVtoRGB(hsv);
541 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
542 }
543 }
544 }
545
546 // Grey
547 m_os_image_grey_day =
548 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
549
550 int gimg_width = m_os_image_grey_day.GetWidth();
551 int gimg_height = m_os_image_grey_day.GetHeight();
552
553 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
554 m_os_image_grey_night = m_os_image_grey_day.Copy();
555
556 for (int iy = 0; iy < gimg_height; iy++) {
557 for (int ix = 0; ix < gimg_width; ix++) {
558 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
559 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
560 m_os_image_grey_day.GetGreen(ix, iy),
561 m_os_image_grey_day.GetBlue(ix, iy));
562 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
563 hsv.value = hsv.value * factor_dusk;
564 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
565 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
566
567 hsv = wxImage::RGBtoHSV(rgb);
568 hsv.value = hsv.value * factor_night;
569 nrgb = wxImage::HSVtoRGB(hsv);
570 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
571 }
572 }
573 }
574
575 // Yellow
576 m_os_image_yellow_day = m_os_image_red_day.Copy();
577
578 gimg_width = m_os_image_yellow_day.GetWidth();
579 gimg_height = m_os_image_yellow_day.GetHeight();
580
581 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
582 m_os_image_yellow_night = m_os_image_red_day.Copy();
583
584 for (int iy = 0; iy < gimg_height; iy++) {
585 for (int ix = 0; ix < gimg_width; ix++) {
586 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
587 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
588 m_os_image_yellow_day.GetGreen(ix, iy),
589 m_os_image_yellow_day.GetBlue(ix, iy));
590 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
591 hsv.hue += 60. / 360.; // shift to yellow
592 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
593 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
594
595 hsv = wxImage::RGBtoHSV(rgb);
596 hsv.value = hsv.value * factor_dusk;
597 hsv.hue += 60. / 360.; // shift to yellow
598 nrgb = wxImage::HSVtoRGB(hsv);
599 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
600
601 hsv = wxImage::RGBtoHSV(rgb);
602 hsv.hue += 60. / 360.; // shift to yellow
603 hsv.value = hsv.value * factor_night;
604 nrgb = wxImage::HSVtoRGB(hsv);
605 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
606 }
607 }
608 }
609
610 // Set initial pointers to ownship images
611 m_pos_image_red = &m_os_image_red_day;
612 m_pos_image_yellow = &m_os_image_yellow_day;
613 m_pos_image_grey = &m_os_image_grey_day;
614
615 SetUserOwnship();
616
617 m_pBrightPopup = NULL;
618
619#ifdef ocpnUSE_GL
620 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
621#endif
622
623 SetupGridFont();
624
625 m_Piano = new Piano(this);
626
627 m_bShowCompassWin = true;
628 m_Compass = new ocpnCompass(this);
629 m_Compass->SetScaleFactor(g_compass_scalefactor);
630 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
631
632 if (IsPrimaryCanvas()) {
633 m_notification_button = new NotificationButton(this);
634 m_notification_button->SetScaleFactor(g_compass_scalefactor);
635 m_notification_button->Show(true);
636 }
637
638 m_pianoFrozen = false;
639
640 SetMinSize(wxSize(200, 200));
641
642 m_displayScale = 1.0;
643#if defined(__WXOSX__) || defined(__WXGTK3__)
644 // Support scaled HDPI displays.
645 m_displayScale = GetContentScaleFactor();
646#endif
647 VPoint.SetPixelScale(m_displayScale);
648
649#ifdef HAVE_WX_GESTURE_EVENTS
650 // if (!m_glcc)
651 {
652 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
653 wxLogError("Failed to enable touch events");
654 }
655
656 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
657
658 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
659 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
660
661 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
662 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
663
664 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
665 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
666
667 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
668 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
669 }
670#endif
671
672 // Listen for notification events
673 auto &noteman = NotificationManager::GetInstance();
674
675 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
676 evt_notificationlist_change_listener.Listen(
677 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
678 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
679 if (m_NotificationsList && m_NotificationsList->IsShown()) {
680 m_NotificationsList->ReloadNotificationList();
681 }
682 Refresh();
683 });
684}
685
686ChartCanvas::~ChartCanvas() {
687 delete pThumbDIBShow;
688
689 // Delete Cursors
690 delete pCursorLeft;
691 delete pCursorRight;
692 delete pCursorUp;
693 delete pCursorDown;
694 delete pCursorArrow;
695 delete pCursorPencil;
696 delete pCursorCross;
697
698 delete pPanTimer;
699 delete pMovementTimer;
700 delete pMovementStopTimer;
701 delete pCurTrackTimer;
702 delete pRotDefTimer;
703 delete m_DoubleClickTimer;
704
705 delete m_pTrackRolloverWin;
706 delete m_pRouteRolloverWin;
707 delete m_pAISRolloverWin;
708 delete m_pBrightPopup;
709
710 delete m_pCIWin;
711
712 delete pscratch_bm;
713
714 m_dc_route.SelectObject(wxNullBitmap);
715 delete proute_bm;
716
717 delete pWorldBackgroundChart;
718 delete pss_overlay_bmp;
719
720 delete m_pEM_Feet;
721 delete m_pEM_Meters;
722 delete m_pEM_Fathoms;
723
724 delete m_pEM_OverZoom;
725 // delete m_pEM_CM93Offset;
726
727 delete m_prot_bm;
728
729 delete m_pos_image_user_day;
730 delete m_pos_image_user_dusk;
731 delete m_pos_image_user_night;
732 delete m_pos_image_user_grey_day;
733 delete m_pos_image_user_grey_dusk;
734 delete m_pos_image_user_grey_night;
735 delete m_pos_image_user_yellow_day;
736 delete m_pos_image_user_yellow_dusk;
737 delete m_pos_image_user_yellow_night;
738
739 delete undo;
740#ifdef ocpnUSE_GL
741 if (!g_bdisable_opengl) {
742 delete m_glcc;
743
744#if wxCHECK_VERSION(2, 9, 0)
745 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
746#endif
747 }
748#endif
749
750 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
751 // wx tries to deliver events to this canvas during destroy.
752 MUIBar *muiBar = m_muiBar;
753 m_muiBar = 0;
754 delete muiBar;
755 delete m_pQuilt;
756 delete m_pCurrentStack;
757 delete m_Compass;
758 delete m_Piano;
759 delete m_notification_button;
760}
761
762void ChartCanvas::SetupGridFont() {
763 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
764 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
765 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
766 m_pgridFont = FontMgr::Get().FindOrCreateFont(
767 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
768 FALSE, wxString("Arial"));
769}
770
771void ChartCanvas::RebuildCursors() {
772 delete pCursorLeft;
773 delete pCursorRight;
774 delete pCursorUp;
775 delete pCursorDown;
776 delete pCursorArrow;
777 delete pCursorPencil;
778 delete pCursorCross;
779
780 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
781 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
782
783 double pencilScale =
784 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
785
786 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
787 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
788 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
789 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
790 wxImage ICursorPencil =
791 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
792 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
793
794#if !defined(__WXMSW__) && !defined(__WXQT__)
795 ICursorLeft.ConvertAlphaToMask(128);
796 ICursorRight.ConvertAlphaToMask(128);
797 ICursorUp.ConvertAlphaToMask(128);
798 ICursorDown.ConvertAlphaToMask(128);
799 ICursorPencil.ConvertAlphaToMask(10);
800 ICursorCross.ConvertAlphaToMask(10);
801#endif
802
803 if (ICursorLeft.Ok()) {
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
805 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
806 pCursorLeft = new wxCursor(ICursorLeft);
807 } else
808 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
809
810 if (ICursorRight.Ok()) {
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
812 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
813 pCursorRight = new wxCursor(ICursorRight);
814 } else
815 pCursorRight = new wxCursor(wxCURSOR_ARROW);
816
817 if (ICursorUp.Ok()) {
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
819 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
820 pCursorUp = new wxCursor(ICursorUp);
821 } else
822 pCursorUp = new wxCursor(wxCURSOR_ARROW);
823
824 if (ICursorDown.Ok()) {
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
826 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
827 pCursorDown = new wxCursor(ICursorDown);
828 } else
829 pCursorDown = new wxCursor(wxCURSOR_ARROW);
830
831 if (ICursorPencil.Ok()) {
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
833 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
834 pCursorPencil = new wxCursor(ICursorPencil);
835 } else
836 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
837
838 if (ICursorCross.Ok()) {
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
840 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
841 pCursorCross = new wxCursor(ICursorCross);
842 } else
843 pCursorCross = new wxCursor(wxCURSOR_ARROW);
844
845 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
846 pPlugIn_Cursor = NULL;
847}
848
849void ChartCanvas::CanvasApplyLocale() {
850 CreateDepthUnitEmbossMaps(m_cs);
851 CreateOZEmbossMapData(m_cs);
852}
853
854void ChartCanvas::SetupGlCanvas() {
855#ifndef __ANDROID__
856#ifdef ocpnUSE_GL
857 if (!g_bdisable_opengl) {
858 if (g_bopengl) {
859 wxLogMessage("Creating glChartCanvas");
860 m_glcc = new glChartCanvas(this);
861
862 // We use one context for all GL windows, so that textures etc will be
863 // automatically shared
864 if (IsPrimaryCanvas()) {
865 // qDebug() << "Creating Primary Context";
866
867 // wxGLContextAttrs ctxAttr;
868 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
869 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
870 // NULL, &ctxAttr);
871 wxGLContext *pctx = new wxGLContext(m_glcc);
872 m_glcc->SetContext(pctx);
873 g_pGLcontext = pctx; // Save a copy of the common context
874 } else {
875#ifdef __WXOSX__
876 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
877#else
878 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
879 // saved common context
880#endif
881 }
882 }
883 }
884#endif
885#endif
886
887#ifdef __ANDROID__ // ocpnUSE_GL
888 if (!g_bdisable_opengl) {
889 if (g_bopengl) {
890 // qDebug() << "SetupGlCanvas";
891 wxLogMessage("Creating glChartCanvas");
892
893 // We use one context for all GL windows, so that textures etc will be
894 // automatically shared
895 if (IsPrimaryCanvas()) {
896 qDebug() << "Creating Primary glChartCanvas";
897
898 // wxGLContextAttrs ctxAttr;
899 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
900 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
901 // NULL, &ctxAttr);
902 m_glcc = new glChartCanvas(this);
903
904 wxGLContext *pctx = new wxGLContext(m_glcc);
905 m_glcc->SetContext(pctx);
906 g_pGLcontext = pctx; // Save a copy of the common context
907 m_glcc->m_pParentCanvas = this;
908 // m_glcc->Reparent(this);
909 } else {
910 qDebug() << "Creating Secondary glChartCanvas";
911 // QGLContext *pctx =
912 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
913 // << "pctx: " << pctx;
914
915 m_glcc =
916 new glChartCanvas(wxTheApp->GetTopWindow(),
917 top_frame::Get()->GetWxGlCanvas()); // Shared
918 // m_glcc = new glChartCanvas(this, pctx); //Shared
919 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
920 wxGLContext *pwxctx = new wxGLContext(m_glcc);
921 m_glcc->SetContext(pwxctx);
922 m_glcc->m_pParentCanvas = this;
923 // m_glcc->Reparent(this);
924 }
925 }
926 }
927#endif
928}
929
930void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
931 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
932
933 // On Android, we get a KillFocus on just about every keystroke.
934 // Why?
935#ifdef __ANDROID__
936 return;
937#endif
938
939 // Special logic:
940 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
941 // canvas focus. Why??? Who knows... So, we provide for this case by
942 // starting a timer if required to actually Finish() a route on a legitimate
943 // focus change, but not if the focus is quickly regained ( <20 msec.) on
944 // this canvas.
945#ifdef __WXOSX__
946 if (m_routeState && m_FinishRouteOnKillFocus)
947 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
948#else
949 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
950#endif
951}
952
953void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
954 m_routeFinishTimer.Stop();
955
956 // Try to keep the global top-line menubar selections up to date with the
957 // current "focus" canvas
958 top_frame::Get()->UpdateGlobalMenuItems(this);
959
960 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
961}
962
963void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
964 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
965}
966
967#ifdef HAVE_WX_GESTURE_EVENTS
968void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
969#ifdef __ANDROID__
970 /* we defer the popup menu call upon the leftup event
971 else the menu disappears immediately,
972 (see
973 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
974 */
975 m_popupWanted = true;
976#else
977 m_inLongPress = !g_bhide_context_menus;
978
979 // Send a synthetic mouse left-up event to sync the mouse pan logic.
980 m_menuPos = event.GetPosition();
981 wxMouseEvent ev(wxEVT_LEFT_UP);
982 ev.m_x = m_menuPos.x;
983 ev.m_y = m_menuPos.y;
984 wxPostEvent(this, ev);
985
986 // In touch mode, send a "RIGHT CLICK" event, for plugins
987 if (g_btouch) {
988 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
989 ev_right_click.m_x = m_menuPos.x;
990 ev_right_click.m_y = m_menuPos.y;
991 MouseEvent(ev_right_click);
992 }
993#endif
994}
995
996void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
997 // not implemented yet
998}
999
1000void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1001
1002void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1003
1004void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1005#ifdef __WXGTK__
1006 long dt = m_sw_left_up.Time() - m_sw_up_time;
1007 m_sw_up_time = m_sw_left_up.Time();
1008
1009 // printf(" dt %ld\n",dt);
1010 if (dt < 5) {
1011 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1012 // ignore it.
1013 return;
1014 }
1015#endif
1016 // printf("Left_UP\n");
1017
1018 wxPoint pos = event.GetPosition();
1019
1020 m_leftdown = false;
1021
1022 if (!m_popupWanted) {
1023 wxMouseEvent ev(wxEVT_LEFT_UP);
1024 ev.m_x = pos.x;
1025 ev.m_y = pos.y;
1026 MouseEvent(ev);
1027 return;
1028 }
1029
1030 m_popupWanted = false;
1031
1032 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1033 ev.m_x = pos.x;
1034 ev.m_y = pos.y;
1035
1036 MouseEvent(ev);
1037}
1038
1039void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1040 m_leftdown = true;
1041
1042 // Detect and manage multiple left-downs coming from GTK mouse emulation
1043#ifdef __WXGTK__
1044 long dt = m_sw_left_down.Time() - m_sw_down_time;
1045 m_sw_down_time = m_sw_left_down.Time();
1046
1047 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1048
1049 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1050 // The timing between the two events is dependent upon the wxWidgets
1051 // message queue status, and the processing time required for intervening
1052 // events.
1053 // We detect and remove the duplicate events by measuring the elapsed time
1054 // between arrival of events.
1055 // Choose a duplicate detection time long enough to catch worst case time lag
1056 // between duplicating events, but considerably shorter than the nominal
1057 // "intentional double-click" time interval defined generally as 350 msec.
1058 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1059 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1060 // ignore it.
1061 return;
1062 }
1063#endif
1064
1065 // printf("Left_DOWN\n");
1066
1067 // detect and manage double-tap
1068#ifdef __WXGTK__
1069 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1070 2; // Use system setting for distance
1071 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1072 m_lastTapPos.y - max_double_click_distance,
1073 max_double_click_distance * 2, max_double_click_distance * 2);
1074
1075 // A new tap has started, check if it's close enough and in time
1076 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1077 // printf(" TapBump 1\n");
1078 m_tap_count += 1;
1079 } else {
1080 // printf(" TapSet 1\n");
1081 m_tap_count = 1;
1082 m_lastTapPos = event.GetPosition();
1083 m_tap_timer.StartOnce(
1084 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1085 }
1086
1087 if (m_tap_count == 2) {
1088 // printf(" Doubletap detected\n");
1089 m_tap_count = 0; // Reset after a double-tap
1090
1091 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1092 ev.m_x = event.m_x;
1093 ev.m_y = event.m_y;
1094 // wxPostEvent(this, ev);
1095 MouseEvent(ev);
1096 return;
1097 }
1098
1099#endif
1100
1101 MouseEvent(event);
1102}
1103
1104void ChartCanvas::OnMotion(wxMouseEvent &event) {
1105 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1106 dragging, upon simple click, and without the OnLeftDown event before Thus,
1107 this consists in skiping it, and setting the leftdown bit according to a
1108 status that we trust */
1109 event.m_leftDown = m_leftdown;
1110 MouseEvent(event);
1111}
1112
1113void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1114 /* there are spurious end zoom events upon right-click */
1115 if (event.IsGestureEnd()) return;
1116
1117 double factor = event.GetZoomFactor();
1118
1119 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1120 m_oldVPSScale = GetVPScale();
1121 }
1122
1123 double current_vps = GetVPScale();
1124 double wanted_factor = m_oldVPSScale / current_vps * factor;
1125
1126 ZoomCanvas(wanted_factor, true, false);
1127
1128 // Allow combined zoom/pan operation
1129 if (event.IsGestureStart()) {
1130 m_zoomStartPoint = event.GetPosition();
1131 } else {
1132 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1133 PanCanvas(-delta.x, -delta.y);
1134 m_zoomStartPoint = event.GetPosition();
1135 }
1136}
1137
1138void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1139
1140void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1141 DoRotateCanvas(0.0);
1142}
1143#endif /* HAVE_WX_GESTURE_EVENTS */
1144
1145void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1146 // printf("tap timer %d\n", m_tap_count);
1147 m_tap_count = 0;
1148}
1149
1150void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1151 m_FinishRouteOnKillFocus = false;
1152 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1153 m_FinishRouteOnKillFocus = true;
1154}
1155
1156void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1157 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1158 m_vLat = pcc->iLat;
1159 m_vLon = pcc->iLon;
1160
1161 m_restore_dbindex = pcc->DBindex;
1162 m_bFollow = pcc->bFollow;
1163 if (pcc->GroupID < 0) pcc->GroupID = 0;
1164
1165 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1166 m_groupIndex = 0;
1167 else
1168 m_groupIndex = pcc->GroupID;
1169
1170 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1171
1172 ShowTides(pcc->bShowTides);
1173 ShowCurrents(pcc->bShowCurrents);
1174
1175 SetShowDepthUnits(pcc->bShowDepthUnits);
1176 SetShowGrid(pcc->bShowGrid);
1177 SetShowOutlines(pcc->bShowOutlines);
1178
1179 SetShowAIS(pcc->bShowAIS);
1180 SetAttenAIS(pcc->bAttenAIS);
1181
1182 // ENC options
1183 SetShowENCText(pcc->bShowENCText);
1184 m_encDisplayCategory = pcc->nENCDisplayCategory;
1185 m_encShowDepth = pcc->bShowENCDepths;
1186 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1187 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1188 m_encShowLights = pcc->bShowENCLights;
1189 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1190 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1191 m_encShowDataQual = pcc->bShowENCDataQuality;
1192
1193 bool courseUp = pcc->bCourseUp;
1194 bool headUp = pcc->bHeadUp;
1195 m_upMode = NORTH_UP_MODE;
1196 if (courseUp)
1197 m_upMode = COURSE_UP_MODE;
1198 else if (headUp)
1199 m_upMode = HEAD_UP_MODE;
1200
1201 m_bLookAhead = pcc->bLookahead;
1202
1203 m_singleChart = NULL;
1204}
1205
1206void ChartCanvas::ApplyGlobalSettings() {
1207 // GPS compas window
1208 if (m_Compass) {
1209 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1210 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1211 }
1212 if (m_notification_button) m_notification_button->UpdateStatus();
1213}
1214
1215void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1216 bool groupOK = CheckGroup(m_groupIndex);
1217
1218 if (!groupOK) {
1219 SetGroupIndex(m_groupIndex, true);
1220 }
1221}
1222
1223void ChartCanvas::SetShowGPS(bool bshow) {
1224 if (m_bShowGPS != bshow) {
1225 delete m_Compass;
1226 m_Compass = new ocpnCompass(this, bshow);
1227 m_Compass->SetScaleFactor(g_compass_scalefactor);
1228 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1229 }
1230 m_bShowGPS = bshow;
1231}
1232
1233void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1234 m_bShowCompassWin = bshow;
1235 if (m_Compass) {
1236 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1237 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1238 }
1239}
1240
1241int ChartCanvas::GetPianoHeight() {
1242 int height = 0;
1243 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1244
1245 return height;
1246}
1247
1248void ChartCanvas::ConfigureChartBar() {
1249 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1250
1251 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1252 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1253
1254 if (GetQuiltMode()) {
1255 m_Piano->SetRoundedRectangles(true);
1256 }
1257 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1258 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1259 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1260}
1261
1262void ChartCanvas::ShowTides(bool bShow) {
1263 top_frame::Get()->LoadHarmonics();
1264
1265 if (ptcmgr->IsReady()) {
1266 SetbShowTide(bShow);
1267
1268 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1269 } else {
1270 wxLogMessage("Chart1::Event...TCMgr Not Available");
1271 SetbShowTide(false);
1272 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1273 }
1274
1275 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1276 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1277
1278 // TODO
1279 // if( GetbShowTide() ) {
1280 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1281 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1282 // update
1283 // } else
1284 // FrameTCTimer.Stop();
1285}
1286
1287void ChartCanvas::ShowCurrents(bool bShow) {
1288 top_frame::Get()->LoadHarmonics();
1289
1290 if (ptcmgr->IsReady()) {
1291 SetbShowCurrent(bShow);
1292 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1293 } else {
1294 wxLogMessage("Chart1::Event...TCMgr Not Available");
1295 SetbShowCurrent(false);
1296 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1297 }
1298
1299 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1300 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1301
1302 // TODO
1303 // if( GetbShowCurrent() ) {
1304 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1305 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1306 // update
1307 // } else
1308 // FrameTCTimer.Stop();
1309}
1310
1311// TODO
1312static ChartDummy *pDummyChart;
1313
1316
1317void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1318
1319void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1320 SetAlertString("");
1321
1322 int new_index = index;
1323 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1324
1325 bool bgroup_override = false;
1326 int old_group_index = new_index;
1327
1328 if (!CheckGroup(new_index)) {
1329 new_index = 0;
1330 bgroup_override = true;
1331 }
1332
1333 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1334 new_index = index;
1335
1336 // Get the currently displayed chart native scale, and the current ViewPort
1337 int current_chart_native_scale = GetCanvasChartNativeScale();
1338 ViewPort vp = GetVP();
1339
1340 m_groupIndex = new_index;
1341
1342 // Are there ENCs in this group
1343 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1344
1345 // Update the MUIBar for ENC availability
1346 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1347
1348 // Allow the chart database to pre-calculate the MBTile inclusion test
1349 // boolean...
1350 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1351
1352 // Invalidate the "sticky" chart on group change, since it might not be in
1353 // the new group
1354 g_sticky_chart = -1;
1355
1356 // We need a chartstack and quilt to figure out which chart to open in the
1357 // new group
1358 UpdateCanvasOnGroupChange();
1359
1360 int dbi_now = -1;
1361 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1362
1363 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1364
1365 // If a new reference chart is indicated, set a good scale for it.
1366 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1367 double best_scale = GetBestStartScale(dbi_hint, vp);
1368 SetVPScale(best_scale);
1369 }
1370
1371 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1372
1373 // Refresh the canvas, selecting the "best" chart,
1374 // applying the prior ViewPort exactly
1375 canvasChartsRefresh(dbi_hint);
1376
1377 UpdateCanvasControlBar();
1378
1379 if (!autoSwitch && bgroup_override) {
1380 // show a short timed message box
1381 wxString msg(_("Group \""));
1382
1383 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1384 msg += pGroup->m_group_name;
1385
1386 msg += _("\" is empty.");
1387
1388 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1389
1390 return;
1391 }
1392
1393 // Message box is deferred so that canvas refresh occurs properly before
1394 // dialog
1395 if (bgroup_override) {
1396 wxString msg(_("Group \""));
1397
1398 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1399 msg += pGroup->m_group_name;
1400
1401 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1402
1403 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1404 }
1405}
1406
1407bool ChartCanvas::CheckGroup(int igroup) {
1408 if (!ChartData) return true; // Not known yet...
1409
1410 if (igroup == 0) return true; // "all charts" is always OK
1411
1412 if (igroup < 0) // negative group is an error
1413 return false;
1414
1415 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1416
1417 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1418 // and auto-shift to group 0
1419 return false;
1420
1421 for (const auto &elem : pGroup->m_element_array) {
1422 for (unsigned int ic = 0;
1423 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1424 auto &cte = ChartData->GetChartTableEntry(ic);
1425 wxString chart_full_path(cte.GetpFullPath(), wxConvUTF8);
1426
1427 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1428 }
1429 }
1430
1431 // If necessary, check for GSHHS
1432 for (const auto &elem : pGroup->m_element_array) {
1433 const wxString &element_root = elem.m_element_name;
1434 wxString test_string = "GSHH";
1435 if (element_root.Upper().Contains(test_string)) return true;
1436 }
1437
1438 return false;
1439}
1440
1441void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1442 if (!ChartData) return;
1443
1444 AbstractPlatform::ShowBusySpinner();
1445
1446 double old_scale = GetVPScale();
1447 InvalidateQuilt();
1448 SetQuiltRefChart(-1);
1449
1450 m_singleChart = NULL;
1451
1452 // delete m_pCurrentStack;
1453 // m_pCurrentStack = NULL;
1454
1455 // Build a new ChartStack
1456 if (!m_pCurrentStack) {
1457 m_pCurrentStack = new ChartStack;
1458 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1459 }
1460
1461 if (-1 != dbi_hint) {
1462 if (GetQuiltMode()) {
1463 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1464 SetQuiltRefChart(dbi_hint);
1465 } else {
1466 // Open the saved chart
1467 ChartBase *pTentative_Chart;
1468 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1469
1470 if (pTentative_Chart) {
1471 /* m_singleChart is always NULL here, (set above) should this go before
1472 * that? */
1473 if (m_singleChart) m_singleChart->Deactivate();
1474
1475 m_singleChart = pTentative_Chart;
1476 m_singleChart->Activate();
1477
1478 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1479 GetpCurrentStack(), m_singleChart->GetFullPath());
1480 }
1481 }
1482
1483 // refresh_Piano();
1484 } else {
1485 // Select reference chart from the stack, as though clicked by user
1486 // Make it the smallest scale chart on the stack
1487 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1488 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1489 SetQuiltRefChart(selected_index);
1490 }
1491
1492 // Validate the correct single chart, or set the quilt mode as appropriate
1493 SetupCanvasQuiltMode();
1494 if (!GetQuiltMode() && m_singleChart == 0) {
1495 // use a dummy like in DoChartUpdate
1496 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1497 m_singleChart = pDummyChart;
1498 SetVPScale(old_scale);
1499 }
1500
1501 ReloadVP();
1502
1503 UpdateCanvasControlBar();
1504 UpdateGPSCompassStatusBox(true);
1505
1506 SetCursor(wxCURSOR_ARROW);
1507
1508 AbstractPlatform::HideBusySpinner();
1509}
1510
1511bool ChartCanvas::DoCanvasUpdate() {
1512 double tLat, tLon; // Chart Stack location
1513 double vpLat, vpLon; // ViewPort location
1514 bool blong_jump = false;
1515 meters_to_shift = 0;
1516 dir_to_shift = 0;
1517
1518 bool bNewChart = false;
1519 bool bNewView = false;
1520 bool bCanvasChartAutoOpen = true; // debugging
1521
1522 bool bNewPiano = false;
1523 bool bOpenSpecified;
1524 ChartStack LastStack;
1525 ChartBase *pLast_Ch;
1526
1527 ChartStack WorkStack;
1528
1529 if (bDBUpdateInProgress) return false;
1530 if (!ChartData) return false;
1531
1532 if (ChartData->IsBusy()) return false;
1533 if (m_chart_drag_inertia_active) return false;
1534
1535 // Startup case:
1536 // Quilting is enabled, but the last chart seen was not quiltable
1537 // In this case, drop to single chart mode, set persistence flag,
1538 // And open the specified chart
1539 // TODO implement this
1540 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1541 // if( GetQuiltMode() ) {
1542 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1543 // gFrame->ToggleQuiltMode();
1544 // m_bpersistent_quilt = true;
1545 // m_singleChart = NULL;
1546 // }
1547 // }
1548 // }
1549
1550 // If in auto-follow mode, use the current glat,glon to build chart
1551 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1552 // other means
1553
1554 if (m_bFollow) {
1555 tLat = gLat;
1556 tLon = gLon;
1557
1558 // Set the ViewPort center based on the OWNSHIP offset
1559 double dx = m_OSoffsetx;
1560 double dy = m_OSoffsety;
1561 double d_east = dx / GetVP().view_scale_ppm;
1562 double d_north = dy / GetVP().view_scale_ppm;
1563
1564 if (GetUpMode() == NORTH_UP_MODE) {
1565 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1566 } else {
1567 double offset_angle = atan2(d_north, d_east);
1568 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1569 double chart_angle = GetVPRotation();
1570 double target_angle = chart_angle + offset_angle;
1571 double d_east_mod = offset_distance * cos(target_angle);
1572 double d_north_mod = offset_distance * sin(target_angle);
1573 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1574 }
1575
1576 // on lookahead mode, adjust the vp center point
1577 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1578 double cog_to_use = gCog;
1579 if (g_btenhertz &&
1580 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1581 cog_to_use = gCog_gt;
1582 blong_jump = true;
1583 }
1584 if (!g_btenhertz) cog_to_use = g_COGAvg;
1585
1586 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1587
1588 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1589 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1590
1591 double pixel_delta_tent =
1592 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1593
1594 double pixel_delta = 0;
1595
1596 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1597 // avoid jumping of the vp center point during slow maneuvering, or at
1598 // anchor....
1599 if (!std::isnan(gSog)) {
1600 if (gSog < 2.0)
1601 pixel_delta = 0.;
1602 else
1603 pixel_delta = pixel_delta_tent;
1604 }
1605
1606 meters_to_shift = 0;
1607 dir_to_shift = 0;
1608 if (!std::isnan(gCog)) {
1609 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1610 dir_to_shift = cog_to_use;
1611 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1612 &vpLon);
1613 } else {
1614 vpLat = gLat;
1615 vpLon = gLon;
1616 }
1617 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1618 m_OSoffsetx = 0; // center ownship on loss of GPS
1619 m_OSoffsety = 0;
1620 vpLat = gLat;
1621 vpLon = gLon;
1622 }
1623
1624 } else {
1625 tLat = m_vLat;
1626 tLon = m_vLon;
1627 vpLat = m_vLat;
1628 vpLon = m_vLon;
1629 }
1630
1631 if (GetQuiltMode()) {
1632 int current_db_index = -1;
1633 if (m_pCurrentStack)
1634 current_db_index =
1635 m_pCurrentStack
1636 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1637 // chart dbIndex
1638 else
1639 m_pCurrentStack = new ChartStack;
1640
1641 // This logic added to enable opening a chart when there is no
1642 // previous chart indication, either from inital startup, or from adding
1643 // new chart directory
1644 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1645 m_pCurrentStack) {
1646 if (m_pCurrentStack->nEntry) {
1647 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1648 1); // smallest scale
1649 SelectQuiltRefdbChart(new_dbIndex, true);
1650 m_bautofind = false;
1651 }
1652 }
1653
1654 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1655 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1656
1657 if (m_bFirstAuto) {
1658 // Allow the chart database to pre-calculate the MBTile inclusion test
1659 // boolean...
1660 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1661
1662 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1663 // physical pixels. On standard DPI displays where logical = physical
1664 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1665 // logical pixels, this ratio would be 0.5.
1666 double proposed_scale_onscreen =
1667 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1668
1669 int initial_db_index = m_restore_dbindex;
1670 if (initial_db_index < 0) {
1671 if (m_pCurrentStack->nEntry) {
1672 initial_db_index =
1673 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1674 } else
1675 m_bautofind = true; // initial_db_index = 0;
1676 }
1677
1678 if (m_pCurrentStack->nEntry) {
1679 int initial_type = ChartData->GetDBChartType(initial_db_index);
1680
1681 // Check to see if the target new chart is quiltable as a reference
1682 // chart
1683
1684 if (!IsChartQuiltableRef(initial_db_index)) {
1685 // If it is not quiltable, then walk the stack up looking for a
1686 // satisfactory chart i.e. one that is quiltable and of the same type
1687 // XXX if there's none?
1688 int stack_index = 0;
1689
1690 if (stack_index >= 0) {
1691 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1692 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1693 if (IsChartQuiltableRef(test_db_index) &&
1694 (initial_type ==
1695 ChartData->GetDBChartType(initial_db_index))) {
1696 initial_db_index = test_db_index;
1697 break;
1698 }
1699 stack_index++;
1700 }
1701 }
1702 }
1703
1704 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1705 if (pc) {
1706 SetQuiltRefChart(initial_db_index);
1707 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1708 }
1709 }
1710 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1711 // just GetVPScale(), so I'm not sure why it's necessary to define the
1712 // proposed_scale_onscreen variable.
1713 bNewView |= SetViewPoint(vpLat, vpLon,
1714 GetCanvasScaleFactor() / proposed_scale_onscreen,
1715 0, GetVPRotation());
1716 }
1717 // Measure rough jump distance if in bfollow mode
1718 // No good reason to do smooth pan for
1719 // jump distance more than one screen width at scale.
1720 bool super_jump = false;
1721 if (m_bFollow) {
1722 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1723 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1724 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1725 }
1726#if 0
1727 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1728 int nstep = 5;
1729 if (blong_jump) nstep = 20;
1730 StartTimedMovementVP(vpLat, vpLon, nstep);
1731 } else
1732#endif
1733 {
1734 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1735 }
1736
1737 goto update_finish;
1738 }
1739
1740 // Single Chart Mode from here....
1741 pLast_Ch = m_singleChart;
1742 ChartTypeEnum new_open_type;
1743 ChartFamilyEnum new_open_family;
1744 if (pLast_Ch) {
1745 new_open_type = pLast_Ch->GetChartType();
1746 new_open_family = pLast_Ch->GetChartFamily();
1747 } else {
1748 new_open_type = CHART_TYPE_KAP;
1749 new_open_family = CHART_FAMILY_RASTER;
1750 }
1751
1752 bOpenSpecified = m_bFirstAuto;
1753
1754 // Make sure the target stack is valid
1755 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1756
1757 // Build a chart stack based on tLat, tLon
1758 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1759 m_groupIndex)) { // Bogus Lat, Lon?
1760 if (NULL == pDummyChart) {
1761 pDummyChart = new ChartDummy;
1762 bNewChart = true;
1763 }
1764
1765 if (m_singleChart)
1766 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1767
1768 m_singleChart = pDummyChart;
1769
1770 // If the current viewpoint is invalid, set the default scale to
1771 // something reasonable.
1772 double set_scale = GetVPScale();
1773 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1774
1775 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1776
1777 // If the chart stack has just changed, there is new status
1778 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1779 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1780 bNewPiano = true;
1781 bNewChart = true;
1782 }
1783 }
1784
1785 // Copy the new (by definition empty) stack into the target stack
1786 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1787
1788 goto update_finish;
1789 }
1790
1791 // Check to see if Chart Stack has changed
1792 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1793 // New chart stack, so...
1794 bNewPiano = true;
1795
1796 // Save a copy of the current stack
1797 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1798
1799 // Copy the new stack into the target stack
1800 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1801
1802 // Is Current Chart in new stack?
1803
1804 int tEntry = -1;
1805 if (NULL != m_singleChart) // this handles startup case
1806 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1807 m_singleChart->GetFullPath());
1808
1809 if (tEntry != -1) { // m_singleChart is in the new stack
1810 m_pCurrentStack->CurrentStackEntry = tEntry;
1811 bNewChart = false;
1812 }
1813
1814 else // m_singleChart is NOT in new stack
1815 { // So, need to open a new chart
1816 // Find the largest scale raster chart that opens OK
1817
1818 ChartBase *pProposed = NULL;
1819
1820 if (bCanvasChartAutoOpen) {
1821 bool search_direction =
1822 false; // default is to search from lowest to highest
1823 int start_index = 0;
1824
1825 // A special case: If panning at high scale, open largest scale
1826 // chart first
1827 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1828 (LastStack.nEntry == 0)) {
1829 search_direction = true;
1830 start_index = m_pCurrentStack->nEntry - 1;
1831 }
1832
1833 // Another special case, open specified index on program start
1834 if (bOpenSpecified) {
1835 search_direction = false;
1836 start_index = 0;
1837 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1838 start_index = 0;
1839
1840 new_open_type = CHART_TYPE_DONTCARE;
1841 }
1842
1843 pProposed = ChartData->OpenStackChartConditional(
1844 m_pCurrentStack, start_index, search_direction, new_open_type,
1845 new_open_family);
1846
1847 // Try to open other types/families of chart in some priority
1848 if (NULL == pProposed)
1849 pProposed = ChartData->OpenStackChartConditional(
1850 m_pCurrentStack, start_index, search_direction,
1851 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1852
1853 if (NULL == pProposed)
1854 pProposed = ChartData->OpenStackChartConditional(
1855 m_pCurrentStack, start_index, search_direction,
1856 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1857
1858 bNewChart = true;
1859
1860 } // bCanvasChartAutoOpen
1861
1862 else
1863 pProposed = NULL;
1864
1865 // If no go, then
1866 // Open a Dummy Chart
1867 if (NULL == pProposed) {
1868 if (NULL == pDummyChart) {
1869 pDummyChart = new ChartDummy;
1870 bNewChart = true;
1871 }
1872
1873 if (pLast_Ch)
1874 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1875
1876 pProposed = pDummyChart;
1877 }
1878
1879 // Arriving here, pProposed points to an opened chart, or NULL.
1880 if (m_singleChart) m_singleChart->Deactivate();
1881 m_singleChart = pProposed;
1882
1883 if (m_singleChart) {
1884 m_singleChart->Activate();
1885 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1886 m_pCurrentStack, m_singleChart->GetFullPath());
1887 }
1888 } // need new chart
1889
1890 // Arriving here, m_singleChart is opened and OK, or NULL
1891 if (NULL != m_singleChart) {
1892 // Setup the view using the current scale
1893 double set_scale = GetVPScale();
1894
1895 // If the current viewpoint is invalid, set the default scale to
1896 // something reasonable.
1897 if (!GetVP().IsValid())
1898 set_scale = 1. / 20000.;
1899 else { // otherwise, match scale if elected.
1900 double proposed_scale_onscreen;
1901
1902 if (m_bFollow) { // autoset the scale only if in autofollow
1903 double new_scale_ppm =
1904 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1905 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1906 } else
1907 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1908
1909 // This logic will bring a new chart onscreen at roughly twice the true
1910 // paper scale equivalent. Note that first chart opened on application
1911 // startup (bOpenSpecified = true) will open at the config saved scale
1912 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1913 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1914 double equivalent_vp_scale =
1915 GetCanvasScaleFactor() / proposed_scale_onscreen;
1916 double new_scale_ppm =
1917 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1918 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1919 }
1920
1921 if (m_bFollow) { // bounds-check the scale only if in autofollow
1922 proposed_scale_onscreen =
1923 wxMin(proposed_scale_onscreen,
1924 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1925 GetCanvasWidth()));
1926 proposed_scale_onscreen =
1927 wxMax(proposed_scale_onscreen,
1928 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1930 }
1931
1932 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1933 }
1934
1935 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1936 m_singleChart->GetChartSkew() * PI / 180.,
1937 GetVPRotation());
1938 }
1939 } // new stack
1940
1941 else // No change in Chart Stack
1942 {
1943 if ((m_bFollow) && m_singleChart)
1944 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1945 m_singleChart->GetChartSkew() * PI / 180.,
1946 GetVPRotation());
1947 }
1948
1949update_finish:
1950
1951 // TODO
1952 // if( bNewPiano ) UpdateControlBar();
1953
1954 m_bFirstAuto = false; // Auto open on program start
1955
1956 // If we need a Refresh(), do it here...
1957 // But don't duplicate a Refresh() done by SetViewPoint()
1958 if (bNewChart && !bNewView) Refresh(false);
1959
1960#ifdef ocpnUSE_GL
1961 // If a new chart, need to invalidate gl viewport for refresh
1962 // so the fbo gets flushed
1963 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1964#endif
1965
1966 return bNewChart | bNewView;
1967}
1968
1969void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1970 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1971
1972 SetQuiltRefChart(db_index);
1973 if (ChartData) {
1974 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1975 if (pc) {
1976 if (b_autoscale) {
1977 double best_scale_ppm = GetBestVPScale(pc);
1978 SetVPScale(best_scale_ppm);
1979 }
1980 } else
1981 SetQuiltRefChart(-1);
1982 } else
1983 SetQuiltRefChart(-1);
1984}
1985
1986void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1987 std::vector<int> piano_chart_index_array =
1988 GetQuiltExtendedStackdbIndexArray();
1989 int current_db_index = piano_chart_index_array[selected_index];
1990
1991 SelectQuiltRefdbChart(current_db_index);
1992}
1993
1994double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1995 if (pchart) {
1996 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1997
1998 if ((g_bPreserveScaleOnX) ||
1999 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2000 double new_scale_ppm = GetVPScale();
2001 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2002 } else {
2003 // This logic will bring the new chart onscreen at roughly twice the true
2004 // paper scale equivalent.
2005 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2006 double equivalent_vp_scale =
2007 GetCanvasScaleFactor() / proposed_scale_onscreen;
2008 double new_scale_ppm =
2009 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2010 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2011 }
2012
2013 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2014 // set. Otherwise, we get severe performance problems on all platforms
2015
2016 double max_underzoom_multiplier = 2.0;
2017 if (GetVP().b_quilt) {
2018 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2019 pchart->GetChartType(),
2020 pchart->GetChartFamily());
2021 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2022 }
2023
2024 proposed_scale_onscreen = wxMin(
2025 proposed_scale_onscreen,
2026 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2027 max_underzoom_multiplier);
2028
2029 // And, do not allow excessive overzoom either
2030 proposed_scale_onscreen =
2031 wxMax(proposed_scale_onscreen,
2032 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2033
2034 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2035 } else
2036 return 1.0;
2037}
2038
2039void ChartCanvas::SetupCanvasQuiltMode() {
2040 if (GetQuiltMode()) // going to quilt mode
2041 {
2042 ChartData->LockCache();
2043
2044 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2045
2046 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2047
2048 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2049 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2050 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2051 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2052
2053 m_Piano->SetRoundedRectangles(true);
2054
2055 // Select the proper Ref chart
2056 int target_new_dbindex = -1;
2057 if (m_pCurrentStack) {
2058 target_new_dbindex =
2059 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2060
2061 if (-1 != target_new_dbindex) {
2062 if (!IsChartQuiltableRef(target_new_dbindex)) {
2063 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2064 int type = ChartData->GetDBChartType(target_new_dbindex);
2065
2066 // walk the stack up looking for a satisfactory chart
2067 int stack_index = m_pCurrentStack->CurrentStackEntry;
2068
2069 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2070 (stack_index >= 0)) {
2071 int proj_tent = ChartData->GetDBChartProj(
2072 m_pCurrentStack->GetDBIndex(stack_index));
2073 int type_tent = ChartData->GetDBChartType(
2074 m_pCurrentStack->GetDBIndex(stack_index));
2075
2076 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2077 if ((proj == proj_tent) && (type_tent == type)) {
2078 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2079 break;
2080 }
2081 }
2082 stack_index++;
2083 }
2084 }
2085 }
2086 }
2087
2088 if (IsChartQuiltableRef(target_new_dbindex))
2089 SelectQuiltRefdbChart(target_new_dbindex,
2090 false); // Try not to allow a scale change
2091 else
2092 SelectQuiltRefdbChart(-1, false);
2093
2094 m_singleChart = NULL; // Bye....
2095
2096 // Re-qualify the quilt reference chart selection
2097 AdjustQuiltRefChart();
2098
2099 // Restore projection type saved on last quilt mode toggle
2100 // TODO
2101 // if(g_sticky_projection != -1)
2102 // GetVP().SetProjectionType(g_sticky_projection);
2103 // else
2104 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2105 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2106
2107 } else // going to SC Mode
2108 {
2109 std::vector<int> empty_array;
2110 m_Piano->SetActiveKeyArray(empty_array);
2111 m_Piano->SetNoshowIndexArray(empty_array);
2112 m_Piano->SetEclipsedIndexArray(empty_array);
2113
2114 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2115 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2116 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2117 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2118 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2119
2120 m_Piano->SetRoundedRectangles(false);
2121 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2122 }
2123
2124 // When shifting from quilt to single chart mode, select the "best" single
2125 // chart to show
2126 if (!GetQuiltMode()) {
2127 if (ChartData && ChartData->IsValid()) {
2128 UnlockQuilt();
2129
2130 double tLat, tLon;
2131 if (m_bFollow == true) {
2132 tLat = gLat;
2133 tLon = gLon;
2134 } else {
2135 tLat = m_vLat;
2136 tLon = m_vLon;
2137 }
2138
2139 if (!m_singleChart) {
2140 // Build a temporary chart stack based on tLat, tLon
2141 ChartStack TempStack;
2142 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2143 m_groupIndex);
2144
2145 // Iterate over the quilt charts actually shown, looking for the
2146 // largest scale chart that will be in the new chartstack.... This
2147 // will (almost?) always be the reference chart....
2148
2149 ChartBase *Candidate_Chart = NULL;
2150 int cur_max_scale = (int)1e8;
2151
2152 ChartBase *pChart = GetFirstQuiltChart();
2153 while (pChart) {
2154 // Is this pChart in new stack?
2155 int tEntry =
2156 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2157 if (tEntry != -1) {
2158 if (pChart->GetNativeScale() < cur_max_scale) {
2159 Candidate_Chart = pChart;
2160 cur_max_scale = pChart->GetNativeScale();
2161 }
2162 }
2163 pChart = GetNextQuiltChart();
2164 }
2165
2166 m_singleChart = Candidate_Chart;
2167
2168 // If the quilt is empty, there is no "best" chart.
2169 // So, open the smallest scale chart in the current stack
2170 if (NULL == m_singleChart) {
2171 m_singleChart = ChartData->OpenStackChartConditional(
2172 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2173 CHART_FAMILY_DONTCARE);
2174 }
2175 }
2176
2177 // Invalidate all the charts in the quilt,
2178 // as any cached data may be region based and not have fullscreen coverage
2179 InvalidateAllQuiltPatchs();
2180
2181 if (m_singleChart) {
2182 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2183 std::vector<int> one_array;
2184 one_array.push_back(dbi);
2185 m_Piano->SetActiveKeyArray(one_array);
2186 }
2187
2188 if (m_singleChart) {
2189 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2190 }
2191 }
2192 // Invalidate the current stack so that it will be rebuilt on next tick
2193 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2194 }
2195}
2196
2197bool ChartCanvas::IsTempMenuBarEnabled() {
2198#ifdef __WXMSW__
2199 int major;
2200 wxGetOsVersion(&major);
2201 return (major >
2202 5); // For Windows, function is only available on Vista and above
2203#else
2204 return true;
2205#endif
2206}
2207
2208double ChartCanvas::GetCanvasRangeMeters() {
2209 int width, height;
2210 GetSize(&width, &height);
2211 int minDimension = wxMin(width, height);
2212
2213 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2214 range *= cos(GetVP().clat * PI / 180.);
2215 return range;
2216}
2217
2218void ChartCanvas::SetCanvasRangeMeters(double range) {
2219 int width, height;
2220 GetSize(&width, &height);
2221 int minDimension = wxMin(width, height);
2222
2223 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2224 SetVPScale(scale_ppm / 2);
2225}
2226
2227bool ChartCanvas::SetUserOwnship() {
2228 // Look for user defined ownship image
2229 // This may be found in the shared data location along with other user
2230 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2231 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2232 double factor_dusk = 0.5;
2233 double factor_night = 0.25;
2234
2235 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2236 m_pos_image_user_day = new wxImage;
2237 *m_pos_image_user_day = pbmp->ConvertToImage();
2238 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2239
2240 int gimg_width = m_pos_image_user_day->GetWidth();
2241 int gimg_height = m_pos_image_user_day->GetHeight();
2242
2243 // Make dusk and night images
2244 m_pos_image_user_dusk = new wxImage;
2245 m_pos_image_user_night = new wxImage;
2246
2247 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2248 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2249
2250 for (int iy = 0; iy < gimg_height; iy++) {
2251 for (int ix = 0; ix < gimg_width; ix++) {
2252 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2253 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2254 m_pos_image_user_day->GetGreen(ix, iy),
2255 m_pos_image_user_day->GetBlue(ix, iy));
2256 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2257 hsv.value = hsv.value * factor_dusk;
2258 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2259 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2260 nrgb.blue);
2261
2262 hsv = wxImage::RGBtoHSV(rgb);
2263 hsv.value = hsv.value * factor_night;
2264 nrgb = wxImage::HSVtoRGB(hsv);
2265 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2266 nrgb.blue);
2267 }
2268 }
2269 }
2270
2271 // Make some alternate greyed out day/dusk/night images
2272 m_pos_image_user_grey_day = new wxImage;
2273 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2274
2275 m_pos_image_user_grey_dusk = new wxImage;
2276 m_pos_image_user_grey_night = new wxImage;
2277
2278 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2279 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2280
2281 for (int iy = 0; iy < gimg_height; iy++) {
2282 for (int ix = 0; ix < gimg_width; ix++) {
2283 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2284 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2285 m_pos_image_user_grey_day->GetGreen(ix, iy),
2286 m_pos_image_user_grey_day->GetBlue(ix, iy));
2287 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2288 hsv.value = hsv.value * factor_dusk;
2289 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2290 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2291 nrgb.blue);
2292
2293 hsv = wxImage::RGBtoHSV(rgb);
2294 hsv.value = hsv.value * factor_night;
2295 nrgb = wxImage::HSVtoRGB(hsv);
2296 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2297 nrgb.blue);
2298 }
2299 }
2300 }
2301
2302 // Make a yellow image for rendering under low accuracy chart conditions
2303 m_pos_image_user_yellow_day = new wxImage;
2304 m_pos_image_user_yellow_dusk = new wxImage;
2305 m_pos_image_user_yellow_night = new wxImage;
2306
2307 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2308 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2309 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2310
2311 for (int iy = 0; iy < gimg_height; iy++) {
2312 for (int ix = 0; ix < gimg_width; ix++) {
2313 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2314 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2315 m_pos_image_user_grey_day->GetGreen(ix, iy),
2316 m_pos_image_user_grey_day->GetBlue(ix, iy));
2317
2318 // Simply remove all "blue" from the greyscaled image...
2319 // so, what is not black becomes yellow.
2320 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2321 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2322 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2323
2324 hsv = wxImage::RGBtoHSV(rgb);
2325 hsv.value = hsv.value * factor_dusk;
2326 nrgb = wxImage::HSVtoRGB(hsv);
2327 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2328
2329 hsv = wxImage::RGBtoHSV(rgb);
2330 hsv.value = hsv.value * factor_night;
2331 nrgb = wxImage::HSVtoRGB(hsv);
2332 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2333 0);
2334 }
2335 }
2336 }
2337
2338 return true;
2339 } else
2340 return false;
2341}
2342
2344 m_display_size_mm = size;
2345
2346 // int sx, sy;
2347 // wxDisplaySize( &sx, &sy );
2348
2349 // Calculate logical pixels per mm for later reference.
2350 wxSize sd = g_Platform->getDisplaySize();
2351 double horizontal = sd.x;
2352 // Set DPI (Win) scale factor
2353 g_scaler = g_Platform->GetDisplayDIPMult(this);
2354
2355 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2356 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2357
2358 if (ps52plib) {
2359 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2360 ps52plib->SetPPMM(m_pix_per_mm);
2361 }
2362
2363 wxString msg;
2364 msg.Printf(
2365 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2366 "%d:%d ",
2367 m_display_size_mm, sd.x, sd.y);
2368 wxLogDebug(msg);
2369
2370 int ssx, ssy;
2371 ssx = g_monitor_info[g_current_monitor].width;
2372 ssy = g_monitor_info[g_current_monitor].height;
2373 msg.Printf("monitor size: %d %d", ssx, ssy);
2374 wxLogDebug(msg);
2375
2376 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2377}
2378#if 0
2379void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2380{
2381 wxString msg(event.m_string.c_str(), wxConvUTF8);
2382 // if cpus are removed between runs
2383 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2384 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2385 }
2386
2387 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2388 {
2389 compress_msg_array.RemoveAt(event.thread);
2390 compress_msg_array.Insert( msg, event.thread);
2391 }
2392 else
2393 compress_msg_array.Add(msg);
2394
2395
2396 wxString combined_msg;
2397 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2398 combined_msg += compress_msg_array[i];
2399 combined_msg += "\n";
2400 }
2401
2402 bool skip = false;
2403 pprog->Update(pprog_count, combined_msg, &skip );
2404 pprog->SetSize(pprog_size);
2405 if(skip)
2406 b_skipout = skip;
2407}
2408#endif
2409void ChartCanvas::InvalidateGL() {
2410 if (!m_glcc) return;
2411#ifdef ocpnUSE_GL
2412 if (g_bopengl) m_glcc->Invalidate();
2413#endif
2414 if (m_Compass) m_Compass->UpdateStatus(true);
2415}
2416
2417int ChartCanvas::GetCanvasChartNativeScale() {
2418 int ret = 1;
2419 if (!VPoint.b_quilt) {
2420 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2421 } else
2422 ret = (int)m_pQuilt->GetRefNativeScale();
2423
2424 return ret;
2425}
2426
2427ChartBase *ChartCanvas::GetChartAtCursor() {
2428 ChartBase *target_chart;
2429 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2430 target_chart = m_singleChart;
2431 else if (VPoint.b_quilt)
2432 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2433 else
2434 target_chart = NULL;
2435 return target_chart;
2436}
2437
2438ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2439 ChartBase *target_chart;
2440 if (VPoint.b_quilt)
2441 target_chart =
2442 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2443 else
2444 target_chart = NULL;
2445 return target_chart;
2446}
2447
2448int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2449 int new_dbIndex = -1;
2450 if (!VPoint.b_quilt) {
2451 if (m_pCurrentStack) {
2452 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2453 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2454 if (sc >= scale) {
2455 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2456 break;
2457 }
2458 }
2459 }
2460 } else {
2461 // Using the current quilt, select a useable reference chart
2462 // Said chart will be in the extended (possibly full-screen) stack,
2463 // And will have a scale equal to or just greater than the stipulated
2464 // value
2465 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2466 if (im > 0) {
2467 for (unsigned int is = 0; is < im; is++) {
2468 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2469 m_pQuilt->GetExtendedStackIndexArray()[is]);
2470 if ((m.Scale_ge(
2471 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2472 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2473 break;
2474 }
2475 }
2476 }
2477 }
2478
2479 return new_dbIndex;
2480}
2481
2482void ChartCanvas::EnablePaint(bool b_enable) {
2483 m_b_paint_enable = b_enable;
2484#ifdef ocpnUSE_GL
2485 if (m_glcc) m_glcc->EnablePaint(b_enable);
2486#endif
2487}
2488
2489bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2490
2491void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2492
2493std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2494 return m_pQuilt->GetQuiltIndexArray();
2495 ;
2496}
2497
2498void ChartCanvas::SetQuiltMode(bool b_quilt) {
2499 VPoint.b_quilt = b_quilt;
2500 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2501}
2502
2503bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2504
2505int ChartCanvas::GetQuiltReferenceChartIndex() {
2506 return m_pQuilt->GetRefChartdbIndex();
2507}
2508
2509void ChartCanvas::InvalidateAllQuiltPatchs() {
2510 m_pQuilt->InvalidateAllQuiltPatchs();
2511}
2512
2513ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2514 return m_pQuilt->GetLargestScaleChart();
2515}
2516
2517ChartBase *ChartCanvas::GetFirstQuiltChart() {
2518 return m_pQuilt->GetFirstChart();
2519}
2520
2521ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2522
2523int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2524
2525void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2526 m_pQuilt->SetHiliteIndex(dbIndex);
2527}
2528
2529void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2530 m_pQuilt->SetHiliteIndexArray(hilite_array);
2531}
2532
2533void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2534 m_pQuilt->ClearHiliteIndexArray();
2535}
2536
2537std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2538 bool flag2) {
2539 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2540}
2541
2542int ChartCanvas::GetQuiltRefChartdbIndex() {
2543 return m_pQuilt->GetRefChartdbIndex();
2544}
2545
2546std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2547 return m_pQuilt->GetExtendedStackIndexArray();
2548}
2549
2550std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2551 return m_pQuilt->GetFullscreenIndexArray();
2552}
2553
2554std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2555 return m_pQuilt->GetEclipsedStackIndexArray();
2556}
2557
2558void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2559
2560double ChartCanvas::GetQuiltMaxErrorFactor() {
2561 return m_pQuilt->GetMaxErrorFactor();
2562}
2563
2564bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2565 return m_pQuilt->IsChartQuiltableRef(db_index);
2566}
2567
2568bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2569 double chartMaxScale =
2570 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2571 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2572}
2573
2574void ChartCanvas::StartMeasureRoute() {
2575 if (!m_routeState) { // no measure tool if currently creating route
2576 if (m_bMeasure_Active) {
2577 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2578 m_pMeasureRoute = NULL;
2579 }
2580
2581 m_bMeasure_Active = true;
2582 m_nMeasureState = 1;
2583 m_bDrawingRoute = false;
2584
2585 SetCursor(*pCursorPencil);
2586 Refresh();
2587 }
2588}
2589
2590void ChartCanvas::CancelMeasureRoute() {
2591 m_bMeasure_Active = false;
2592 m_nMeasureState = 0;
2593 m_bDrawingRoute = false;
2594
2595 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2596 m_pMeasureRoute = NULL;
2597
2598 SetCursor(*pCursorArrow);
2599}
2600
2601ViewPort &ChartCanvas::GetVP() { return VPoint; }
2602
2603void ChartCanvas::SetVP(ViewPort &vp) {
2604 VPoint = vp;
2605 VPoint.SetPixelScale(m_displayScale);
2606}
2607
2608// void ChartCanvas::SetFocus()
2609// {
2610// printf("set %d\n", m_canvasIndex);
2611// //wxWindow:SetFocus();
2612// }
2613
2614void ChartCanvas::TriggerDeferredFocus() {
2615 // #if defined(__WXGTK__) || defined(__WXOSX__)
2616
2617 m_deferredFocusTimer.Start(20, true);
2618
2619#if defined(__WXGTK__) || defined(__WXOSX__)
2620 top_frame::Get()->Raise();
2621#endif
2622
2623 // top_frame::Get()->Raise();
2624 // #else
2625 // SetFocus();
2626 // Refresh(true);
2627 // #endif
2628}
2629
2630void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2631 SetFocus();
2632 Refresh(true);
2633}
2634
2635void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2636 if (SendKeyEventToPlugins(event))
2637 return; // PlugIn did something, and does not want the canvas to do
2638 // anything else
2639
2640 int key_char = event.GetKeyCode();
2641 switch (key_char) {
2642 case '?':
2643 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2644 break;
2645 case '+':
2646 ZoomCanvas(g_plus_minus_zoom_factor, false);
2647 break;
2648 case '-':
2649 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2650 break;
2651 default:
2652 break;
2653 }
2654 if (g_benable_rotate) {
2655 switch (key_char) {
2656 case ']':
2657 RotateCanvas(1);
2658 Refresh();
2659 break;
2660
2661 case '[':
2662 RotateCanvas(-1);
2663 Refresh();
2664 break;
2665
2666 case '\\':
2667 DoRotateCanvas(0);
2668 break;
2669 }
2670 }
2671
2672 event.Skip();
2673}
2674
2675void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2676 if (SendKeyEventToPlugins(event))
2677 return; // PlugIn did something, and does not want the canvas to do
2678 // anything else
2679
2680 bool b_handled = false;
2681
2682 m_modkeys = event.GetModifiers();
2683
2684 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2685
2686#ifdef OCPN_ALT_MENUBAR
2687#ifndef __WXOSX__
2688 // If the permanent menubar is disabled, we show it temporarily when Alt is
2689 // pressed or when Alt + a letter is presssed (for the top-menu-level
2690 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2691 // some special cases.
2692 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2693 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2694 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2695 if (!g_bTempShowMenuBar) {
2696 g_bTempShowMenuBar = true;
2697 top_frame::Get()->ApplyGlobalSettings(false);
2698 }
2699 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2700 event.Skip();
2701 return;
2702 }
2703 // If another key is pressed while Alt is down, do NOT toggle the menus when
2704 // Alt is released
2705 if (event.GetKeyCode() != WXK_ALT) {
2706 m_bMayToggleMenuBar = false;
2707 }
2708 }
2709#endif
2710#endif
2711
2712 // HOTKEYS
2713 switch (event.GetKeyCode()) {
2714 case WXK_TAB:
2715 // parent_frame->SwitchKBFocus( this );
2716 break;
2717
2718 case WXK_MENU:
2719 int x, y;
2720 event.GetPosition(&x, &y);
2721 m_FinishRouteOnKillFocus = false;
2722 CallPopupMenu(x, y);
2723 m_FinishRouteOnKillFocus = true;
2724 break;
2725
2726 case WXK_ALT:
2727 m_modkeys |= wxMOD_ALT;
2728 break;
2729
2730 case WXK_CONTROL:
2731 m_modkeys |= wxMOD_CONTROL;
2732 break;
2733
2734#ifdef __WXOSX__
2735 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2736 case WXK_RAW_CONTROL:
2737 m_modkeys |= wxMOD_RAW_CONTROL;
2738 break;
2739#endif
2740
2741 case WXK_LEFT:
2742 if (m_modkeys == wxMOD_CONTROL)
2743 top_frame::Get()->DoStackDown(this);
2744 else if (g_bsmoothpanzoom) {
2745 StartTimedMovement();
2746 m_panx = -1;
2747 } else {
2748 PanCanvas(-panspeed, 0);
2749 }
2750 b_handled = true;
2751 break;
2752
2753 case WXK_UP:
2754 if (g_bsmoothpanzoom) {
2755 StartTimedMovement();
2756 m_pany = -1;
2757 } else
2758 PanCanvas(0, -panspeed);
2759 b_handled = true;
2760 break;
2761
2762 case WXK_RIGHT:
2763 if (m_modkeys == wxMOD_CONTROL)
2764 top_frame::Get()->DoStackUp(this);
2765 else if (g_bsmoothpanzoom) {
2766 StartTimedMovement();
2767 m_panx = 1;
2768 } else
2769 PanCanvas(panspeed, 0);
2770 b_handled = true;
2771
2772 break;
2773
2774 case WXK_DOWN:
2775 if (g_bsmoothpanzoom) {
2776 StartTimedMovement();
2777 m_pany = 1;
2778 } else
2779 PanCanvas(0, panspeed);
2780 b_handled = true;
2781 break;
2782
2783 case WXK_F2: {
2784 if (event.ShiftDown()) {
2785 double scale = GetVP().view_scale_ppm;
2786 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2787
2788 std::shared_ptr<HostApi> host_api;
2789 host_api = GetHostApi();
2790 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2791
2792 if (api_121)
2793 api_121->SelectChartFamily(m_canvasIndex,
2794 (ChartFamilyEnumPI)target_family);
2795 } else
2796 TogglebFollow();
2797 break;
2798 }
2799 case WXK_F3: {
2800 if (event.ShiftDown()) {
2801 double scale = GetVP().view_scale_ppm;
2802 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2803
2804 std::shared_ptr<HostApi> host_api;
2805 host_api = GetHostApi();
2806 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2807
2808 if (api_121)
2809 api_121->SelectChartFamily(m_canvasIndex,
2810 (ChartFamilyEnumPI)target_family);
2811 } else {
2812 SetShowENCText(!GetShowENCText());
2813 Refresh(true);
2814 InvalidateGL();
2815 }
2816 break;
2817 }
2818 case WXK_F4:
2819 if (!m_bMeasure_Active) {
2820 if (event.ShiftDown())
2821 m_bMeasure_DistCircle = true;
2822 else
2823 m_bMeasure_DistCircle = false;
2824
2825 StartMeasureRoute();
2826 } else {
2827 CancelMeasureRoute();
2828
2829 SetCursor(*pCursorArrow);
2830
2831 // SurfaceToolbar();
2832 InvalidateGL();
2833 Refresh(false);
2834 }
2835
2836 break;
2837
2838 case WXK_F5:
2839 top_frame::Get()->ToggleColorScheme();
2840 top_frame::Get()->Raise();
2841 TriggerDeferredFocus();
2842 break;
2843
2844 case WXK_F6: {
2845 int mod = m_modkeys & wxMOD_SHIFT;
2846 if (mod != m_brightmod) {
2847 m_brightmod = mod;
2848 m_bbrightdir = !m_bbrightdir;
2849 }
2850
2851 if (!m_bbrightdir) {
2852 g_nbrightness -= 10;
2853 if (g_nbrightness <= MIN_BRIGHT) {
2854 g_nbrightness = MIN_BRIGHT;
2855 m_bbrightdir = true;
2856 }
2857 } else {
2858 g_nbrightness += 10;
2859 if (g_nbrightness >= MAX_BRIGHT) {
2860 g_nbrightness = MAX_BRIGHT;
2861 m_bbrightdir = false;
2862 }
2863 }
2864
2865 SetScreenBrightness(g_nbrightness);
2866 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2867
2868 SetFocus(); // just in case the external program steals it....
2869 top_frame::Get()->Raise(); // And reactivate the application main
2870
2871 break;
2872 }
2873
2874 case WXK_F7:
2875 top_frame::Get()->DoStackDown(this);
2876 break;
2877
2878 case WXK_F8:
2879 top_frame::Get()->DoStackUp(this);
2880 break;
2881
2882#ifndef __WXOSX__
2883 case WXK_F9: {
2884 ToggleCanvasQuiltMode();
2885 break;
2886 }
2887#endif
2888
2889 case WXK_F11:
2890 top_frame::Get()->ToggleFullScreen();
2891 b_handled = true;
2892 break;
2893
2894 case WXK_F12: {
2895 if (m_modkeys == wxMOD_ALT) {
2896 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2897 } else {
2898 ToggleChartOutlines();
2899 }
2900 break;
2901 }
2902
2903 case WXK_PAUSE: // Drop MOB
2904 top_frame::Get()->ActivateMOB();
2905 break;
2906
2907 // NUMERIC PAD
2908 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2909 case WXK_PAGEUP: {
2910 ZoomCanvas(g_plus_minus_zoom_factor, false);
2911 break;
2912 }
2913 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2914 case WXK_PAGEDOWN: {
2915 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2916 break;
2917 }
2918 case WXK_DELETE:
2919 case WXK_BACK:
2920 if (m_bMeasure_Active) {
2921 if (m_nMeasureState > 2) {
2922 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2923 m_pMeasureRoute->m_lastMousePointIndex =
2924 m_pMeasureRoute->GetnPoints();
2925 m_nMeasureState--;
2926 top_frame::Get()->RefreshAllCanvas();
2927 } else {
2928 CancelMeasureRoute();
2929 StartMeasureRoute();
2930 }
2931 }
2932 break;
2933 default:
2934 break;
2935 }
2936
2937 if (event.GetKeyCode() < 128) // ascii
2938 {
2939 int key_char = event.GetKeyCode();
2940
2941 // Handle both QWERTY and AZERTY keyboard separately for a few control
2942 // codes
2943 if (!g_b_assume_azerty) {
2944#ifdef __WXMAC__
2945 if (g_benable_rotate) {
2946 switch (key_char) {
2947 // On other platforms these are handled in OnKeyChar, which
2948 // (apparently) works better in some locales. On OS X it is better
2949 // to handle them here, since pressing Alt (which should change the
2950 // rotation speed) changes the key char and so prevents the keys
2951 // from working.
2952 case ']':
2953 RotateCanvas(1);
2954 b_handled = true;
2955 break;
2956
2957 case '[':
2958 RotateCanvas(-1);
2959 b_handled = true;
2960 break;
2961
2962 case '\\':
2963 DoRotateCanvas(0);
2964 b_handled = true;
2965 break;
2966 }
2967 }
2968#endif
2969 } else { // AZERTY
2970 switch (key_char) {
2971 case 43:
2972 ZoomCanvas(g_plus_minus_zoom_factor, false);
2973 break;
2974
2975 case 54: // '-' alpha/num pad
2976 // case 56: // '_' alpha/num pad
2977 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2978 break;
2979 }
2980 }
2981
2982#ifdef __WXOSX__
2983 // Ctrl+Cmd+F toggles fullscreen on macOS
2984 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2985 m_modkeys & wxMOD_RAW_CONTROL) {
2986 top_frame::Get()->ToggleFullScreen();
2987 return;
2988 }
2989#endif
2990
2991 if (event.ControlDown()) key_char -= 64;
2992
2993 if (key_char >= '0' && key_char <= '9')
2994 SetGroupIndex(key_char - '0');
2995 else
2996
2997 switch (key_char) {
2998 case 'A':
2999 SetShowENCAnchor(!GetShowENCAnchor());
3000 ReloadVP();
3001
3002 break;
3003
3004 case 'C':
3005 top_frame::Get()->ToggleColorScheme();
3006 break;
3007
3008 case 'D': {
3009 int x, y;
3010 event.GetPosition(&x, &y);
3011 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3012 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3013 // First find out what kind of chart is being used
3014 if (!pPopupDetailSlider) {
3015 if (VPoint.b_quilt) {
3016 if (m_pQuilt) {
3017 if (m_pQuilt->GetChartAtPix(
3018 VPoint,
3019 wxPoint(
3020 x, y))) // = null if no chart loaded for this point
3021 {
3022 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3023 ->GetChartType();
3024 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3025 ->GetChartFamily();
3026 }
3027 }
3028 } else {
3029 if (m_singleChart) {
3030 ChartType = m_singleChart->GetChartType();
3031 ChartFam = m_singleChart->GetChartFamily();
3032 }
3033 }
3034 // If a charttype is found show the popupslider
3035 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3036 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3038 this, -1, ChartType, ChartFam,
3039 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3040 wxDefaultSize, wxSIMPLE_BORDER, "");
3042 }
3043 } else //( !pPopupDetailSlider ) close popupslider
3044 {
3046 pPopupDetailSlider = NULL;
3047 }
3048 break;
3049 }
3050
3051 case 'E':
3052 m_nmea_log->Show();
3053 m_nmea_log->Raise();
3054 break;
3055
3056 case 'L':
3057 SetShowENCLights(!GetShowENCLights());
3058 ReloadVP();
3059
3060 break;
3061
3062 case 'M':
3063 if (event.ShiftDown())
3064 m_bMeasure_DistCircle = true;
3065 else
3066 m_bMeasure_DistCircle = false;
3067
3068 StartMeasureRoute();
3069 break;
3070
3071 case 'N':
3072 if (g_bInlandEcdis && ps52plib) {
3073 SetENCDisplayCategory((_DisCat)STANDARD);
3074 }
3075 break;
3076
3077 case 'O':
3078 ToggleChartOutlines();
3079 break;
3080
3081 case 'Q':
3082 ToggleCanvasQuiltMode();
3083 break;
3084
3085 case 'P':
3086 top_frame::Get()->ToggleTestPause();
3087 break;
3088 case 'R':
3089 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3090 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3091 g_iNavAidRadarRingsNumberVisible = 1;
3092 else if (!g_bNavAidRadarRingsShown &&
3093 g_iNavAidRadarRingsNumberVisible == 1)
3094 g_iNavAidRadarRingsNumberVisible = 0;
3095 break;
3096 case 'S':
3097 SetShowENCDepth(!m_encShowDepth);
3098 ReloadVP();
3099 break;
3100
3101 case 'T':
3102 SetShowENCText(!GetShowENCText());
3103 ReloadVP();
3104 break;
3105
3106 case 'U':
3107 SetShowENCDataQual(!GetShowENCDataQual());
3108 ReloadVP();
3109 break;
3110
3111 case 'V':
3112 m_bShowNavobjects = !m_bShowNavobjects;
3113 Refresh(true);
3114 break;
3115
3116 case 'W': // W Toggle CPA alarm
3117 ToggleCPAWarn();
3118
3119 break;
3120
3121 case 1: // Ctrl A
3122 TogglebFollow();
3123
3124 break;
3125
3126 case 2: // Ctrl B
3127 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3128 break;
3129
3130 case 13: // Ctrl M // Drop Marker at cursor
3131 {
3132 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3133 break;
3134 }
3135
3136 case 14: // Ctrl N - Activate next waypoint in a route
3137 {
3138 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3139 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3140 if ((indexActive + 1) <= r->GetnPoints()) {
3142 InvalidateGL();
3143 Refresh(false);
3144 }
3145 }
3146 break;
3147 }
3148
3149 case 15: // Ctrl O - Drop Marker at boat's position
3150 {
3151 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3152 break;
3153 }
3154
3155 case 32: // Special needs use space bar
3156 {
3157 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3158 break;
3159 }
3160
3161 case -32: // Ctrl Space // Drop MOB
3162 {
3163 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3164
3165 break;
3166 }
3167
3168 case -20: // Ctrl ,
3169 {
3170 top_frame::Get()->DoSettings();
3171 break;
3172 }
3173 case 17: // Ctrl Q
3174 parent_frame->Close();
3175 return;
3176
3177 case 18: // Ctrl R
3178 StartRoute();
3179 return;
3180
3181 case 20: // Ctrl T
3182 if (NULL == pGoToPositionDialog) // There is one global instance of
3183 // the Go To Position Dialog
3185 pGoToPositionDialog->SetCanvas(this);
3186 pGoToPositionDialog->Show();
3187 break;
3188
3189 case 25: // Ctrl Y
3190 if (undo->AnythingToRedo()) {
3191 undo->RedoNextAction();
3192 InvalidateGL();
3193 Refresh(false);
3194 }
3195 break;
3196
3197 case 26:
3198 if (event.ShiftDown()) { // Shift-Ctrl-Z
3199 if (undo->AnythingToRedo()) {
3200 undo->RedoNextAction();
3201 InvalidateGL();
3202 Refresh(false);
3203 }
3204 } else { // Ctrl Z
3205 if (undo->AnythingToUndo()) {
3206 undo->UndoLastAction();
3207 InvalidateGL();
3208 Refresh(false);
3209 }
3210 }
3211 break;
3212
3213 case 27:
3214 // Generic break
3215 if (m_bMeasure_Active) {
3216 CancelMeasureRoute();
3217
3218 SetCursor(*pCursorArrow);
3219
3220 // SurfaceToolbar();
3221 top_frame::Get()->RefreshAllCanvas();
3222 }
3223
3224 if (m_routeState) // creating route?
3225 {
3226 FinishRoute();
3227 // SurfaceToolbar();
3228 InvalidateGL();
3229 Refresh(false);
3230 }
3231
3232 break;
3233
3234 case 7: // Ctrl G
3235 switch (gamma_state) {
3236 case (0):
3237 r_gamma_mult = 0;
3238 g_gamma_mult = 1;
3239 b_gamma_mult = 0;
3240 gamma_state = 1;
3241 break;
3242 case (1):
3243 r_gamma_mult = 1;
3244 g_gamma_mult = 0;
3245 b_gamma_mult = 0;
3246 gamma_state = 2;
3247 break;
3248 case (2):
3249 r_gamma_mult = 1;
3250 g_gamma_mult = 1;
3251 b_gamma_mult = 1;
3252 gamma_state = 0;
3253 break;
3254 }
3255 SetScreenBrightness(g_nbrightness);
3256
3257 break;
3258
3259 case 9: // Ctrl I
3260 if (event.ControlDown()) {
3261 m_bShowCompassWin = !m_bShowCompassWin;
3262 SetShowGPSCompassWindow(m_bShowCompassWin);
3263 Refresh(false);
3264 }
3265 break;
3266
3267 default:
3268 break;
3269
3270 } // switch
3271 }
3272
3273 // Allow OnKeyChar to catch the key events too.
3274 if (!b_handled) {
3275 event.Skip();
3276 }
3277}
3278
3279void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3280 if (SendKeyEventToPlugins(event))
3281 return; // PlugIn did something, and does not want the canvas to do
3282 // anything else
3283
3284 switch (event.GetKeyCode()) {
3285 case WXK_TAB:
3286 top_frame::Get()->SwitchKBFocus(this);
3287 break;
3288
3289 case WXK_LEFT:
3290 case WXK_RIGHT:
3291 m_panx = 0;
3292 if (!m_pany) m_panspeed = 0;
3293 break;
3294
3295 case WXK_UP:
3296 case WXK_DOWN:
3297 m_pany = 0;
3298 if (!m_panx) m_panspeed = 0;
3299 break;
3300
3301 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3302 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3303 case WXK_PAGEUP:
3304 case WXK_PAGEDOWN:
3305 if (m_mustmove) DoMovement(m_mustmove);
3306
3307 m_zoom_factor = 1;
3308 break;
3309
3310 case WXK_ALT:
3311 m_modkeys &= ~wxMOD_ALT;
3312#ifdef OCPN_ALT_MENUBAR
3313#ifndef __WXOSX__
3314 // If the permanent menu bar is disabled, and we are not in the middle of
3315 // another key combo, then show the menu bar temporarily when Alt is
3316 // released (or hide it if already visible).
3317 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3318 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3319 top_frame::Get()->ApplyGlobalSettings(false);
3320 }
3321 m_bMayToggleMenuBar = true;
3322#endif
3323#endif
3324 break;
3325
3326 case WXK_CONTROL:
3327 m_modkeys &= ~wxMOD_CONTROL;
3328 break;
3329 }
3330
3331 if (event.GetKeyCode() < 128) // ascii
3332 {
3333 int key_char = event.GetKeyCode();
3334
3335 // Handle both QWERTY and AZERTY keyboard separately for a few control
3336 // codes
3337 if (!g_b_assume_azerty) {
3338 switch (key_char) {
3339 case '+':
3340 case '=':
3341 case '-':
3342 case '_':
3343 case 54:
3344 case 56: // '_' alpha/num pad
3345 DoMovement(m_mustmove);
3346
3347 // m_zoom_factor = 1;
3348 break;
3349 case '[':
3350 case ']':
3351 DoMovement(m_mustmove);
3352 m_rotation_speed = 0;
3353 break;
3354 }
3355 } else {
3356 switch (key_char) {
3357 case 43:
3358 case 54: // '-' alpha/num pad
3359 case 56: // '_' alpha/num pad
3360 DoMovement(m_mustmove);
3361
3362 m_zoom_factor = 1;
3363 break;
3364 }
3365 }
3366 }
3367 event.Skip();
3368}
3369
3370void ChartCanvas::ToggleChartOutlines() {
3371 m_bShowOutlines = !m_bShowOutlines;
3372
3373 Refresh(false);
3374
3375#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3376 // needs a full refresh
3377 if (g_bopengl) InvalidateGL();
3378#endif
3379}
3380
3381void ChartCanvas::ToggleLookahead() {
3382 m_bLookAhead = !m_bLookAhead;
3383 m_OSoffsetx = 0; // center ownship
3384 m_OSoffsety = 0;
3385}
3386
3387void ChartCanvas::SetUpMode(int mode) {
3388 m_upMode = mode;
3389
3390 if (mode != NORTH_UP_MODE) {
3391 // Stuff the COGAvg table in case COGUp is selected
3392 double stuff = 0;
3393 if (!std::isnan(gCog)) stuff = gCog;
3394
3395 if (g_COGAvgSec > 0) {
3396 auto cog_table = top_frame::Get()->GetCOGTable();
3397 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3398 }
3399 g_COGAvg = stuff;
3400 top_frame::Get()->StartCogTimer();
3401 } else {
3402 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3403 SetVPRotation(GetVPSkew());
3404 else
3405 SetVPRotation(0); /* reset to north up */
3406 }
3407
3408 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3409 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3410
3411 UpdateGPSCompassStatusBox(true);
3412 top_frame::Get()->DoChartUpdate();
3413}
3414
3415bool ChartCanvas::DoCanvasCOGSet() {
3416 if (GetUpMode() == NORTH_UP_MODE) return false;
3417 double cog_use = g_COGAvg;
3418 if (g_btenhertz) cog_use = gCog;
3419
3420 double rotation = 0;
3421 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3422 rotation = -gHdt * PI / 180.;
3423 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3424 rotation = -cog_use * PI / 180.;
3425
3426 SetVPRotation(rotation);
3427 return true;
3428}
3429
3430double easeOutCubic(double t) {
3431 // Starts quickly and slows down toward the end
3432 return 1.0 - pow(1.0 - t, 3.0);
3433}
3434
3435void ChartCanvas::StartChartDragInertia() {
3436 m_bChartDragging = false;
3437
3438 // Set some parameters
3439 m_chart_drag_inertia_time = 750; // msec
3440 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3441 m_last_elapsed = 0;
3442
3443 // Calculate ending drag velocity
3444 size_t n_vel = 10;
3445 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3446 int xacc = 0;
3447 int yacc = 0;
3448 double tacc = 0;
3449 size_t length = m_drag_vec_t.size();
3450 for (size_t i = 0; i < n_vel; i++) {
3451 xacc += m_drag_vec_x.at(length - 1 - i);
3452 yacc += m_drag_vec_y.at(length - 1 - i);
3453 tacc += m_drag_vec_t.at(length - 1 - i);
3454 }
3455
3456 if (tacc == 0) return;
3457
3458 double drag_velocity_x = xacc / tacc;
3459 double drag_velocity_y = yacc / tacc;
3460 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3461 // drag_velocity_y);
3462
3463 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3464 // touch tap.
3465 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3466
3467 m_chart_drag_velocity_x = drag_velocity_x;
3468 m_chart_drag_velocity_y = drag_velocity_y;
3469
3470 m_chart_drag_inertia_active = true;
3471 // First callback as fast as possible.
3472 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3473}
3474
3475void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3476 if (!m_chart_drag_inertia_active) return;
3477 // Calculate time fraction from 0..1
3478 wxLongLong now = wxGetLocalTimeMillis();
3479 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3480 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3481 if (t > 1.0) t = 1.0;
3482 double e = 1.0 - easeOutCubic(t); // 0..1
3483
3484 double dx =
3485 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3486 double dy =
3487 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3488
3489 m_last_elapsed = elapsed;
3490
3491 // Ensure that target destination lies on whole-pixel boundary
3492 // This allows the render engine to use a faster FBO copy method for drawing
3493 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3494 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3495 double inertia_lat, inertia_lon;
3496 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3497 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3498 // Check if ownship has moved off-screen
3499 if (!IsOwnshipOnScreen()) {
3500 m_bFollow = false; // update the follow flag
3501 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3502 UpdateFollowButtonState();
3503 m_OSoffsetx = 0;
3504 m_OSoffsety = 0;
3505 } else {
3506 m_OSoffsetx += dx;
3507 m_OSoffsety -= dy;
3508 }
3509
3510 Refresh(false);
3511
3512 // Stop condition
3513 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3514 m_chart_drag_inertia_timer.Stop();
3515
3516 // Disable chart pan movement logic
3517 m_target_lat = GetVP().clat;
3518 m_target_lon = GetVP().clon;
3519 m_pan_drag.x = m_pan_drag.y = 0;
3520 m_panx = m_pany = 0;
3521 m_chart_drag_inertia_active = false;
3522 DoCanvasUpdate();
3523
3524 } else {
3525 int target_redraw_interval = 40; // msec
3526 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3527 }
3528}
3529
3530void ChartCanvas::StopMovement() {
3531 m_panx = m_pany = 0;
3532 m_panspeed = 0;
3533 m_zoom_factor = 1;
3534 m_rotation_speed = 0;
3535 m_mustmove = 0;
3536#if 0
3537#if !defined(__WXGTK__) && !defined(__WXQT__)
3538 SetFocus();
3539 top_frame::Get()->Raise();
3540#endif
3541#endif
3542}
3543
3544/* instead of integrating in timer callbacks
3545 (which do not always get called fast enough)
3546 we can perform the integration of movement
3547 at each render frame based on the time change */
3548bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3549 // Start/restart the stop movement timer
3550 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3551
3552 if (!pMovementTimer->IsRunning()) {
3553 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3554 }
3555
3556 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3557 // already moving, gets called again because of key-repeat event
3558 return false;
3559 }
3560
3561 m_last_movement_time = wxDateTime::UNow();
3562
3563 return true;
3564}
3565void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3566 int nstep) {
3567 // Save the target
3568 m_target_lat = target_lat;
3569 m_target_lon = target_lon;
3570
3571 // Save the start point
3572 m_start_lat = GetVP().clat;
3573 m_start_lon = GetVP().clon;
3574
3575 m_VPMovementTimer.Start(1, true); // oneshot
3576 m_timed_move_vp_active = true;
3577 m_stvpc = 0;
3578 m_timedVP_step = nstep;
3579}
3580
3581void ChartCanvas::DoTimedMovementVP() {
3582 if (!m_timed_move_vp_active) return; // not active
3583 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3584 StopMovement();
3585 return;
3586 }
3587 // Stop condition
3588 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3589 double d2 =
3590 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3591 d2 = pow(d2, 0.5);
3592
3593 if (d2 < one_pix) {
3594 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3595 StopMovementVP();
3596 return;
3597 }
3598
3599 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3600 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3601 // StopMovementVP();
3602 // return;
3603 // }
3604
3605 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3606 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3607
3608 m_run_lat = new_lat;
3609 m_run_lon = new_lon;
3610
3611 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3612}
3613
3614void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3615
3616void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3617
3618void ChartCanvas::StartTimedMovementTarget() {}
3619
3620void ChartCanvas::DoTimedMovementTarget() {}
3621
3622void ChartCanvas::StopMovementTarget() {}
3623int ntm;
3624
3625void ChartCanvas::DoTimedMovement() {
3626 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3627 !m_rotation_speed)
3628 return; /* not moving */
3629
3630 wxDateTime now = wxDateTime::UNow();
3631 long dt = 0;
3632 if (m_last_movement_time.IsValid())
3633 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3634
3635 m_last_movement_time = now;
3636
3637 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3638 dt = 500;
3639
3640 DoMovement(dt);
3641}
3642
3644 /* if we get here quickly assume 1ms so that some movement occurs */
3645 if (dt == 0) dt = 1;
3646
3647 m_mustmove -= dt;
3648 if (m_mustmove < 0) m_mustmove = 0;
3649
3650 if (!m_inPinch) { // this stops compound zoom/pan
3651 if (m_pan_drag.x || m_pan_drag.y) {
3652 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3653 m_pan_drag.x = m_pan_drag.y = 0;
3654 }
3655
3656 if (m_panx || m_pany) {
3657 const double slowpan = .1, maxpan = 2;
3658 if (m_modkeys == wxMOD_ALT)
3659 m_panspeed = slowpan;
3660 else {
3661 m_panspeed += (double)dt / 500; /* apply acceleration */
3662 m_panspeed = wxMin(maxpan, m_panspeed);
3663 }
3664 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3665 }
3666 }
3667 if (m_zoom_factor != 1) {
3668 double alpha = 400, beta = 1.5;
3669 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3670
3671 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3672
3673 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3674
3675 // Try to hit the zoom target exactly.
3676 // if(m_wheelzoom_stop_oneshot > 0)
3677 {
3678 if (zoom_factor > 1) {
3679 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3680 zoom_factor = VPoint.chart_scale / m_zoom_target;
3681 }
3682
3683 else if (zoom_factor < 1) {
3684 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3685 zoom_factor = VPoint.chart_scale / m_zoom_target;
3686 }
3687 }
3688
3689 if (fabs(zoom_factor - 1) > 1e-4) {
3690 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3691 } else {
3692 StopMovement();
3693 }
3694
3695 if (m_wheelzoom_stop_oneshot > 0) {
3696 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3697 m_wheelzoom_stop_oneshot = 0;
3698 StopMovement();
3699 }
3700
3701 // Don't overshoot the zoom target.
3702 if (zoom_factor > 1) {
3703 if (VPoint.chart_scale <= m_zoom_target) {
3704 m_wheelzoom_stop_oneshot = 0;
3705 StopMovement();
3706 }
3707 } else if (zoom_factor < 1) {
3708 if (VPoint.chart_scale >= m_zoom_target) {
3709 m_wheelzoom_stop_oneshot = 0;
3710 StopMovement();
3711 }
3712 }
3713 }
3714 }
3715
3716 if (m_rotation_speed) { /* in degrees per second */
3717 double speed = m_rotation_speed;
3718 if (m_modkeys == wxMOD_ALT) speed /= 10;
3719 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3720 }
3721}
3722
3723void ChartCanvas::SetColorScheme(ColorScheme cs) {
3724 SetAlertString("");
3725
3726 // Setup ownship image pointers
3727 switch (cs) {
3728 case GLOBAL_COLOR_SCHEME_DAY:
3729 m_pos_image_red = &m_os_image_red_day;
3730 m_pos_image_grey = &m_os_image_grey_day;
3731 m_pos_image_yellow = &m_os_image_yellow_day;
3732 m_pos_image_user = m_pos_image_user_day;
3733 m_pos_image_user_grey = m_pos_image_user_grey_day;
3734 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3735 m_cTideBitmap = m_bmTideDay;
3736 m_cCurrentBitmap = m_bmCurrentDay;
3737
3738 break;
3739 case GLOBAL_COLOR_SCHEME_DUSK:
3740 m_pos_image_red = &m_os_image_red_dusk;
3741 m_pos_image_grey = &m_os_image_grey_dusk;
3742 m_pos_image_yellow = &m_os_image_yellow_dusk;
3743 m_pos_image_user = m_pos_image_user_dusk;
3744 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3745 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3746 m_cTideBitmap = m_bmTideDusk;
3747 m_cCurrentBitmap = m_bmCurrentDusk;
3748 break;
3749 case GLOBAL_COLOR_SCHEME_NIGHT:
3750 m_pos_image_red = &m_os_image_red_night;
3751 m_pos_image_grey = &m_os_image_grey_night;
3752 m_pos_image_yellow = &m_os_image_yellow_night;
3753 m_pos_image_user = m_pos_image_user_night;
3754 m_pos_image_user_grey = m_pos_image_user_grey_night;
3755 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3756 m_cTideBitmap = m_bmTideNight;
3757 m_cCurrentBitmap = m_bmCurrentNight;
3758 break;
3759 default:
3760 m_pos_image_red = &m_os_image_red_day;
3761 m_pos_image_grey = &m_os_image_grey_day;
3762 m_pos_image_yellow = &m_os_image_yellow_day;
3763 m_pos_image_user = m_pos_image_user_day;
3764 m_pos_image_user_grey = m_pos_image_user_grey_day;
3765 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3766 m_cTideBitmap = m_bmTideDay;
3767 m_cCurrentBitmap = m_bmCurrentDay;
3768 break;
3769 }
3770
3771 CreateDepthUnitEmbossMaps(cs);
3772 CreateOZEmbossMapData(cs);
3773
3774 // Set up fog effect base color
3775 m_fog_color = wxColor(
3776 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3777 float dim = 1.0;
3778 switch (cs) {
3779 case GLOBAL_COLOR_SCHEME_DUSK:
3780 dim = 0.5;
3781 break;
3782 case GLOBAL_COLOR_SCHEME_NIGHT:
3783 dim = 0.25;
3784 break;
3785 default:
3786 break;
3787 }
3788 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3789 m_fog_color.Blue() * dim);
3790
3791 // Really dark
3792#if 0
3793 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3794 SetBackgroundColour( wxColour(0,0,0) );
3795
3796 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3797 }
3798 else{
3799 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3800#ifndef __WXMAC__
3801 SetBackgroundColour( wxNullColour );
3802#endif
3803 }
3804#endif
3805
3806 // UpdateToolbarColorScheme(cs);
3807
3808 m_Piano->SetColorScheme(cs);
3809
3810 m_Compass->SetColorScheme(cs);
3811
3812 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3813
3814 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3815
3816 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3817 if (m_notification_button) {
3818 m_notification_button->SetColorScheme(cs);
3819 }
3820
3821#ifdef ocpnUSE_GL
3822 if (g_bopengl && m_glcc) {
3823 m_glcc->SetColorScheme(cs);
3824 g_glTextureManager->ClearAllRasterTextures();
3825 // m_glcc->FlushFBO();
3826 }
3827#endif
3828 SetbTCUpdate(true); // force re-render of tide/current locators
3829 m_brepaint_piano = true;
3830
3831 ReloadVP();
3832
3833 m_cs = cs;
3834}
3835
3836wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3837 wxImage img = Bitmap.ConvertToImage();
3838 int sx = img.GetWidth();
3839 int sy = img.GetHeight();
3840
3841 wxImage new_img(img);
3842
3843 for (int i = 0; i < sx; i++) {
3844 for (int j = 0; j < sy; j++) {
3845 if (!img.IsTransparent(i, j)) {
3846 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3847 (unsigned char)(img.GetGreen(i, j) * factor),
3848 (unsigned char)(img.GetBlue(i, j) * factor));
3849 }
3850 }
3851 }
3852
3853 wxBitmap ret = wxBitmap(new_img);
3854
3855 return ret;
3856}
3857
3858void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3859 int max) {
3860 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3861 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3862
3863 if (!m_pBrightPopup) {
3864 // Calculate size
3865 int x, y;
3866 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3867
3868 m_pBrightPopup = new TimedPopupWin(this, 3);
3869
3870 m_pBrightPopup->SetSize(x, y);
3871 m_pBrightPopup->Move(120, 120);
3872 }
3873
3874 int bmpsx = m_pBrightPopup->GetSize().x;
3875 int bmpsy = m_pBrightPopup->GetSize().y;
3876
3877 wxBitmap bmp(bmpsx, bmpsx);
3878 wxMemoryDC mdc(bmp);
3879
3880 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3881 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3882 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3883 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3884 mdc.Clear();
3885
3886 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3887
3888 mdc.SetFont(*pfont);
3889 wxString val;
3890
3891 if (brightness == max)
3892 val = "MAX";
3893 else if (brightness == min)
3894 val = "MIN";
3895 else
3896 val.Printf("%3d", brightness);
3897
3898 mdc.DrawText(val, 0, 0);
3899
3900 mdc.SelectObject(wxNullBitmap);
3901
3902 m_pBrightPopup->SetBitmap(bmp);
3903 m_pBrightPopup->Show();
3904 m_pBrightPopup->Refresh();
3905}
3906
3907void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3908 m_b_rot_hidef = true;
3909 ReloadVP();
3910}
3911
3912void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3913 if (!g_bRollover) return;
3914
3915 bool b_need_refresh = false;
3916
3917 wxSize win_size = GetSize() * m_displayScale;
3918 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3919
3920 // Handle the AIS Rollover Window first
3921 bool showAISRollover = false;
3922 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3923 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3924 SelectItem *pFind = pSelectAIS->FindSelection(
3925 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3926 if (pFind) {
3927 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3928 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3929
3930 if (ptarget) {
3931 showAISRollover = true;
3932
3933 if (NULL == m_pAISRolloverWin) {
3934 m_pAISRolloverWin = new RolloverWin(this);
3935 m_pAISRolloverWin->IsActive(false);
3936 b_need_refresh = true;
3937 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3938 m_AISRollover_MMSI != FoundAIS_MMSI) {
3939 // Sometimes the mouse moves fast enough to get over a new AIS
3940 // target before the one-shot has fired to remove the old target.
3941 // Result: wrong target data is shown.
3942 // Detect this case,close the existing rollover ASAP, and restart
3943 // the timer.
3944 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3945 m_pAISRolloverWin->IsActive(false);
3946 m_AISRollover_MMSI = 0;
3947 Refresh();
3948 return;
3949 }
3950
3951 m_AISRollover_MMSI = FoundAIS_MMSI;
3952
3953 if (!m_pAISRolloverWin->IsActive()) {
3954 wxString s = ptarget->GetRolloverString();
3955 m_pAISRolloverWin->SetString(s);
3956
3957 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3958 AIS_ROLLOVER, win_size);
3959 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3960 m_pAISRolloverWin->IsActive(true);
3961 b_need_refresh = true;
3962 }
3963 }
3964 } else {
3965 m_AISRollover_MMSI = 0;
3966 showAISRollover = false;
3967 }
3968 }
3969
3970 // Maybe turn the rollover off
3971 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3972 m_pAISRolloverWin->IsActive(false);
3973 m_AISRollover_MMSI = 0;
3974 b_need_refresh = true;
3975 }
3976
3977 // Now the Route info rollover
3978 // Show the route segment info
3979 bool showRouteRollover = false;
3980
3981 if (NULL == m_pRolloverRouteSeg) {
3982 // Get a list of all selectable sgements, and search for the first
3983 // visible segment as the rollover target.
3984
3985 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3986 SelectableItemList SelList = pSelect->FindSelectionList(
3987 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3988 auto node = SelList.begin();
3989 while (node != SelList.end()) {
3990 SelectItem *pFindSel = *node;
3991
3992 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3993
3994 if (pr && pr->IsVisible()) {
3995 m_pRolloverRouteSeg = pFindSel;
3996 showRouteRollover = true;
3997
3998 if (NULL == m_pRouteRolloverWin) {
3999 m_pRouteRolloverWin = new RolloverWin(this, 10);
4000 m_pRouteRolloverWin->IsActive(false);
4001 }
4002
4003 if (!m_pRouteRolloverWin->IsActive()) {
4004 wxString s;
4005 RoutePoint *segShow_point_a =
4006 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4007 RoutePoint *segShow_point_b =
4008 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4009
4010 double brg, dist;
4011 DistanceBearingMercator(
4012 segShow_point_b->m_lat, segShow_point_b->m_lon,
4013 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4014
4015 if (!pr->m_bIsInLayer)
4016 s.Append(_("Route") + ": ");
4017 else
4018 s.Append(_("Layer Route: "));
4019
4020 if (pr->m_RouteNameString.IsEmpty())
4021 s.Append(_("(unnamed)"));
4022 else
4023 s.Append(pr->m_RouteNameString);
4024
4025 s << "\n"
4026 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4027 << "\n"
4028 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4029 << segShow_point_b->GetName() << "\n";
4030
4031 if (g_bShowTrue)
4032 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4033 (int)floor(brg + 0.5), 0x00B0);
4034 if (g_bShowMag) {
4035 double latAverage =
4036 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4037 double lonAverage =
4038 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4039 double varBrg =
4040 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4041
4042 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4043 (int)floor(varBrg + 0.5), 0x00B0);
4044 }
4045
4046 s << FormatDistanceAdaptive(dist);
4047
4048 // Compute and display cumulative distance from route start point to
4049 // current leg end point and RNG,TTG,ETA from ship to current leg end
4050 // point for active route
4051 double shiptoEndLeg = 0.;
4052 bool validActive = false;
4053 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4054 validActive = true;
4055
4056 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4057 auto node = pr->pRoutePointList->begin();
4058 RoutePoint *prp;
4059 float dist_to_endleg = 0;
4060 wxString t;
4061
4062 for (++node; node != pr->pRoutePointList->end(); ++node) {
4063 prp = *node;
4064 if (validActive)
4065 shiptoEndLeg += prp->m_seg_len;
4066 else if (prp->m_bIsActive)
4067 validActive = true;
4068 dist_to_endleg += prp->m_seg_len;
4069 if (prp->IsSame(segShow_point_a)) break;
4070 }
4071 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4072 }
4073 // write from ship to end selected leg point data if the route is
4074 // active
4075 if (validActive) {
4076 s << "\n"
4077 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4078 shiptoEndLeg +=
4080 ->GetCurrentRngToActivePoint(); // add distance from ship
4081 // to active point
4082 shiptoEndLeg +=
4083 segShow_point_b
4084 ->m_seg_len; // add the lenght of the selected leg
4085 s << FormatDistanceAdaptive(shiptoEndLeg);
4086 // ensure sog/cog are valid and vmg is positive to keep data
4087 // coherent
4088 double vmg = 0.;
4089 if (!std::isnan(gCog) && !std::isnan(gSog))
4090 vmg = gSog *
4091 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4092 PI / 180.);
4093 if (vmg > 0.) {
4094 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4095 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4096 s << " - "
4097 << wxString(ttg_sec > SECONDS_PER_DAY
4098 ? ttg_span.Format(_("%Dd %H:%M"))
4099 : ttg_span.Format(_("%H:%M")));
4100 wxDateTime dtnow, eta;
4101 eta = dtnow.SetToCurrent().Add(ttg_span);
4102 s << " - " << eta.Format("%b").Mid(0, 4)
4103 << eta.Format(" %d %H:%M");
4104 } else
4105 s << " ---- ----";
4106 }
4107 m_pRouteRolloverWin->SetString(s);
4108
4109 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4110 LEG_ROLLOVER, win_size);
4111 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4112 m_pRouteRolloverWin->IsActive(true);
4113 b_need_refresh = true;
4114 showRouteRollover = true;
4115 break;
4116 }
4117 } else {
4118 ++node;
4119 }
4120 }
4121 } else {
4122 // Is the cursor still in select radius, and not timed out?
4123 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4124 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4125 m_pRolloverRouteSeg))
4126 showRouteRollover = false;
4127 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4128 showRouteRollover = false;
4129 else
4130 showRouteRollover = true;
4131 }
4132
4133 // If currently creating a route, do not show this rollover window
4134 if (m_routeState) showRouteRollover = false;
4135
4136 // Similar for AIS target rollover window
4137 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4138 showRouteRollover = false;
4139
4140 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4141 !showRouteRollover) {
4142 m_pRouteRolloverWin->IsActive(false);
4143 m_pRolloverRouteSeg = NULL;
4144 m_pRouteRolloverWin->Destroy();
4145 m_pRouteRolloverWin = NULL;
4146 b_need_refresh = true;
4147 } else if (m_pRouteRolloverWin && showRouteRollover) {
4148 m_pRouteRolloverWin->IsActive(true);
4149 b_need_refresh = true;
4150 }
4151
4152 // Now the Track info rollover
4153 // Show the track segment info
4154 bool showTrackRollover = false;
4155
4156 if (NULL == m_pRolloverTrackSeg) {
4157 // Get a list of all selectable sgements, and search for the first
4158 // visible segment as the rollover target.
4159
4160 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4161 SelectableItemList SelList = pSelect->FindSelectionList(
4162 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4163
4164 auto node = SelList.begin();
4165 while (node != SelList.end()) {
4166 SelectItem *pFindSel = *node;
4167
4168 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4169
4170 if (pt && pt->IsVisible()) {
4171 m_pRolloverTrackSeg = pFindSel;
4172 showTrackRollover = true;
4173
4174 if (NULL == m_pTrackRolloverWin) {
4175 m_pTrackRolloverWin = new RolloverWin(this, 10);
4176 m_pTrackRolloverWin->IsActive(false);
4177 }
4178
4179 if (!m_pTrackRolloverWin->IsActive()) {
4180 wxString s;
4181 TrackPoint *segShow_point_a =
4182 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4183 TrackPoint *segShow_point_b =
4184 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4185
4186 double brg, dist;
4187 DistanceBearingMercator(
4188 segShow_point_b->m_lat, segShow_point_b->m_lon,
4189 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4190
4191 if (!pt->m_bIsInLayer)
4192 s.Append(_("Track") + ": ");
4193 else
4194 s.Append(_("Layer Track: "));
4195
4196 if (pt->GetName().IsEmpty())
4197 s.Append(_("(unnamed)"));
4198 else
4199 s.Append(pt->GetName());
4200 double tlenght = pt->Length();
4201 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4202 if (pt->GetLastPoint()->GetTimeString() &&
4203 pt->GetPoint(0)->GetTimeString()) {
4204 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4205 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4206 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4207 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4208 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4209 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4210 << getUsrSpeedUnit();
4211 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4212 : ttime.Format(" %H:%M"));
4213 }
4214 }
4215
4216 if (g_bShowTrackPointTime &&
4217 strlen(segShow_point_b->GetTimeString())) {
4218 wxString stamp = segShow_point_b->GetTimeString();
4219 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4220 if (timestamp.IsValid()) {
4221 // Format track rollover timestamp to OCPN global TZ setting
4224 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4225 }
4226 s << "\n" << _("Segment Created: ") << stamp;
4227 }
4228
4229 s << "\n";
4230 if (g_bShowTrue)
4231 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4232 0x00B0);
4233
4234 if (g_bShowMag) {
4235 double latAverage =
4236 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4237 double lonAverage =
4238 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4239 double varBrg =
4240 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4241
4242 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4243 0x00B0);
4244 }
4245
4246 s << FormatDistanceAdaptive(dist);
4247
4248 if (segShow_point_a->GetTimeString() &&
4249 segShow_point_b->GetTimeString()) {
4250 wxDateTime apoint = segShow_point_a->GetCreateTime();
4251 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4252 if (apoint.IsValid() && bpoint.IsValid()) {
4253 double segmentSpeed = toUsrSpeed(
4254 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4255 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4256 << getUsrSpeedUnit();
4257 }
4258 }
4259
4260 m_pTrackRolloverWin->SetString(s);
4261
4262 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4263 LEG_ROLLOVER, win_size);
4264 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4265 m_pTrackRolloverWin->IsActive(true);
4266 b_need_refresh = true;
4267 showTrackRollover = true;
4268 break;
4269 }
4270 } else {
4271 ++node;
4272 }
4273 }
4274 } else {
4275 // Is the cursor still in select radius, and not timed out?
4276 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4277 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4278 m_pRolloverTrackSeg))
4279 showTrackRollover = false;
4280 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4281 showTrackRollover = false;
4282 else
4283 showTrackRollover = true;
4284 }
4285
4286 // Similar for AIS target rollover window
4287 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4288 showTrackRollover = false;
4289
4290 // Similar for route rollover window
4291 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4292 showTrackRollover = false;
4293
4294 // TODO We onlt show tracks on primary canvas....
4295 // if(!IsPrimaryCanvas())
4296 // showTrackRollover = false;
4297
4298 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4299 !showTrackRollover) {
4300 m_pTrackRolloverWin->IsActive(false);
4301 m_pRolloverTrackSeg = NULL;
4302 m_pTrackRolloverWin->Destroy();
4303 m_pTrackRolloverWin = NULL;
4304 b_need_refresh = true;
4305 } else if (m_pTrackRolloverWin && showTrackRollover) {
4306 m_pTrackRolloverWin->IsActive(true);
4307 b_need_refresh = true;
4308 }
4309
4310 if (b_need_refresh) Refresh();
4311}
4312
4313void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4314 if ((GetShowENCLights() || m_bsectors_shown) &&
4315 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4316 extendedSectorLegs)) {
4317 if (!m_bsectors_shown) {
4318 ReloadVP(false);
4319 m_bsectors_shown = true;
4320 }
4321 } else {
4322 if (m_bsectors_shown) {
4323 ReloadVP(false);
4324 m_bsectors_shown = false;
4325 }
4326 }
4327
4328// This is here because GTK status window update is expensive..
4329// cairo using pango rebuilds the font every time so is very
4330// inefficient
4331// Anyway, only update the status bar when this timer expires
4332#if defined(__WXGTK__) || defined(__WXQT__)
4333 {
4334 // Check the absolute range of the cursor position
4335 // There could be a window wherein the chart geoereferencing is not
4336 // valid....
4337 double cursor_lat, cursor_lon;
4338 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4339
4340 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4341 while (cursor_lon < -180.) cursor_lon += 360.;
4342
4343 while (cursor_lon > 180.) cursor_lon -= 360.;
4344
4345 SetCursorStatus(cursor_lat, cursor_lon);
4346 }
4347 }
4348#endif
4349}
4350
4351void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4352 if (!top_frame::Get()->GetFrameStatusBar()) return;
4353
4354 wxString s1;
4355 s1 += " ";
4356 s1 += toSDMM(1, cursor_lat);
4357 s1 += " ";
4358 s1 += toSDMM(2, cursor_lon);
4359
4360 if (STAT_FIELD_CURSOR_LL >= 0)
4361 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4362
4363 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4364
4365 double brg, dist;
4366 wxString sm;
4367 wxString st;
4368 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4369 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4370 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4371
4372 wxString s = st + sm;
4373 s << FormatDistanceAdaptive(dist);
4374
4375 // CUSTOMIZATION - LIVE ETA OPTION
4376 // -------------------------------------------------------
4377 // Calculate an "live" ETA based on route starting from the current
4378 // position of the boat and goes to the cursor of the mouse.
4379 // In any case, an standard ETA will be calculated with a default speed
4380 // of the boat to give an estimation of the route (in particular if GPS
4381 // is off).
4382
4383 // Display only if option "live ETA" is selected in Settings > Display >
4384 // General.
4385 if (g_bShowLiveETA) {
4386 float realTimeETA;
4387 float boatSpeed;
4388 float boatSpeedDefault = g_defaultBoatSpeed;
4389
4390 // Calculate Estimate Time to Arrival (ETA) in minutes
4391 // Check before is value not closed to zero (it will make an very big
4392 // number...)
4393 if (!std::isnan(gSog)) {
4394 boatSpeed = gSog;
4395 if (boatSpeed < 0.5) {
4396 realTimeETA = 0;
4397 } else {
4398 realTimeETA = dist / boatSpeed * 60;
4399 }
4400 } else {
4401 realTimeETA = 0;
4402 }
4403
4404 // Add space after distance display
4405 s << " ";
4406 // Display ETA
4407 s << minutesToHoursDays(realTimeETA);
4408
4409 // In any case, display also an ETA with default speed at 6knts
4410
4411 s << " [@";
4412 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4413 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4414 s << " ";
4415 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4416 s << "]";
4417 }
4418 // END OF - LIVE ETA OPTION
4419
4420 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4421}
4422
4423// CUSTOMIZATION - FORMAT MINUTES
4424// -------------------------------------------------------
4425// New function to format minutes into a more readable format:
4426// * Hours + minutes, or
4427// * Days + hours.
4428wxString minutesToHoursDays(float timeInMinutes) {
4429 wxString s;
4430
4431 if (timeInMinutes == 0) {
4432 s << "--min";
4433 }
4434
4435 // Less than 60min, keep time in minutes
4436 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4437 s << wxString::Format("%d", (int)timeInMinutes);
4438 s << "min";
4439 }
4440
4441 // Between 1h and less than 24h, display time in hours, minutes
4442 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4443 int hours;
4444 int min;
4445 hours = (int)timeInMinutes / 60;
4446 min = (int)timeInMinutes % 60;
4447
4448 if (min == 0) {
4449 s << wxString::Format("%d", hours);
4450 s << "h";
4451 } else {
4452 s << wxString::Format("%d", hours);
4453 s << "h";
4454 s << wxString::Format("%d", min);
4455 s << "min";
4456 }
4457
4458 }
4459
4460 // More than 24h, display time in days, hours
4461 else if (timeInMinutes > 24 * 60) {
4462 int days;
4463 int hours;
4464 days = (int)(timeInMinutes / 60) / 24;
4465 hours = (int)(timeInMinutes / 60) % 24;
4466
4467 if (hours == 0) {
4468 s << wxString::Format("%d", days);
4469 s << "d";
4470 } else {
4471 s << wxString::Format("%d", days);
4472 s << "d";
4473 s << wxString::Format("%d", hours);
4474 s << "h";
4475 }
4476 }
4477
4478 return s;
4479}
4480
4481// END OF CUSTOMIZATION - FORMAT MINUTES
4482// Thanks open source code ;-)
4483// -------------------------------------------------------
4484
4485void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4486 double clat, clon;
4487 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4488 *lat = clat;
4489 *lon = clon;
4490}
4491
4492void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4493 wxPoint2DDouble *r) {
4494 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4495}
4496
4498 double rlon, wxPoint2DDouble *r) {
4499 // If the Current Chart is a raster chart, and the
4500 // requested lat/long is within the boundaries of the chart,
4501 // and the VP is not rotated,
4502 // then use the embedded BSB chart georeferencing algorithm
4503 // for greater accuracy
4504 // Additionally, use chart embedded georef if the projection is TMERC
4505 // i.e. NOT MERCATOR and NOT POLYCONIC
4506
4507 // If for some reason the chart rejects the request by returning an error,
4508 // then fall back to Viewport Projection estimate from canvas parameters
4509 if (!g_bopengl && m_singleChart &&
4510 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4511 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4512 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4513 (m_singleChart->GetChartProjectionType() !=
4514 PROJECTION_TRANSVERSE_MERCATOR) &&
4515 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4516 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4517 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4518 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4519 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4520 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4521 // Cur_BSB_Ch->GetCOVRTablenPoints
4522 // ( 0 ), rlon,
4523 // rlat );
4524 // bInside = true;
4525 // if ( bInside )
4526 if (Cur_BSB_Ch) {
4527 // This is a Raster chart....
4528 // If the VP is changing, the raster chart parameters may not yet be
4529 // setup So do that before accessing the chart's embedded
4530 // georeferencing
4531 Cur_BSB_Ch->SetVPRasterParms(vp);
4532 double rpixxd, rpixyd;
4533 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4534 r->m_x = rpixxd;
4535 r->m_y = rpixyd;
4536 return;
4537 }
4538 }
4539 }
4540
4541 // if needed, use the VPoint scaling estimator,
4542 *r = vp.GetDoublePixFromLL(rlat, rlon);
4543}
4544
4545// This routine might be deleted and all of the rendering improved
4546// to have floating point accuracy
4547bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4548 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4549}
4550
4551bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4552 wxPoint *r) {
4553 wxPoint2DDouble p;
4554 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4555
4556 // some projections give nan values when invisible values (other side of
4557 // world) are requested we should stop using integer coordinates or return
4558 // false here (and test it everywhere)
4559 if (std::isnan(p.m_x)) {
4560 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4561 return false;
4562 }
4563
4564 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4565 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4566 else
4567 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4568
4569 return true;
4570}
4571
4572void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4573 double &lon) {
4574 // If the Current Chart is a raster chart, and the
4575 // requested x,y is within the boundaries of the chart,
4576 // and the VP is not rotated,
4577 // then use the embedded BSB chart georeferencing algorithm
4578 // for greater accuracy
4579 // Additionally, use chart embedded georef if the projection is TMERC
4580 // i.e. NOT MERCATOR and NOT POLYCONIC
4581
4582 // If for some reason the chart rejects the request by returning an error,
4583 // then fall back to Viewport Projection estimate from canvas parameters
4584 bool bUseVP = true;
4585
4586 if (!g_bopengl && m_singleChart &&
4587 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4588 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4589 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4590 (m_singleChart->GetChartProjectionType() !=
4591 PROJECTION_TRANSVERSE_MERCATOR) &&
4592 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4593 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4594 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4595 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4596
4597 // TODO maybe need iterative process to validate bInside
4598 // first pass is mercator, then check chart boundaries
4599
4600 if (Cur_BSB_Ch) {
4601 // This is a Raster chart....
4602 // If the VP is changing, the raster chart parameters may not yet be
4603 // setup So do that before accessing the chart's embedded
4604 // georeferencing
4605 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4606
4607 double slat, slon;
4608 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4609 lat = slat;
4610
4611 if (slon < -180.)
4612 slon += 360.;
4613 else if (slon > 180.)
4614 slon -= 360.;
4615
4616 lon = slon;
4617 bUseVP = false;
4618 }
4619 }
4620 }
4621
4622 // if needed, use the VPoint scaling estimator
4623 if (bUseVP) {
4624 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4625 }
4626}
4627
4629 StopMovement();
4630 DoZoomCanvas(factor, false);
4631 extendedSectorLegs.clear();
4632}
4633
4634void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4635 bool stoptimer) {
4636 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4637
4638 if (g_bsmoothpanzoom) {
4639 if (StartTimedMovement(stoptimer)) {
4640 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4641 m_zoom_factor = factor;
4642 }
4643
4644 m_zoom_target = VPoint.chart_scale / factor;
4645 } else {
4646 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4647
4648 DoZoomCanvas(factor, can_zoom_to_cursor);
4649 }
4650
4651 extendedSectorLegs.clear();
4652}
4653
4654void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4655 // possible on startup
4656 if (!ChartData) return;
4657 if (!m_pCurrentStack) return;
4658
4659 /* TODO: queue the quilted loading code to a background thread
4660 so yield is never called from here, and also rendering is not delayed */
4661
4662 // Cannot allow Yield() re-entrancy here
4663 if (m_bzooming) return;
4664 m_bzooming = true;
4665
4666 double old_ppm = GetVP().view_scale_ppm;
4667
4668 // Capture current cursor position for zoom to cursor
4669 double zlat = m_cursor_lat;
4670 double zlon = m_cursor_lon;
4671
4672 double proposed_scale_onscreen =
4673 GetVP().chart_scale /
4674 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4675 bool b_do_zoom = false;
4676
4677 if (factor > 1) {
4678 b_do_zoom = true;
4679
4680 // double zoom_factor = factor;
4681
4682 ChartBase *pc = NULL;
4683
4684 if (!VPoint.b_quilt) {
4685 pc = m_singleChart;
4686 } else {
4687 if (!m_disable_adjust_on_zoom) {
4688 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4689 if (new_db_index >= 0)
4690 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4691 else { // for whatever reason, no reference chart is known
4692 // Choose the smallest scale chart on the current stack
4693 // and then adjust for scale range
4694 int current_ref_stack_index = -1;
4695 if (m_pCurrentStack->nEntry) {
4696 int trial_index =
4697 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4698 m_pQuilt->SetReferenceChart(trial_index);
4699 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4700 if (new_db_index >= 0)
4701 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4702 }
4703 }
4704
4705 if (m_pCurrentStack)
4706 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4707 new_db_index); // highlite the correct bar entry
4708 }
4709 }
4710
4711 if (pc) {
4712 // double target_scale_ppm = GetVPScale() * zoom_factor;
4713 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4714 // target_scale_ppm;
4715
4716 // Query the chart to determine the appropriate zoom range
4717 double min_allowed_scale =
4718 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4719
4720 if (proposed_scale_onscreen < min_allowed_scale) {
4721 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4722 m_zoom_factor = 1; /* stop zooming */
4723 b_do_zoom = false;
4724 } else
4725 proposed_scale_onscreen = min_allowed_scale;
4726 }
4727
4728 } else {
4729 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4730 }
4731
4732 } else if (factor < 1) {
4733 b_do_zoom = true;
4734
4735 ChartBase *pc = NULL;
4736
4737 bool b_smallest = false;
4738
4739 if (!VPoint.b_quilt) { // not quilted
4740 pc = m_singleChart;
4741
4742 if (pc) {
4743 // If m_singleChart is not on the screen, unbound the zoomout
4744 LLBBox viewbox = VPoint.GetBBox();
4745 // BoundingBox chart_box;
4746 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4747 double max_allowed_scale;
4748
4749 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4750
4751 // We can allow essentially unbounded zoomout in single chart mode
4752 // if( ChartData->GetDBBoundingBox( current_index,
4753 // &chart_box ) &&
4754 // !viewbox.IntersectOut( chart_box ) )
4755 // // Clamp the minimum scale zoom-out to the value
4756 // specified by the chart max_allowed_scale =
4757 // wxMin(max_allowed_scale, 4.0 *
4758 // pc->GetNormalScaleMax(
4759 // GetCanvasScaleFactor(),
4760 // GetCanvasWidth() ) );
4761 if (proposed_scale_onscreen > max_allowed_scale) {
4762 m_zoom_factor = 1; /* stop zooming */
4763 proposed_scale_onscreen = max_allowed_scale;
4764 }
4765 }
4766
4767 } else {
4768 if (!m_disable_adjust_on_zoom) {
4769 int new_db_index =
4770 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4771 if (new_db_index >= 0)
4772 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4773
4774 if (m_pCurrentStack)
4775 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4776 new_db_index); // highlite the correct bar entry
4777
4778 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4779
4780 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4781 proposed_scale_onscreen =
4782 wxMin(proposed_scale_onscreen,
4783 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4784 }
4785
4786 // set a minimum scale
4787 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4788 m_absolute_min_scale_ppm)
4789 proposed_scale_onscreen =
4790 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4791 }
4792 }
4793 double new_scale =
4794 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4795
4796 if (b_do_zoom) {
4797 // Disable ZTC if lookahead is ON, and currently b_follow is active
4798 bool b_allow_ztc = true;
4799 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4800 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4801 if (m_bLookAhead) {
4802 double brg, distance;
4803 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4804 &distance);
4805 dir_to_shift = brg;
4806 meters_to_shift = distance * 1852;
4807 }
4808 // Arrange to combine the zoom and pan into one operation for smoother
4809 // appearance
4810 SetVPScale(new_scale, false); // adjust, but deferred refresh
4811 wxPoint r;
4812 GetCanvasPointPix(zlat, zlon, &r);
4813 // this will emit the Refresh()
4814 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4815 } else {
4816 SetVPScale(new_scale);
4817 if (m_bFollow) DoCanvasUpdate();
4818 }
4819 }
4820
4821 m_bzooming = false;
4822}
4823
4824void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4825 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4826 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4827}
4828
4829int rot;
4830void ChartCanvas::RotateCanvas(double dir) {
4831 // SetUpMode(NORTH_UP_MODE);
4832
4833 if (g_bsmoothpanzoom) {
4834 if (StartTimedMovement()) {
4835 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4836 m_rotation_speed = dir * 60;
4837 }
4838 } else {
4839 double speed = dir * 10;
4840 if (m_modkeys == wxMOD_ALT) speed /= 20;
4841 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4842 }
4843}
4844
4845void ChartCanvas::DoRotateCanvas(double rotation) {
4846 while (rotation < 0) rotation += 2 * PI;
4847 while (rotation > 2 * PI) rotation -= 2 * PI;
4848
4849 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4850
4851 SetVPRotation(rotation);
4852 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4853}
4854
4855void ChartCanvas::DoTiltCanvas(double tilt) {
4856 while (tilt < 0) tilt = 0;
4857 while (tilt > .95) tilt = .95;
4858
4859 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4860
4861 VPoint.tilt = tilt;
4862 Refresh(false);
4863}
4864
4865void ChartCanvas::TogglebFollow() {
4866 if (!m_bFollow)
4867 SetbFollow();
4868 else
4869 ClearbFollow();
4870}
4871
4872void ChartCanvas::ClearbFollow() {
4873 m_bFollow = false; // update the follow flag
4874
4875 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4876
4877 UpdateFollowButtonState();
4878
4879 DoCanvasUpdate();
4880 ReloadVP();
4881 top_frame::Get()->SetChartUpdatePeriod();
4882}
4883
4884void ChartCanvas::SetbFollow() {
4885 // Is the OWNSHIP on-screen?
4886 // If not, then reset the OWNSHIP offset to 0 (center screen)
4887 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4888 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4889 m_OSoffsetx = 0;
4890 m_OSoffsety = 0;
4891 }
4892
4893 // Apply the present b_follow offset values to ship position
4894 wxPoint2DDouble p;
4896 p.m_x += m_OSoffsetx;
4897 p.m_y -= m_OSoffsety;
4898
4899 // compute the target center screen lat/lon
4900 double dlat, dlon;
4901 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4902
4903 JumpToPosition(dlat, dlon, GetVPScale());
4904 m_bFollow = true;
4905
4906 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4907 UpdateFollowButtonState();
4908
4909 if (!g_bSmoothRecenter) {
4910 DoCanvasUpdate();
4911 ReloadVP();
4912 }
4913 top_frame::Get()->SetChartUpdatePeriod();
4914}
4915
4916void ChartCanvas::UpdateFollowButtonState() {
4917 if (m_muiBar) {
4918 if (!m_bFollow)
4919 m_muiBar->SetFollowButtonState(0);
4920 else {
4921 if (m_bLookAhead)
4922 m_muiBar->SetFollowButtonState(2);
4923 else
4924 m_muiBar->SetFollowButtonState(1);
4925 }
4926 }
4927
4928#ifdef __ANDROID__
4929 if (!m_bFollow)
4930 androidSetFollowTool(0);
4931 else {
4932 if (m_bLookAhead)
4933 androidSetFollowTool(2);
4934 else
4935 androidSetFollowTool(1);
4936 }
4937#endif
4938
4939 // Look for plugin using API-121 or later
4940 // If found, make the follow state callback.
4941 if (g_pi_manager) {
4942 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4943 if (pic->m_enabled && pic->m_init_state) {
4944 switch (pic->m_api_version) {
4945 case 121: {
4946 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4947 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4948 break;
4949 }
4950 default:
4951 break;
4952 }
4953 }
4954 }
4955 }
4956}
4957
4958void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4959 if (g_bSmoothRecenter && !m_routeState) {
4960 if (StartSmoothJump(lat, lon, scale_ppm))
4961 return;
4962 else {
4963 // move closer to the target destination, and try again
4964 double gcDist, gcBearingEnd;
4965 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4966 &gcBearingEnd);
4967 gcBearingEnd += 180;
4968 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4969 GetCanvasWidth() / GetVPScale(); // meters
4970 double lon_offset =
4971 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4972 double new_lat = lat + (lat_offset / (1852 * 60));
4973 double new_lon = lon + (lon_offset / (1852 * 60));
4974 SetViewPoint(new_lat, new_lon);
4975 ReloadVP();
4976 StartSmoothJump(lat, lon, scale_ppm);
4977 return;
4978 }
4979 }
4980
4981 if (lon > 180.0) lon -= 360.0;
4982 m_vLat = lat;
4983 m_vLon = lon;
4984 StopMovement();
4985 m_bFollow = false;
4986
4987 if (!GetQuiltMode()) {
4988 double skew = 0;
4989 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4990 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4991 } else {
4992 if (scale_ppm != GetVPScale()) {
4993 // XXX should be done in SetViewPoint
4994 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4995 AdjustQuiltRefChart();
4996 }
4997 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4998 }
4999
5000 ReloadVP();
5001
5002 UpdateFollowButtonState();
5003
5004 // TODO
5005 // if( g_pi_manager ) {
5006 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5007 // }
5008}
5009
5010bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5011 // Check distance to jump, in pixels at current chart scale
5012 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5013 // width.
5014 double gcDist;
5015 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5016 double distance_pixels = gcDist * GetVPScale();
5017 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5018 // Jump is too far, try again
5019 return false;
5020 }
5021
5022 // Save where we're coming from
5023 m_startLat = m_vLat;
5024 m_startLon = m_vLon;
5025 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5026
5027 // Save where we want to end up
5028 m_endLat = lat;
5029 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5030 m_endScale = scale_ppm;
5031
5032 // Setup timing
5033 m_animationDuration = 600; // ms
5034 m_animationStart = wxGetLocalTimeMillis();
5035
5036 // Stop any previous movement, ensure no conflicts
5037 StopMovement();
5038 m_bFollow = false;
5039
5040 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5041 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5042 m_animationActive = true;
5043
5044 return true;
5045}
5046
5047void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5048 // Calculate time fraction from 0..1
5049 wxLongLong now = wxGetLocalTimeMillis();
5050 double elapsed = (now - m_animationStart).ToDouble();
5051 double t = elapsed / m_animationDuration.ToDouble();
5052 if (t > 1.0) t = 1.0;
5053
5054 // Ease function for smoother movement
5055 double e = easeOutCubic(t);
5056
5057 // Interpolate lat/lon/scale
5058 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5059 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5060 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5061
5062 // Update viewpoint
5063 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5064 // portion)
5065 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5066 ReloadVP();
5067
5068 // If we reached the end, stop the timer and finalize
5069 if (t >= 1.0) {
5070 m_easeTimer.Stop();
5071 m_animationActive = false;
5072 UpdateFollowButtonState();
5073 ZoomCanvasSimple(1.0001);
5074 DoCanvasUpdate();
5075 ReloadVP();
5076 }
5077}
5078
5079bool ChartCanvas::PanCanvas(double dx, double dy) {
5080 if (!ChartData) return false;
5081 extendedSectorLegs.clear();
5082
5083 double dlat, dlon;
5084 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5085
5086 int iters = 0;
5087 for (;;) {
5088 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5089
5090 if (iters++ > 5) return false;
5091 if (!std::isnan(dlat)) break;
5092
5093 dx *= .5, dy *= .5;
5094 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5095 }
5096
5097 // avoid overshooting the poles
5098 if (dlat > 90)
5099 dlat = 90;
5100 else if (dlat < -90)
5101 dlat = -90;
5102
5103 if (dlon > 360.) dlon -= 360.;
5104 if (dlon < -360.) dlon += 360.;
5105
5106 // This should not really be necessary, but round-trip georef on some
5107 // charts is not perfect, So we can get creep on repeated unidimensional
5108 // pans, and corrupt chart cacheing.......
5109
5110 // But this only works on north-up projections
5111 // TODO: can we remove this now?
5112 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5113 // .001 ) ) {
5114 //
5115 // if( dx == 0 ) dlon = clon;
5116 // if( dy == 0 ) dlat = clat;
5117 // }
5118
5119 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5120
5121 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5122
5123 if (VPoint.b_quilt) {
5124 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5125 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5126 // Tweak the scale slightly for a new ref chart
5127 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5128 if (pc) {
5129 double tweak_scale_ppm =
5130 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5131 SetVPScale(tweak_scale_ppm);
5132 }
5133 }
5134
5135 if (new_ref_dbIndex == -1) {
5136#pragma GCC diagnostic push
5137#pragma GCC diagnostic ignored "-Warray-bounds"
5138 // The compiler sees a -1 index being used. Does not happen, though.
5139
5140 // for whatever reason, no reference chart is known
5141 // Probably panned out of the coverage region
5142 // If any charts are anywhere on-screen, choose the smallest
5143 // scale chart on the screen to be a new reference chart.
5144 int trial_index = -1;
5145 if (m_pCurrentStack->nEntry) {
5146 int trial_index =
5147 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5148 }
5149
5150 if (trial_index < 0) {
5151 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5152 if (full_screen_array.size())
5153 trial_index = full_screen_array[full_screen_array.size() - 1];
5154 }
5155
5156 if (trial_index >= 0) {
5157 m_pQuilt->SetReferenceChart(trial_index);
5158 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5159 VPoint.rotation);
5160 ReloadVP();
5161 }
5162#pragma GCC diagnostic pop
5163 }
5164 }
5165
5166 // Turn off bFollow only if the ownship has left the screen
5167 if (m_bFollow) {
5168 double offx, offy;
5169 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5170
5171 double offset_angle = atan2(offy, offx);
5172 double offset_distance = sqrt((offy * offy) + (offx * offx));
5173 double chart_angle = GetVPRotation();
5174 double target_angle = chart_angle - offset_angle;
5175 double d_east_mod = offset_distance * cos(target_angle);
5176 double d_north_mod = offset_distance * sin(target_angle);
5177
5178 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5179 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5180
5181 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5182 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5183 m_bFollow = false; // update the follow flag
5184 UpdateFollowButtonState();
5185 }
5186 }
5187
5188 Refresh(false);
5189
5190 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5191
5192 return true;
5193}
5194
5195bool ChartCanvas::IsOwnshipOnScreen() {
5196 wxPoint r;
5198 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5199 ((r.y > 0) && r.y < GetCanvasHeight()))
5200 return true;
5201 else
5202 return false;
5203}
5204
5205void ChartCanvas::ReloadVP(bool b_adjust) {
5206 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5207
5208 LoadVP(VPoint, b_adjust);
5209}
5210
5211void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5212#ifdef ocpnUSE_GL
5213 if (g_bopengl && m_glcc) {
5214 m_glcc->Invalidate();
5215 if (m_glcc->GetSize() != GetSize()) {
5216 m_glcc->SetSize(GetSize());
5217 }
5218 } else
5219#endif
5220 {
5221 m_cache_vp.Invalidate();
5222 m_bm_cache_vp.Invalidate();
5223 }
5224
5225 VPoint.Invalidate();
5226
5227 if (m_pQuilt) m_pQuilt->Invalidate();
5228
5229 // Make sure that the Selected Group is sensible...
5230 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5231 // m_groupIndex = 0;
5232 // if( !CheckGroup( m_groupIndex ) )
5233 // m_groupIndex = 0;
5234
5235 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5236 vp.m_projection_type, b_adjust);
5237}
5238
5239void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5240 m_pQuilt->SetReferenceChart(dbIndex);
5241 VPoint.Invalidate();
5242 m_pQuilt->Invalidate();
5243}
5244
5245double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5246 if (m_pQuilt)
5247 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5248 else
5249 return vp.view_scale_ppm;
5250}
5251
5252// Verify and adjust the current reference chart,
5253// so that it will not lead to excessive overzoom or underzoom onscreen
5254int ChartCanvas::AdjustQuiltRefChart() {
5255 int ret = -1;
5256 if (m_pQuilt) {
5257 wxASSERT(ChartData);
5258 ChartBase *pc =
5259 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5260 if (pc) {
5261 double min_ref_scale =
5262 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5263 double max_ref_scale =
5264 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5265
5266 if (VPoint.chart_scale < min_ref_scale) {
5267 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5268 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5269 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5270 } else {
5271 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5272
5273 if (!brender_ok) {
5274 int target_stack_index = wxNOT_FOUND;
5275 int il = 0;
5276 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5277 if (index == m_pQuilt->GetRefChartdbIndex()) {
5278 target_stack_index = il;
5279 break;
5280 }
5281 il++;
5282 }
5283 if (wxNOT_FOUND == target_stack_index) // should never happen...
5284 target_stack_index = 0;
5285
5286 int ref_family = pc->GetChartFamily();
5287 int extended_array_count =
5288 m_pQuilt->GetExtendedStackIndexArray().size();
5289 while ((!brender_ok) &&
5290 ((int)target_stack_index < (extended_array_count - 1))) {
5291 target_stack_index++;
5292 int test_db_index =
5293 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5294
5295 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5296 IsChartQuiltableRef(test_db_index)) {
5297 // open the target, and check the min_scale
5298 ChartBase *ptest_chart =
5299 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5300 if (ptest_chart) {
5301 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5302 }
5303 }
5304 }
5305
5306 if (brender_ok) { // found a better reference chart
5307 int new_db_index =
5308 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5309 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5310 IsChartQuiltableRef(new_db_index)) {
5311 m_pQuilt->SetReferenceChart(new_db_index);
5312 ret = new_db_index;
5313 } else
5314 ret = m_pQuilt->GetRefChartdbIndex();
5315 } else
5316 ret = m_pQuilt->GetRefChartdbIndex();
5317
5318 } else
5319 ret = m_pQuilt->GetRefChartdbIndex();
5320 }
5321 } else
5322 ret = -1;
5323 }
5324
5325 return ret;
5326}
5327
5328void ChartCanvas::UpdateCanvasOnGroupChange() {
5329 delete m_pCurrentStack;
5330 m_pCurrentStack = new ChartStack;
5331 wxASSERT(ChartData);
5332 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5333 m_groupIndex);
5334
5335 if (m_pQuilt) {
5336 m_pQuilt->Compose(VPoint);
5337 SetFocus();
5338 }
5339}
5340
5341bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5342 double latNE, double lonNE) {
5343 // Center Point
5344 double latc = (latSW + latNE) / 2.0;
5345 double lonc = (lonSW + lonNE) / 2.0;
5346
5347 // Get scale in ppm (latitude)
5348 double ne_easting, ne_northing;
5349 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5350
5351 double sw_easting, sw_northing;
5352 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5353
5354 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5355
5356 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5357}
5358
5359bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5360 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5361 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5362}
5363
5364bool ChartCanvas::SetVPProjection(int projection) {
5365 if (!g_bopengl) // alternative projections require opengl
5366 return false;
5367
5368 // the view scale varies depending on geographic location and projection
5369 // rescale to keep the relative scale on the screen the same
5370 double prev_true_scale_ppm = m_true_scale_ppm;
5371 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5372 VPoint.skew, VPoint.rotation, projection) &&
5373 SetVPScale(wxMax(
5374 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5375 m_absolute_min_scale_ppm));
5376}
5377
5378bool ChartCanvas::SetViewPoint(double lat, double lon) {
5379 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5380 VPoint.rotation);
5381}
5382
5383bool ChartCanvas::SetVPRotation(double angle) {
5384 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5385 VPoint.skew, angle);
5386}
5387bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5388 double skew, double rotation, int projection,
5389 bool b_adjust, bool b_refresh) {
5390 if (ChartData->IsBusy()) return false;
5391 bool b_ret = false;
5392 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5393 skew -= 2 * PI;
5394 // Any sensible change?
5395 if (VPoint.IsValid()) {
5396 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5397 (fabs(VPoint.skew - skew) < 1e-9) &&
5398 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5399 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5400 (VPoint.m_projection_type == projection ||
5401 projection == PROJECTION_UNKNOWN))
5402 return false;
5403 }
5404 if (VPoint.m_projection_type != projection)
5405 VPoint.InvalidateTransformCache(); // invalidate
5406
5407 // Take a local copy of the last viewport
5408 ViewPort last_vp = VPoint;
5409
5410 VPoint.skew = skew;
5411 VPoint.clat = lat;
5412 VPoint.clon = lon;
5413 VPoint.rotation = rotation;
5414 VPoint.view_scale_ppm = scale_ppm;
5415 if (projection != PROJECTION_UNKNOWN)
5416 VPoint.SetProjectionType(projection);
5417 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5418 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5419
5420 // don't allow latitude above 88 for mercator (90 is infinity)
5421 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5422 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5423 if (VPoint.clat > 89.5)
5424 VPoint.clat = 89.5;
5425 else if (VPoint.clat < -89.5)
5426 VPoint.clat = -89.5;
5427 }
5428
5429 // don't zoom out too far for transverse mercator polyconic until we resolve
5430 // issues
5431 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5432 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5433 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5434
5435 // SetVPRotation(rotation);
5436
5437 if (!g_bopengl) // tilt is not possible without opengl
5438 VPoint.tilt = 0;
5439
5440 if ((VPoint.pix_width <= 0) ||
5441 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5442 return false;
5443
5444 bool bwasValid = VPoint.IsValid();
5445 VPoint.Validate(); // Mark this ViewPoint as OK
5446
5447 // Has the Viewport scale changed? If so, invalidate the vp
5448 if (last_vp.view_scale_ppm != scale_ppm) {
5449 m_cache_vp.Invalidate();
5450 InvalidateGL();
5451 }
5452
5453 // A preliminary value, may be tweaked below
5454 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5455
5456 // recompute cursor position
5457 // and send to interested plugins if the mouse is actually in this window
5458 int mouseX = mouse_x;
5459 int mouseY = mouse_y;
5460 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5461 (mouseY < VPoint.pix_height)) {
5462 double lat, lon;
5463 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5464 m_cursor_lat = lat;
5465 m_cursor_lon = lon;
5466 SendCursorLatLonToAllPlugIns(lat, lon);
5467 }
5468
5469 if (!VPoint.b_quilt && m_singleChart) {
5470 VPoint.SetBoxes();
5471
5472 // Allow the chart to adjust the new ViewPort for performance optimization
5473 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5474 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5475
5476 // If there is a sensible change in the chart render, refresh the whole
5477 // screen
5478 if ((!m_cache_vp.IsValid()) ||
5479 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5480 Refresh(false);
5481 b_ret = true;
5482 } else {
5483 wxPoint cp_last, cp_this;
5484 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5485 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5486
5487 if (cp_last != cp_this) {
5488 Refresh(false);
5489 b_ret = true;
5490 }
5491 }
5492 // Create the stack
5493 if (m_pCurrentStack) {
5494 assert(ChartData != 0);
5495 int current_db_index;
5496 current_db_index =
5497 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5498
5499 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5500 m_groupIndex);
5501 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5502 }
5503
5504 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5505 }
5506
5507 // Handle the quilted case
5508 if (VPoint.b_quilt) {
5509 VPoint.SetBoxes();
5510
5511 if (last_vp.view_scale_ppm != scale_ppm)
5512 m_pQuilt->InvalidateAllQuiltPatchs();
5513
5514 // Create the quilt
5515 if (ChartData /*&& ChartData->IsValid()*/) {
5516 if (!m_pCurrentStack) return false;
5517
5518 int current_db_index;
5519 current_db_index =
5520 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5521
5522 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5523 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5524
5525 // Check to see if the current quilt reference chart is in the new stack
5526 int current_ref_stack_index = -1;
5527 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5528 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5529 current_ref_stack_index = i;
5530 }
5531
5532 if (g_bFullScreenQuilt) {
5533 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5534 }
5535
5536 // We might need a new Reference Chart
5537 bool b_needNewRef = false;
5538
5539 // If the new stack does not contain the current ref chart....
5540 if ((-1 == current_ref_stack_index) &&
5541 (m_pQuilt->GetRefChartdbIndex() >= 0))
5542 b_needNewRef = true;
5543
5544 // Would the current Ref Chart be excessively underzoomed?
5545 // We need to check this here to be sure, since we cannot know where the
5546 // reference chart was assigned. For instance, the reference chart may
5547 // have been selected from the config file, or from a long jump with a
5548 // chart family switch implicit. Anyway, we check to be sure....
5549 bool renderable = true;
5550 ChartBase *referenceChart =
5551 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5552 if (referenceChart) {
5553 double chartMaxScale = referenceChart->GetNormalScaleMax(
5554 GetCanvasScaleFactor(), GetCanvasWidth());
5555 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5556 }
5557 if (!renderable) b_needNewRef = true;
5558
5559 // Need new refchart?
5560 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5561 const ChartTableEntry &cte_ref =
5562 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5563 int target_scale = cte_ref.GetScale();
5564 int target_type = cte_ref.GetChartType();
5565 int candidate_stack_index;
5566
5567 // reset the ref chart in a way that does not lead to excessive
5568 // underzoom, for performance reasons Try to find a chart that is the
5569 // same type, and has a scale of just smaller than the current ref
5570 // chart
5571
5572 candidate_stack_index = 0;
5573 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5574 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5575 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5576 int candidate_scale = cte_candidate.GetScale();
5577 int candidate_type = cte_candidate.GetChartType();
5578
5579 if ((candidate_scale >= target_scale) &&
5580 (candidate_type == target_type)) {
5581 bool renderable = true;
5582 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5583 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5584 if (tentative_referenceChart) {
5585 double chartMaxScale =
5586 tentative_referenceChart->GetNormalScaleMax(
5587 GetCanvasScaleFactor(), GetCanvasWidth());
5588 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5589 }
5590
5591 if (renderable) break;
5592 }
5593
5594 candidate_stack_index++;
5595 }
5596
5597 // If that did not work, look for a chart of just larger scale and
5598 // same type
5599 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5600 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5601 while (candidate_stack_index >= 0) {
5602 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5603 if (idx >= 0) {
5604 const ChartTableEntry &cte_candidate =
5605 ChartData->GetChartTableEntry(idx);
5606 int candidate_scale = cte_candidate.GetScale();
5607 int candidate_type = cte_candidate.GetChartType();
5608
5609 if ((candidate_scale <= target_scale) &&
5610 (candidate_type == target_type))
5611 break;
5612 }
5613 candidate_stack_index--;
5614 }
5615 }
5616
5617 // and if that did not work, chose stack entry 0
5618 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5619 (candidate_stack_index < 0))
5620 candidate_stack_index = 0;
5621
5622 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5623
5624 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5625 }
5626
5627 if (!g_bopengl) {
5628 // Preset the VPoint projection type to match what the quilt projection
5629 // type will be
5630 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5631
5632 // Always keep the default Mercator projection if the reference chart is
5633 // not in the PatchList or the scale is too small for it to render.
5634
5635 bool renderable = true;
5636 ChartBase *referenceChart =
5637 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5638 if (referenceChart) {
5639 double chartMaxScale = referenceChart->GetNormalScaleMax(
5640 GetCanvasScaleFactor(), GetCanvasWidth());
5641 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5642 proj = ChartData->GetDBChartProj(ref_db_index);
5643 } else
5644 proj = PROJECTION_MERCATOR;
5645
5646 VPoint.b_MercatorProjectionOverride =
5647 (m_pQuilt->GetnCharts() == 0 || !renderable);
5648
5649 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5650
5651 VPoint.SetProjectionType(proj);
5652 }
5653
5654 // If this quilt will be a perceptible delta from the existing quilt,
5655 // then refresh the entire screen
5656 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5657 // Allow the quilt to adjust the new ViewPort for performance
5658 // optimization This will normally be only a fractional (i.e.
5659 // sub-pixel) adjustment...
5660 if (b_adjust) {
5661 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5662 }
5663
5664 // ChartData->ClearCacheInUseFlags();
5665 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5666
5667 // wxStopWatch sw;
5668
5669#ifdef __ANDROID__
5670 // This is an optimization for panning on touch screen systems.
5671 // The quilt composition is deferred until the OnPaint() message gets
5672 // finally removed and processed from the message queue.
5673 // Takes advantage of the fact that touch-screen pan gestures are
5674 // usually short in distance,
5675 // so not requiring a full quilt rebuild until the pan gesture is
5676 // complete.
5677 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5678 // qDebug() << "Force compose";
5679 m_pQuilt->Compose(VPoint);
5680 } else {
5681 m_pQuilt->Invalidate();
5682 }
5683#else
5684 m_pQuilt->Compose(VPoint);
5685#endif
5686
5687 // printf("comp time %ld\n", sw.Time());
5688
5689 // If the extended chart stack has changed, invalidate any cached
5690 // render bitmap
5691 // if(m_pQuilt->GetXStackHash() != hash1) {
5692 // m_bm_cache_vp.Invalidate();
5693 // InvalidateGL();
5694 // }
5695
5696 ChartData->PurgeCacheUnusedCharts(0.7);
5697
5698 if (b_refresh) Refresh(false);
5699
5700 b_ret = true;
5701 }
5702 }
5703
5704 VPoint.skew = 0.; // Quilting supports 0 Skew
5705 } else if (!g_bopengl) {
5706 OcpnProjType projection = PROJECTION_UNKNOWN;
5707 if (m_singleChart) // viewport projection must match chart projection
5708 // without opengl
5709 projection = m_singleChart->GetChartProjectionType();
5710 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5711 VPoint.SetProjectionType(projection);
5712 }
5713
5714 // Has the Viewport projection changed? If so, invalidate the vp
5715 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5716 m_cache_vp.Invalidate();
5717 InvalidateGL();
5718 }
5719
5720 UpdateCanvasControlBar(); // Refresh the Piano
5721
5722 VPoint.chart_scale = 1.0; // fallback default value
5723
5724 if (VPoint.GetBBox().GetValid()) {
5725 // Update the viewpoint reference scale
5726 if (m_singleChart)
5727 VPoint.ref_scale = m_singleChart->GetNativeScale();
5728 else {
5729#ifdef __ANDROID__
5730 // This is an optimization for panning on touch screen systems.
5731 // See above.
5732 // Quilt might not be fully composed at this point, so for cm93
5733 // the reference scale may not be known.
5734 // In this case, do not update the VP ref_scale.
5735 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5736 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5737 }
5738#else
5739 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5740#endif
5741 }
5742
5743 // Calculate the on-screen displayed actual scale
5744 // by a simple traverse northward from the center point
5745 // of roughly one eighth of the canvas height
5746 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5747
5748 double delta_check =
5749 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5750 delta_check /= 8.;
5751
5752 double check_point = wxMin(89., VPoint.clat);
5753
5754 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5755
5756 double rhumbDist;
5757 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5758 VPoint.clon, 0, &rhumbDist);
5759
5760 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5761 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5762 // Calculate the distance between r1 and r in physical pixels.
5763 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5764 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5765
5766 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5767
5768 // A fall back in case of very high zoom-out, giving delta_y == 0
5769 // which can probably only happen with vector charts
5770 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5771
5772 // Another fallback, for highly zoomed out charts
5773 // This adjustment makes the displayed TrueScale correspond to the
5774 // same algorithm used to calculate the chart zoom-out limit for
5775 // ChartDummy.
5776 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5777
5778 if (m_true_scale_ppm)
5779 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5780 else
5781 VPoint.chart_scale = 1.0;
5782
5783 // Create a nice renderable string
5784 double round_factor = 1000.;
5785 if (VPoint.chart_scale <= 1000.)
5786 round_factor = 10.;
5787 else if (VPoint.chart_scale <= 10000.)
5788 round_factor = 100.;
5789 else if (VPoint.chart_scale <= 100000.)
5790 round_factor = 1000.;
5791
5792 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5793 double retina_coef = 1;
5794#ifdef ocpnUSE_GL
5795#ifdef __WXOSX__
5796 if (g_bopengl) {
5797 retina_coef = GetContentScaleFactor();
5798 }
5799#endif
5800#endif
5801
5802 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5803 // rounded to the nearest 10, 100 or 1000.
5804 //
5805 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5806 // true_scale_display. That does not make sense. The chart scale should be
5807 // the same as the true scale within the limits of the rounding factor.
5808 double true_scale_display =
5809 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5810 wxString text;
5811
5812 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5813
5814 if (m_displayed_scale_factor > 10.0)
5815 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5816 m_displayed_scale_factor);
5817 else if (m_displayed_scale_factor > 1.0)
5818 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5819 m_displayed_scale_factor);
5820 else if (m_displayed_scale_factor > 0.1) {
5821 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5822 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5823 } else if (m_displayed_scale_factor > 0.01) {
5824 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5825 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5826 } else {
5827 text.Printf(
5828 "%s %4.0f (---)", _("Scale"),
5829 true_scale_display); // Generally, no chart, so no chart scale factor
5830 }
5831
5832 m_scaleValue = true_scale_display;
5833 m_scaleText = text;
5834 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5835
5836 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5837 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5838 STAT_FIELD_SCALE)) {
5839 // Check to see if the text will fit in the StatusBar field...
5840 bool b_noshow = false;
5841 {
5842 int w = 0;
5843 int h;
5844 wxClientDC dc(top_frame::Get()->GetStatusBar());
5845 if (dc.IsOk()) {
5846 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5847 dc.SetFont(*templateFont);
5848 dc.GetTextExtent(text, &w, &h);
5849
5850 // If text is too long for the allocated field, try to reduce the text
5851 // string a bit.
5852 wxRect rect;
5853 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5854 rect);
5855 if (w && w > rect.width) {
5856 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5857 }
5858
5859 // Test again...if too big still, then give it up.
5860 dc.GetTextExtent(text, &w, &h);
5861
5862 if (w && w > rect.width) {
5863 b_noshow = true;
5864 }
5865 }
5866 }
5867
5868 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5869 }
5870 }
5871
5872 // Maintain member vLat/vLon
5873 m_vLat = VPoint.clat;
5874 m_vLon = VPoint.clon;
5875
5876 return b_ret;
5877}
5878
5879// Static Icon definitions for some symbols requiring
5880// scaling/rotation/translation Very specific wxDC draw commands are
5881// necessary to properly render these icons...See the code in
5882// ShipDraw()
5883
5884// This icon was adapted and scaled from the S52 Presentation Library
5885// version 3_03.
5886// Symbol VECGND02
5887
5888static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5889
5890// This ownship icon was adapted and scaled from the S52 Presentation
5891// Library version 3_03 Symbol OWNSHP05
5892static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5893 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5894
5895wxColour ChartCanvas::PredColor() {
5896 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5897 // visibility.
5898 if (SHIP_NORMAL == m_ownship_state)
5899 return GetGlobalColor("URED");
5900
5901 else if (SHIP_LOWACCURACY == m_ownship_state)
5902 return GetGlobalColor("YELO1");
5903
5904 return GetGlobalColor("NODTA");
5905}
5906
5907wxColour ChartCanvas::ShipColor() {
5908 // Establish ship color
5909 // It changes color based on GPS and Chart accuracy/availability
5910
5911 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5912
5913 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5914
5915 return GetGlobalColor("URED"); // default is OK
5916}
5917
5918void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5919 wxPoint2DDouble lShipMidPoint) {
5920 dc.SetPen(wxPen(PredColor(), 2));
5921
5922 if (SHIP_NORMAL == m_ownship_state)
5923 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5924 else
5925 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5926
5927 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5928 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5929
5930 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5931 lShipMidPoint.m_y);
5932 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5933 lShipMidPoint.m_y + 12);
5934}
5935
5936void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5937 wxPoint GPSOffsetPixels,
5938 wxPoint2DDouble lGPSPoint) {
5939 // if (m_animationActive) return;
5940 // Develop a uniform length for course predictor line dash length, based on
5941 // physical display size Use this reference length to size all other graphics
5942 // elements
5943 float ref_dim = m_display_size_mm / 24;
5944 ref_dim = wxMin(ref_dim, 12);
5945 ref_dim = wxMax(ref_dim, 6);
5946
5947 wxColour cPred;
5948 cPred.Set(g_cog_predictor_color);
5949 if (cPred == wxNullColour) cPred = PredColor();
5950
5951 // Establish some graphic element line widths dependent on the platform
5952 // display resolution
5953 // double nominal_line_width_pix = wxMax(1.0,
5954 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5955 // not less than 1 pixel
5956 double nominal_line_width_pix = wxMax(
5957 1.0,
5958 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5959
5960 // If the calculated value is greater than the config file spec value, then
5961 // use it.
5962 if (nominal_line_width_pix > g_cog_predictor_width)
5963 g_cog_predictor_width = nominal_line_width_pix;
5964
5965 // Calculate ownship Position Predictor
5966 wxPoint lPredPoint, lHeadPoint;
5967
5968 float pCog = std::isnan(gCog) ? 0 : gCog;
5969 float pSog = std::isnan(gSog) ? 0 : gSog;
5970
5971 double pred_lat, pred_lon;
5972 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5973 &pred_lat, &pred_lon);
5974 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5975
5976 // test to catch the case where COG/HDG line crosses the screen
5977 LLBBox box;
5978
5979 // Should we draw the Head vector?
5980 // Compare the points lHeadPoint and lPredPoint
5981 // If they differ by more than n pixels, and the head vector is valid, then
5982 // render the head vector
5983
5984 float ndelta_pix = 10.;
5985 double hdg_pred_lat, hdg_pred_lon;
5986 bool b_render_hdt = false;
5987 if (!std::isnan(gHdt)) {
5988 // Calculate ownship Heading pointer as a predictor
5989 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5990 &hdg_pred_lon);
5991 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5992 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5993 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5994 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5995 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5996 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5997 }
5998 }
5999
6000 // draw course over ground if they are longer than the ship
6001 wxPoint lShipMidPoint;
6002 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6003 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6004 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6005 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6006
6007 if (lpp >= img_height / 2) {
6008 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6009 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6010 !std::isnan(gSog)) {
6011 // COG Predictor
6012 float dash_length = ref_dim;
6013 wxDash dash_long[2];
6014 dash_long[0] =
6015 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6016 g_cog_predictor_width); // Long dash , in mm <---------+
6017 dash_long[1] = dash_long[0] / 2.0; // Short gap
6018
6019 // On ultra-hi-res displays, do not allow the dashes to be greater than
6020 // 250, since it is defined as (char)
6021 if (dash_length > 250.) {
6022 dash_long[0] = 250. / g_cog_predictor_width;
6023 dash_long[1] = dash_long[0] / 2;
6024 }
6025
6026 wxPen ppPen2(cPred, g_cog_predictor_width,
6027 (wxPenStyle)g_cog_predictor_style);
6028 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6029 ppPen2.SetDashes(2, dash_long);
6030 dc.SetPen(ppPen2);
6031 dc.StrokeLine(
6032 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6033 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6034
6035 if (g_cog_predictor_width > 1) {
6036 float line_width = g_cog_predictor_width / 3.;
6037
6038 wxDash dash_long3[2];
6039 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6040 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6041
6042 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6043 (wxPenStyle)g_cog_predictor_style);
6044 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6045 ppPen3.SetDashes(2, dash_long3);
6046 dc.SetPen(ppPen3);
6047 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6048 lGPSPoint.m_y + GPSOffsetPixels.y,
6049 lPredPoint.x + GPSOffsetPixels.x,
6050 lPredPoint.y + GPSOffsetPixels.y);
6051 }
6052
6053 if (g_cog_predictor_endmarker) {
6054 // Prepare COG predictor endpoint icon
6055 double png_pred_icon_scale_factor = .4;
6056 if (g_ShipScaleFactorExp > 1.0)
6057 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6058 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6059
6060 wxPoint icon[4];
6061
6062 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6063 (float)(lPredPoint.x - lShipMidPoint.x));
6064 cog_rad += (float)PI;
6065
6066 for (int i = 0; i < 4; i++) {
6067 int j = i * 2;
6068 double pxa = (double)(s_png_pred_icon[j]);
6069 double pya = (double)(s_png_pred_icon[j + 1]);
6070
6071 pya *= png_pred_icon_scale_factor;
6072 pxa *= png_pred_icon_scale_factor;
6073
6074 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6075 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6076
6077 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6078 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6079 }
6080
6081 // Render COG endpoint icon
6082 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6083 wxPENSTYLE_SOLID);
6084 dc.SetPen(ppPen1);
6085 dc.SetBrush(wxBrush(cPred));
6086
6087 dc.StrokePolygon(4, icon);
6088 }
6089 }
6090 }
6091
6092 // HDT Predictor
6093 if (b_render_hdt) {
6094 float hdt_dash_length = ref_dim * 0.4;
6095
6096 cPred.Set(g_ownship_HDTpredictor_color);
6097 if (cPred == wxNullColour) cPred = PredColor();
6098 float hdt_width =
6099 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6100 : g_cog_predictor_width * 0.8);
6101 wxDash dash_short[2];
6102 dash_short[0] =
6103 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6104 hdt_width); // Short dash , in mm <---------+
6105 dash_short[1] =
6106 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6107 hdt_width); // Short gap |
6108
6109 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6110 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6111 ppPen2.SetDashes(2, dash_short);
6112
6113 dc.SetPen(ppPen2);
6114 dc.StrokeLine(
6115 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6116 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6117
6118 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6119 dc.SetPen(ppPen1);
6120 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6121
6122 if (g_ownship_HDTpredictor_endmarker) {
6123 double nominal_circle_size_pixels = wxMax(
6124 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6125
6126 // Scale the circle to ChartScaleFactor, slightly softened....
6127 if (g_ShipScaleFactorExp > 1.0)
6128 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6129
6130 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6131 lHeadPoint.y + GPSOffsetPixels.y,
6132 nominal_circle_size_pixels / 2);
6133 }
6134 }
6135
6136 // Draw radar rings if activated
6137 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6138 double factor = 1.00;
6139 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6140 factor = 1 / 1.852;
6141 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6142 if (std::isnan(gSog))
6143 factor = 0.0;
6144 else
6145 factor = gSog / 60;
6146 }
6147 factor *= g_fNavAidRadarRingsStep;
6148
6149 double tlat, tlon;
6150 wxPoint r;
6151 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6152 GetCanvasPointPix(tlat, tlon, &r);
6153
6154 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6155 pow((double)(lGPSPoint.m_y - r.y), 2));
6156 int pix_radius = (int)lpp;
6157
6158 wxColor rangeringcolour =
6159 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6160
6161 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6162
6163 dc.SetPen(ppPen1);
6164 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6165
6166 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6167 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6168 }
6169}
6170
6171#if ocpnUSE_GL
6172void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6173bool ChartCanvas::CanAccelerateGlPanning() {
6174 return GetglCanvas()->CanAcceleratePanning();
6175}
6176void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6177
6178#else
6179void ChartCanvas::ResetGlGridFont() {}
6180bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6181void ChartCanvas::SetupGlCompression() {}
6182#endif
6183
6184void ChartCanvas::ComputeShipScaleFactor(
6185 float icon_hdt, int ownShipWidth, int ownShipLength,
6186 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6187 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6188 float screenResolution = m_pix_per_mm;
6189
6190 // Calculate the true ship length in exact pixels
6191 double ship_bow_lat, ship_bow_lon;
6192 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6193 &ship_bow_lat, &ship_bow_lon);
6194 wxPoint lShipBowPoint;
6195 wxPoint2DDouble b_point =
6196 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6197 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6198
6199 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6200 powf((float)(b_point.m_y - a_point.m_y), 2));
6201
6202 // And in mm
6203 float shipLength_mm = shipLength_px / screenResolution;
6204
6205 // Set minimum ownship drawing size
6206 float ownship_min_mm = g_n_ownship_min_mm;
6207 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6208
6209 // Calculate Nautical Miles distance from midships to gps antenna
6210 float hdt_ant = icon_hdt + 180.;
6211 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6212 float dx = g_n_gps_antenna_offset_x / 1852.;
6213 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6214 {
6215 hdt_ant = icon_hdt;
6216 dy = -dy;
6217 }
6218
6219 // If the drawn ship size is going to be clamped, adjust the gps antenna
6220 // offsets
6221 if (shipLength_mm < ownship_min_mm) {
6222 dy /= shipLength_mm / ownship_min_mm;
6223 dx /= shipLength_mm / ownship_min_mm;
6224 }
6225
6226 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6227
6228 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6229 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6230 &ship_mid_lon1);
6231
6232 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6233 &lShipMidPoint);
6234
6235 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6236 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6237
6238 float scale_factor = shipLength_px / ownShipLength;
6239
6240 // Calculate a scale factor that would produce a reasonably sized icon
6241 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6242
6243 // And choose the correct one
6244 scale_factor = wxMax(scale_factor, scale_factor_min);
6245
6246 scale_factor_y = scale_factor;
6247 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6248 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6249}
6250
6251void ChartCanvas::ShipDraw(ocpnDC &dc) {
6252 if (!GetVP().IsValid()) return;
6253
6254 wxPoint GPSOffsetPixels(0, 0);
6255 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6256
6257 // COG/SOG may be undefined in NMEA data stream
6258 float pCog = std::isnan(gCog) ? 0 : gCog;
6259 float pSog = std::isnan(gSog) ? 0 : gSog;
6260
6261 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6262
6263 lShipMidPoint = lGPSPoint;
6264
6265 // Draw the icon rotated to the COG
6266 // or to the Hdt if available
6267 float icon_hdt = pCog;
6268 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6269
6270 // COG may be undefined in NMEA data stream
6271 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6272
6273 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6274 // predictor
6275 double osd_head_lat, osd_head_lon;
6276 wxPoint osd_head_point;
6277
6278 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6279 &osd_head_lon);
6280
6281 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6282
6283 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6284 (float)(osd_head_point.x - lShipMidPoint.m_x));
6285 icon_rad += (float)PI;
6286
6287 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6288
6289 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6290 // nominal size and is just barely outside the viewport ....
6291 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6292
6293 // TODO: fix to include actual size of boat that will be rendered
6294 int img_height = 0;
6295 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6296 if (GetVP().chart_scale >
6297 300000) // According to S52, this should be 50,000
6298 {
6299 ShipDrawLargeScale(dc, lShipMidPoint);
6300 img_height = 20;
6301 } else {
6302 wxImage pos_image;
6303
6304 // Substitute user ownship image if found
6305 if (m_pos_image_user)
6306 pos_image = m_pos_image_user->Copy();
6307 else if (SHIP_NORMAL == m_ownship_state)
6308 pos_image = m_pos_image_red->Copy();
6309 if (SHIP_LOWACCURACY == m_ownship_state)
6310 pos_image = m_pos_image_yellow->Copy();
6311 else if (SHIP_NORMAL != m_ownship_state)
6312 pos_image = m_pos_image_grey->Copy();
6313
6314 // Substitute user ownship image if found
6315 if (m_pos_image_user) {
6316 pos_image = m_pos_image_user->Copy();
6317
6318 if (SHIP_LOWACCURACY == m_ownship_state)
6319 pos_image = m_pos_image_user_yellow->Copy();
6320 else if (SHIP_NORMAL != m_ownship_state)
6321 pos_image = m_pos_image_user_grey->Copy();
6322 }
6323
6324 img_height = pos_image.GetHeight();
6325
6326 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6327 g_OwnShipIconType > 0) // use large ship
6328 {
6329 int ownShipWidth = 22; // Default values from s_ownship_icon
6330 int ownShipLength = 84;
6331 if (g_OwnShipIconType == 1) {
6332 ownShipWidth = pos_image.GetWidth();
6333 ownShipLength = pos_image.GetHeight();
6334 }
6335
6336 float scale_factor_x, scale_factor_y;
6337 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6338 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6339 scale_factor_x, scale_factor_y);
6340
6341 if (g_OwnShipIconType == 1) { // Scaled bitmap
6342 pos_image.Rescale(ownShipWidth * scale_factor_x,
6343 ownShipLength * scale_factor_y,
6344 wxIMAGE_QUALITY_HIGH);
6345 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6346 wxImage rot_image =
6347 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6348
6349 // Simple sharpening algorithm.....
6350 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6351 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6352 if (rot_image.GetAlpha(ip, jp) > 64)
6353 rot_image.SetAlpha(ip, jp, 255);
6354
6355 wxBitmap os_bm(rot_image);
6356
6357 int w = os_bm.GetWidth();
6358 int h = os_bm.GetHeight();
6359 img_height = h;
6360
6361 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6362 lShipMidPoint.m_y - h / 2, true);
6363
6364 // Maintain dirty box,, missing in __WXMSW__ library
6365 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6366 lShipMidPoint.m_y - h / 2);
6367 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6368 lShipMidPoint.m_y - h / 2 + h);
6369 }
6370
6371 else if (g_OwnShipIconType == 2) { // Scaled Vector
6372 wxPoint ownship_icon[10];
6373
6374 for (int i = 0; i < 10; i++) {
6375 int j = i * 2;
6376 float pxa = (float)(s_ownship_icon[j]);
6377 float pya = (float)(s_ownship_icon[j + 1]);
6378 pya *= scale_factor_y;
6379 pxa *= scale_factor_x;
6380
6381 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6382 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6383
6384 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6385 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6386 }
6387
6388 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6389 dc.SetPen(ppPen1);
6390 dc.SetBrush(wxBrush(ShipColor()));
6391
6392 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6393
6394 // draw reference point (midships) cross
6395 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6396 ownship_icon[7].y);
6397 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6398 ownship_icon[9].y);
6399 }
6400
6401 img_height = ownShipLength * scale_factor_y;
6402
6403 // Reference point, where the GPS antenna is
6404 int circle_rad = 3;
6405 if (m_pos_image_user) circle_rad = 1;
6406
6407 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6408 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6409 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6410 } else { // Fixed bitmap icon.
6411 /* non opengl, or suboptimal opengl via ocpndc: */
6412 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6413 wxImage rot_image =
6414 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6415
6416 // Simple sharpening algorithm.....
6417 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6418 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6419 if (rot_image.GetAlpha(ip, jp) > 64)
6420 rot_image.SetAlpha(ip, jp, 255);
6421
6422 wxBitmap os_bm(rot_image);
6423
6424 if (g_ShipScaleFactorExp > 1) {
6425 wxImage scaled_image = os_bm.ConvertToImage();
6426 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6427 1.0; // soften the scale factor a bit
6428 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6429 scaled_image.GetHeight() * factor,
6430 wxIMAGE_QUALITY_HIGH));
6431 }
6432 int w = os_bm.GetWidth();
6433 int h = os_bm.GetHeight();
6434 img_height = h;
6435
6436 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6437 lShipMidPoint.m_y - h / 2, true);
6438
6439 // Reference point, where the GPS antenna is
6440 int circle_rad = 3;
6441 if (m_pos_image_user) circle_rad = 1;
6442
6443 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6444 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6445 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6446
6447 // Maintain dirty box,, missing in __WXMSW__ library
6448 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6449 lShipMidPoint.m_y - h / 2);
6450 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6451 lShipMidPoint.m_y - h / 2 + h);
6452 }
6453 } // ownship draw
6454 }
6455
6456 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6457}
6458
6459/* @ChartCanvas::CalcGridSpacing
6460 **
6461 ** Calculate the major and minor spacing between the lat/lon grid
6462 **
6463 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6464 *window
6465 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6466 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6467 ** @return [void]
6468 */
6469void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6470 float &MinorSpacing) {
6471 // table for calculating the distance between the grids
6472 // [0] view_scale ppm
6473 // [1] spacing between major grid lines in degrees
6474 // [2] spacing between minor grid lines in degrees
6475 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6476 {.000001f, 45.0f, 15.0f},
6477 {.0002f, 30.0f, 10.0f},
6478 {.0003f, 10.0f, 2.0f},
6479 {.0008f, 5.0f, 1.0f},
6480 {.001f, 2.0f, 30.0f / 60.0f},
6481 {.003f, 1.0f, 20.0f / 60.0f},
6482 {.006f, 0.5f, 10.0f / 60.0f},
6483 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6484 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6485 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6486 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6487 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6488 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6489 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6490 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6491
6492 unsigned int tabi;
6493 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6494 if (view_scale_ppm < lltab[tabi][0]) break;
6495 MajorSpacing = lltab[tabi][1]; // major latitude distance
6496 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6497 return;
6498}
6499/* @ChartCanvas::CalcGridText *************************************
6500 **
6501 ** Calculates text to display at the major grid lines
6502 **
6503 ** @param [r] latlon [float] latitude or longitude of grid line
6504 ** @param [r] spacing [float] distance between two major grid lines
6505 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6506 **
6507 ** @return
6508 */
6509
6510wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6511 int deg = (int)fabs(latlon); // degrees
6512 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6513 char postfix;
6514
6515 // calculate postfix letter (NSEW)
6516 if (latlon > 0.0) {
6517 if (bPostfix) {
6518 postfix = 'N';
6519 } else {
6520 postfix = 'E';
6521 }
6522 } else if (latlon < 0.0) {
6523 if (bPostfix) {
6524 postfix = 'S';
6525 } else {
6526 postfix = 'W';
6527 }
6528 } else {
6529 postfix = ' '; // no postfix for equator and greenwich
6530 }
6531 // calculate text, display minutes only if spacing is smaller than one degree
6532
6533 wxString ret;
6534 if (spacing >= 1.0) {
6535 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6536 } else if (spacing >= (1.0 / 60.0)) {
6537 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6538 } else {
6539 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6540 }
6541
6542 return ret;
6543}
6544
6545/* @ChartCanvas::GridDraw *****************************************
6546 **
6547 ** Draws major and minor Lat/Lon Grid on the chart
6548 ** - distance between Grid-lm ines are calculated automatic
6549 ** - major grid lines will be across the whole chart window
6550 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6551 **
6552 ** @param [w] dc [wxDC&] the wx drawing context
6553 **
6554 ** @return [void]
6555 ************************************************************************/
6556void ChartCanvas::GridDraw(ocpnDC &dc) {
6557 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6558
6559 double nlat, elon, slat, wlon;
6560 float lat, lon;
6561 float dlon;
6562 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6563 wxCoord w, h;
6564 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6565 dc.SetPen(GridPen);
6566 if (!m_pgridFont) SetupGridFont();
6567 dc.SetFont(*m_pgridFont);
6568 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6569
6570 w = m_canvas_width;
6571 h = m_canvas_height;
6572
6573 GetCanvasPixPoint(0, 0, nlat,
6574 wlon); // get lat/lon of upper left point of the window
6575 GetCanvasPixPoint(w, h, slat,
6576 elon); // get lat/lon of lower right point of the window
6577 dlon =
6578 elon -
6579 wlon; // calculate how many degrees of longitude are shown in the window
6580 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6581 {
6582 dlon = dlon + 360.0;
6583 }
6584 // calculate distance between latitude grid lines
6585 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6586
6587 // calculate position of first major latitude grid line
6588 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6589
6590 // Draw Major latitude grid lines and text
6591 while (lat < nlat) {
6592 wxPoint r;
6593 wxString st =
6594 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6595 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6596 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6597 dc.DrawText(st, 0, r.y); // draw text
6598 lat = lat + gridlatMajor;
6599
6600 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6601 }
6602
6603 // calculate position of first minor latitude grid line
6604 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6605
6606 // Draw minor latitude grid lines
6607 while (lat < nlat) {
6608 wxPoint r;
6609 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6610 dc.DrawLine(0, r.y, 10, r.y, false);
6611 dc.DrawLine(w - 10, r.y, w, r.y, false);
6612 lat = lat + gridlatMinor;
6613 }
6614
6615 // calculate distance between grid lines
6616 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6617
6618 // calculate position of first major latitude grid line
6619 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6620
6621 // draw major longitude grid lines
6622 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6623 wxPoint r;
6624 wxString st = CalcGridText(lon, gridlonMajor, false);
6625 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6626 dc.DrawLine(r.x, 0, r.x, h, false);
6627 dc.DrawText(st, r.x, 0);
6628 lon = lon + gridlonMajor;
6629 if (lon > 180.0) {
6630 lon = lon - 360.0;
6631 }
6632
6633 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6634 }
6635
6636 // calculate position of first minor longitude grid line
6637 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6638 // draw minor longitude grid lines
6639 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6640 wxPoint r;
6641 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6642 dc.DrawLine(r.x, 0, r.x, 10, false);
6643 dc.DrawLine(r.x, h - 10, r.x, h, false);
6644 lon = lon + gridlonMinor;
6645 if (lon > 180.0) {
6646 lon = lon - 360.0;
6647 }
6648 }
6649}
6650
6651void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6652 if (0 ) {
6653 double blat, blon, tlat, tlon;
6654 wxPoint r;
6655
6656 int x_origin = m_bDisplayGrid ? 60 : 20;
6657 int y_origin = m_canvas_height - 50;
6658
6659 float dist;
6660 int count;
6661 wxPen pen1, pen2;
6662
6663 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6664 {
6665 dist = 10.0;
6666 count = 5;
6667 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6668 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6669 } else // Draw 1 mile scale as SCALEB10
6670 {
6671 dist = 1.0;
6672 count = 10;
6673 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6674 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6675 }
6676
6677 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6678 double rotation = -VPoint.rotation;
6679
6680 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6681 GetCanvasPointPix(tlat, tlon, &r);
6682 int l1 = (y_origin - r.y) / count;
6683
6684 for (int i = 0; i < count; i++) {
6685 int y = l1 * i;
6686 if (i & 1)
6687 dc.SetPen(pen1);
6688 else
6689 dc.SetPen(pen2);
6690
6691 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6692 }
6693 } else {
6694 double blat, blon, tlat, tlon;
6695
6696 int x_origin = 5.0 * GetPixPerMM();
6697 int chartbar_height = GetChartbarHeight();
6698 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6699 // if (style->chartStatusWindowTransparent)
6700 // chartbar_height = 0;
6701 int y_origin = m_canvas_height - chartbar_height - 5;
6702#ifdef __WXOSX__
6703 if (!g_bopengl)
6704 y_origin =
6705 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6706#endif
6707
6708 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6709 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6710
6711 double d;
6712 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6713 d /= 2;
6714
6715 int unit = g_iDistanceFormat;
6716 if (d < .5 &&
6717 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6718 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6719
6720 // nice number
6721 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6722 float places = floor(logdist), rem = logdist - places;
6723 dist = pow(10, places);
6724
6725 if (rem < .2)
6726 dist /= 5;
6727 else if (rem < .5)
6728 dist /= 2;
6729
6730 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6731 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6732 double rotation = -VPoint.rotation;
6733
6734 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6735 &tlat, &tlon);
6736 wxPoint r;
6737 GetCanvasPointPix(tlat, tlon, &r);
6738 int l1 = r.x - x_origin;
6739
6740 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6741 12); // Store this for later reference
6742
6743 dc.SetPen(pen1);
6744
6745 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6746 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6747 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6748
6749 if (!m_pgridFont) SetupGridFont();
6750 dc.SetFont(*m_pgridFont);
6751 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6752 int w, h;
6753 dc.GetTextExtent(s, &w, &h);
6754 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6755 if (g_bopengl) {
6756 w /= dpi_factor;
6757 h /= dpi_factor;
6758 }
6759 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6760 }
6761}
6762
6763void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6764 // Constants?
6765 double da_min = 2.;
6766 double da_max = 6.;
6767 double ra_min = 0.;
6768 double ra_max = 40.;
6769
6770 wxPen pen_save = dc.GetPen();
6771
6772 wxDateTime now = wxDateTime::Now();
6773
6774 dc.SetPen(pen);
6775
6776 int x0, y0, x1, y1;
6777
6778 x0 = x1 = x + radius; // Start point
6779 y0 = y1 = y;
6780 double angle = 0.;
6781 int i = 0;
6782
6783 while (angle < 360.) {
6784 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6785 angle += da;
6786
6787 if (angle > 360.) angle = 360.;
6788
6789 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6790
6791 double r;
6792 if (i & 1)
6793 r = radius + ra;
6794 else
6795 r = radius - ra;
6796
6797 x1 = (int)(x + cos(angle * PI / 180.) * r);
6798 y1 = (int)(y + sin(angle * PI / 180.) * r);
6799
6800 dc.DrawLine(x0, y0, x1, y1);
6801
6802 x0 = x1;
6803 y0 = y1;
6804
6805 i++;
6806 }
6807
6808 dc.DrawLine(x + radius, y, x1, y1); // closure
6809
6810 dc.SetPen(pen_save);
6811}
6812
6813static bool bAnchorSoundPlaying = false;
6814
6815static void onAnchorSoundFinished(void *ptr) {
6816 o_sound::g_anchorwatch_sound->UnLoad();
6817 bAnchorSoundPlaying = false;
6818}
6819
6820void ChartCanvas::AlertDraw(ocpnDC &dc) {
6821 using namespace o_sound;
6822 // Visual and audio alert for anchorwatch goes here
6823 bool play_sound = false;
6824 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6825 if (AnchorAlertOn1) {
6826 wxPoint TargetPoint;
6828 &TargetPoint);
6829 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6830 TargetPoint.y, 100);
6831 play_sound = true;
6832 }
6833 } else
6834 AnchorAlertOn1 = false;
6835
6836 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6837 if (AnchorAlertOn2) {
6838 wxPoint TargetPoint;
6840 &TargetPoint);
6841 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6842 TargetPoint.y, 100);
6843 play_sound = true;
6844 }
6845 } else
6846 AnchorAlertOn2 = false;
6847
6848 if (play_sound) {
6849 if (!bAnchorSoundPlaying) {
6850 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6851 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6852 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6853 if (g_anchorwatch_sound->IsOk()) {
6854 bAnchorSoundPlaying = true;
6855 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6856 g_anchorwatch_sound->Play();
6857 }
6858 }
6859 }
6860}
6861
6862void ChartCanvas::UpdateShips() {
6863 // Get the rectangle in the current dc which bounds the "ownship" symbol
6864
6865 wxClientDC dc(this);
6866 if (!dc.IsOk()) return;
6867
6868 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6869 if (!test_bitmap.IsOk()) return;
6870
6871 wxMemoryDC temp_dc(test_bitmap);
6872
6873 temp_dc.ResetBoundingBox();
6874 temp_dc.DestroyClippingRegion();
6875 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6876
6877 // Draw the ownship on the temp_dc
6878 ocpnDC ocpndc = ocpnDC(temp_dc);
6879 ShipDraw(ocpndc);
6880
6881 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6882 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6883 if (p) {
6884 wxPoint px;
6885 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6886 ocpndc.CalcBoundingBox(px.x, px.y);
6887 }
6888 }
6889
6890 ship_draw_rect =
6891 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6892 temp_dc.MaxY() - temp_dc.MinY());
6893
6894 wxRect own_ship_update_rect = ship_draw_rect;
6895
6896 if (!own_ship_update_rect.IsEmpty()) {
6897 // The required invalidate rectangle is the union of the last drawn
6898 // rectangle and this drawn rectangle
6899 own_ship_update_rect.Union(ship_draw_last_rect);
6900 own_ship_update_rect.Inflate(2);
6901 }
6902
6903 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6904
6905 ship_draw_last_rect = ship_draw_rect;
6906
6907 temp_dc.SelectObject(wxNullBitmap);
6908}
6909
6910void ChartCanvas::UpdateAlerts() {
6911 // Get the rectangle in the current dc which bounds the detected Alert
6912 // targets
6913
6914 // Use this dc
6915 wxClientDC dc(this);
6916
6917 // Get dc boundary
6918 int sx, sy;
6919 dc.GetSize(&sx, &sy);
6920
6921 // Need a bitmap
6922 wxBitmap test_bitmap(sx, sy, -1);
6923
6924 // Create a memory DC
6925 wxMemoryDC temp_dc;
6926 temp_dc.SelectObject(test_bitmap);
6927
6928 temp_dc.ResetBoundingBox();
6929 temp_dc.DestroyClippingRegion();
6930 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6931
6932 // Draw the Alert Targets on the temp_dc
6933 ocpnDC ocpndc = ocpnDC(temp_dc);
6934 AlertDraw(ocpndc);
6935
6936 // Retrieve the drawing extents
6937 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6938 temp_dc.MaxX() - temp_dc.MinX(),
6939 temp_dc.MaxY() - temp_dc.MinY());
6940
6941 if (!alert_rect.IsEmpty())
6942 alert_rect.Inflate(2); // clear all drawing artifacts
6943
6944 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6945 // The required invalidate rectangle is the union of the last drawn
6946 // rectangle and this drawn rectangle
6947 wxRect alert_update_rect = alert_draw_rect;
6948 alert_update_rect.Union(alert_rect);
6949
6950 // Invalidate the rectangular region
6951 RefreshRect(alert_update_rect, false);
6952 }
6953
6954 // Save this rectangle for next time
6955 alert_draw_rect = alert_rect;
6956
6957 temp_dc.SelectObject(wxNullBitmap); // clean up
6958}
6959
6960void ChartCanvas::UpdateAIS() {
6961 if (!g_pAIS) return;
6962
6963 // Get the rectangle in the current dc which bounds the detected AIS targets
6964
6965 // Use this dc
6966 wxClientDC dc(this);
6967
6968 // Get dc boundary
6969 int sx, sy;
6970 dc.GetSize(&sx, &sy);
6971
6972 wxRect ais_rect;
6973
6974 // How many targets are there?
6975
6976 // If more than "some number", it will be cheaper to refresh the entire
6977 // screen than to build update rectangles for each target.
6978 if (g_pAIS->GetTargetList().size() > 10) {
6979 ais_rect = wxRect(0, 0, sx, sy); // full screen
6980 } else {
6981 // Need a bitmap
6982 wxBitmap test_bitmap(sx, sy, -1);
6983
6984 // Create a memory DC
6985 wxMemoryDC temp_dc;
6986 temp_dc.SelectObject(test_bitmap);
6987
6988 temp_dc.ResetBoundingBox();
6989 temp_dc.DestroyClippingRegion();
6990 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6991
6992 // Draw the AIS Targets on the temp_dc
6993 ocpnDC ocpndc = ocpnDC(temp_dc);
6994 AISDraw(ocpndc, GetVP(), this);
6995 AISDrawAreaNotices(ocpndc, GetVP(), this);
6996
6997 // Retrieve the drawing extents
6998 ais_rect =
6999 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7000 temp_dc.MaxY() - temp_dc.MinY());
7001
7002 if (!ais_rect.IsEmpty())
7003 ais_rect.Inflate(2); // clear all drawing artifacts
7004
7005 temp_dc.SelectObject(wxNullBitmap); // clean up
7006 }
7007
7008 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7009 // The required invalidate rectangle is the union of the last drawn
7010 // rectangle and this drawn rectangle
7011 wxRect ais_update_rect = ais_draw_rect;
7012 ais_update_rect.Union(ais_rect);
7013
7014 // Invalidate the rectangular region
7015 RefreshRect(ais_update_rect, false);
7016 }
7017
7018 // Save this rectangle for next time
7019 ais_draw_rect = ais_rect;
7020}
7021
7022void ChartCanvas::ToggleCPAWarn() {
7023 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7024 wxString mess;
7025 if (g_bCPAWarn) {
7026 g_bTCPA_Max = true;
7027 mess = _("ON");
7028 } else {
7029 g_bTCPA_Max = false;
7030 mess = _("OFF");
7031 }
7032 // Print to status bar if available.
7033 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7034 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7035 } else {
7036 if (!g_AisFirstTimeUse) {
7037 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7038 _("CPA") + " " + mess, 4, 4);
7039 }
7040 }
7041}
7042
7043void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7044
7045void ChartCanvas::OnSize(wxSizeEvent &event) {
7046 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7047 // GetClientSize returns the size of the canvas area in logical pixels.
7048 GetClientSize(&m_canvas_width, &m_canvas_height);
7049
7050#ifdef __WXOSX__
7051 // Support scaled HDPI displays.
7052 m_displayScale = GetContentScaleFactor();
7053#endif
7054
7055 // Convert to physical pixels.
7056 m_canvas_width *= m_displayScale;
7057 m_canvas_height *= m_displayScale;
7058
7059 // Resize the current viewport
7060 VPoint.pix_width = m_canvas_width;
7061 VPoint.pix_height = m_canvas_height;
7062 VPoint.SetPixelScale(m_displayScale);
7063
7064 // Get some canvas metrics
7065
7066 // Rescale to current value, in order to rebuild VPoint data
7067 // structures for new canvas size
7069
7070 m_absolute_min_scale_ppm =
7071 m_canvas_width /
7072 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7073
7074 // Inform the parent Frame that I am being resized...
7075 top_frame::Get()->ProcessCanvasResize();
7076
7077 // if MUIBar is active, size the bar
7078 // if(g_useMUI && !m_muiBar){ // rebuild if
7079 // necessary
7080 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7081 // m_muiBarHOSize = m_muiBar->GetSize();
7082 // }
7083
7084 if (m_muiBar) {
7085 SetMUIBarPosition();
7086 UpdateFollowButtonState();
7087 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7088 }
7089
7090 // Set up the scroll margins
7091 xr_margin = m_canvas_width * 95 / 100;
7092 xl_margin = m_canvas_width * 5 / 100;
7093 yt_margin = m_canvas_height * 5 / 100;
7094 yb_margin = m_canvas_height * 95 / 100;
7095
7096 if (m_pQuilt)
7097 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7098
7099 // Resize the scratch BM
7100 delete pscratch_bm;
7101 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7102 m_brepaint_piano = true;
7103
7104 // Resize the Route Calculation BM
7105 m_dc_route.SelectObject(wxNullBitmap);
7106 delete proute_bm;
7107 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7108 m_dc_route.SelectObject(*proute_bm);
7109
7110 // Resize the saved Bitmap
7111 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7112
7113 // Resize the working Bitmap
7114 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7115
7116 // Rescale again, to capture all the changes for new canvas size
7118
7119#ifdef ocpnUSE_GL
7120 if (/*g_bopengl &&*/ m_glcc) {
7121 // FIXME (dave) This can go away?
7122 m_glcc->OnSize(event);
7123 }
7124#endif
7125
7126 FormatPianoKeys();
7127 // Invalidate the whole window
7128 ReloadVP();
7129}
7130
7131void ChartCanvas::ProcessNewGUIScale() {
7132 // m_muiBar->Hide();
7133 delete m_muiBar;
7134 m_muiBar = 0;
7135
7136 CreateMUIBar();
7137}
7138
7139void ChartCanvas::CreateMUIBar() {
7140 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7141 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7142 m_muiBar->SetColorScheme(m_cs);
7143 m_muiBarHOSize = m_muiBar->m_size;
7144 }
7145
7146 if (m_muiBar) {
7147 // We need to update the m_bENCGroup flag, not least for the initial
7148 // creation of a MUIBar
7149 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7150
7151 SetMUIBarPosition();
7152 UpdateFollowButtonState();
7153 m_muiBar->UpdateDynamicValues();
7154 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7155 }
7156}
7157
7158void ChartCanvas::SetMUIBarPosition() {
7159 // if MUIBar is active, size the bar
7160 if (m_muiBar) {
7161 // We estimate the piano width based on the canvas width
7162 int pianoWidth = GetClientSize().x * 0.6f;
7163 // If the piano already exists, we can use its exact width
7164 // if(m_Piano)
7165 // pianoWidth = m_Piano->GetWidth();
7166
7167 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7168 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7169 delete m_muiBar;
7170 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7171 m_muiBar->SetColorScheme(m_cs);
7172 }
7173 }
7174
7175 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7176 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7177 delete m_muiBar;
7178 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7179 m_muiBar->SetColorScheme(m_cs);
7180 }
7181 }
7182
7183 m_muiBar->SetBestPosition();
7184 }
7185}
7186
7187void ChartCanvas::DestroyMuiBar() {
7188 if (m_muiBar) {
7189 delete m_muiBar;
7190 m_muiBar = NULL;
7191 }
7192}
7193
7194void ChartCanvas::ShowCompositeInfoWindow(
7195 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7196 if (n_charts > 0) {
7197 if (NULL == m_pCIWin) {
7198 m_pCIWin = new ChInfoWin(this);
7199 m_pCIWin->Hide();
7200 }
7201
7202 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7203 wxString s;
7204
7205 s = _("Composite of ");
7206
7207 wxString s1;
7208 s1.Printf("%d ", n_charts);
7209 if (n_charts > 1)
7210 s1 += _("charts");
7211 else
7212 s1 += _("chart");
7213 s += s1;
7214 s += '\n';
7215
7216 s1.Printf(_("Chart scale"));
7217 s1 += ": ";
7218 wxString s2;
7219 s2.Printf("1:%d\n", scale);
7220 s += s1;
7221 s += s2;
7222
7223 s1 = _("Zoom in for more information");
7224 s += s1;
7225 s += '\n';
7226
7227 int char_width = s1.Length();
7228 int char_height = 3;
7229
7230 if (g_bChartBarEx) {
7231 s += '\n';
7232 int j = 0;
7233 for (int i : index_vector) {
7234 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7235 wxString path = cte.GetFullSystemPath();
7236 s += path;
7237 s += '\n';
7238 char_height++;
7239 char_width = wxMax(char_width, path.Length());
7240 if (j++ >= 9) break;
7241 }
7242 if (j >= 9) {
7243 s += " .\n .\n .\n";
7244 char_height += 3;
7245 }
7246 s += '\n';
7247 char_height += 1;
7248
7249 char_width += 4; // Fluff
7250 }
7251
7252 m_pCIWin->SetString(s);
7253
7254 m_pCIWin->FitToChars(char_width, char_height);
7255
7256 wxPoint p;
7257 p.x = x / GetContentScaleFactor();
7258 if ((p.x + m_pCIWin->GetWinSize().x) >
7259 (m_canvas_width / GetContentScaleFactor()))
7260 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7261 m_pCIWin->GetWinSize().x) /
7262 2; // centered
7263
7264 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7265 4 - m_pCIWin->GetWinSize().y;
7266
7267 m_pCIWin->dbIndex = 0;
7268 m_pCIWin->chart_scale = 0;
7269 m_pCIWin->SetPosition(p);
7270 m_pCIWin->SetBitmap();
7271 m_pCIWin->Refresh();
7272 m_pCIWin->Show();
7273 }
7274 } else {
7275 HideChartInfoWindow();
7276 }
7277}
7278
7279void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7280 if (dbIndex >= 0) {
7281 if (NULL == m_pCIWin) {
7282 m_pCIWin = new ChInfoWin(this);
7283 m_pCIWin->Hide();
7284 }
7285
7286 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7287 wxString s;
7288 ChartBase *pc = NULL;
7289
7290 // TOCTOU race but worst case will reload chart.
7291 // need to lock it or the background spooler may evict charts in
7292 // OpenChartFromDBAndLock
7293 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7294 pc = ChartData->OpenChartFromDBAndLock(
7295 dbIndex, FULL_INIT); // this must come from cache
7296
7297 int char_width, char_height;
7298 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7299 if (pc) ChartData->UnLockCacheChart(dbIndex);
7300
7301 m_pCIWin->SetString(s);
7302 m_pCIWin->FitToChars(char_width, char_height);
7303
7304 wxPoint p;
7305 p.x = x / GetContentScaleFactor();
7306 if ((p.x + m_pCIWin->GetWinSize().x) >
7307 (m_canvas_width / GetContentScaleFactor()))
7308 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7309 m_pCIWin->GetWinSize().x) /
7310 2; // centered
7311
7312 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7313 4 - m_pCIWin->GetWinSize().y;
7314
7315 m_pCIWin->dbIndex = dbIndex;
7316 m_pCIWin->SetPosition(p);
7317 m_pCIWin->SetBitmap();
7318 m_pCIWin->Refresh();
7319 m_pCIWin->Show();
7320 }
7321 } else {
7322 HideChartInfoWindow();
7323 }
7324}
7325
7326void ChartCanvas::HideChartInfoWindow() {
7327 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7328 m_pCIWin->Hide();
7329 m_pCIWin->Destroy();
7330 m_pCIWin = NULL;
7331
7332#ifdef __ANDROID__
7333 androidForceFullRepaint();
7334#endif
7335 }
7336}
7337
7338void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7339 wxMouseEvent ev(wxEVT_MOTION);
7340 ev.m_x = mouse_x;
7341 ev.m_y = mouse_y;
7342 ev.m_leftDown = mouse_leftisdown;
7343
7344 wxEvtHandler *evthp = GetEventHandler();
7345
7346 ::wxPostEvent(evthp, ev);
7347}
7348
7349void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7350 if ((m_panx_target_final - m_panx_target_now) ||
7351 (m_pany_target_final - m_pany_target_now)) {
7352 DoTimedMovementTarget();
7353 } else
7354 DoTimedMovement();
7355}
7356
7357void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7358
7359bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7360 int delta) {
7361 if (m_disable_edge_pan) return false;
7362
7363 bool bft = false;
7364 int pan_margin = m_canvas_width * margin / 100;
7365 int pan_timer_set = 200;
7366 double pan_delta = GetVP().pix_width * delta / 100;
7367 int pan_x = 0;
7368 int pan_y = 0;
7369
7370 if (x > m_canvas_width - pan_margin) {
7371 bft = true;
7372 pan_x = pan_delta;
7373 }
7374
7375 else if (x < pan_margin) {
7376 bft = true;
7377 pan_x = -pan_delta;
7378 }
7379
7380 if (y < pan_margin) {
7381 bft = true;
7382 pan_y = -pan_delta;
7383 }
7384
7385 else if (y > m_canvas_height - pan_margin) {
7386 bft = true;
7387 pan_y = pan_delta;
7388 }
7389
7390 // Of course, if dragging, and the mouse left button is not down, we must
7391 // stop the event injection
7392 if (bdragging) {
7393 if (!g_btouch) {
7394 wxMouseState state = ::wxGetMouseState();
7395#if wxCHECK_VERSION(3, 0, 0)
7396 if (!state.LeftIsDown())
7397#else
7398 if (!state.LeftDown())
7399#endif
7400 bft = false;
7401 }
7402 }
7403 if ((bft) && !pPanTimer->IsRunning()) {
7404 PanCanvas(pan_x, pan_y);
7405 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7406 return true;
7407 }
7408
7409 // This mouse event must not be due to pan timer event injector
7410 // Mouse is out of the pan zone, so prevent any orphan event injection
7411 if ((!bft) && pPanTimer->IsRunning()) {
7412 pPanTimer->Stop();
7413 }
7414
7415 return (false);
7416}
7417
7418// Look for waypoints at the current position.
7419// Used to determine what a mouse event should act on.
7420
7421void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7422 bool setBeingEdited) {
7423 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7424 m_pRoutePointEditTarget = NULL;
7425 m_pFoundPoint = NULL;
7426
7427 SelectItem *pFind = NULL;
7428 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7429 SelectableItemList SelList = pSelect->FindSelectionList(
7430 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7431 for (SelectItem *pFind : SelList) {
7432 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7433
7434 // Get an array of all routes using this point
7435 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7436 // TODO: delete m_pEditRouteArray after use?
7437
7438 // Use route array to determine actual visibility for the point
7439 bool brp_viz = false;
7440 if (m_pEditRouteArray) {
7441 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7442 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7443 if (pr->IsVisible()) {
7444 brp_viz = true;
7445 break;
7446 }
7447 }
7448 } else
7449 brp_viz = frp->IsVisible(); // isolated point
7450
7451 if (brp_viz) {
7452 // Use route array to rubberband all affected routes
7453 if (m_pEditRouteArray) // Editing Waypoint as part of route
7454 {
7455 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7456 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7457 pr->m_bIsBeingEdited = setBeingEdited;
7458 }
7459 m_bRouteEditing = setBeingEdited;
7460 } else // editing Mark
7461 {
7462 frp->m_bRPIsBeingEdited = setBeingEdited;
7463 m_bMarkEditing = setBeingEdited;
7464 }
7465
7466 m_pRoutePointEditTarget = frp;
7467 m_pFoundPoint = pFind;
7468 break; // out of the while(node)
7469 }
7470 } // for (SelectItem...
7471}
7472std::shared_ptr<HostApi121::PiPointContext>
7473ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7474 // General Right Click
7475 // Look for selectable objects
7476 double slat, slon;
7477 GetCanvasPixPoint(x, y, slat, slon);
7478
7479 SelectItem *pFindAIS;
7480 SelectItem *pFindRP;
7481 SelectItem *pFindRouteSeg;
7482 SelectItem *pFindTrackSeg;
7483 SelectItem *pFindCurrent = NULL;
7484 SelectItem *pFindTide = NULL;
7485
7486 // Get all the selectable things at the selected point
7487 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7488 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7489 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7490 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7491 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7492
7493 if (m_bShowCurrent)
7494 pFindCurrent =
7495 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7496
7497 if (m_bShowTide) // look for tide stations
7498 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7499
7500 int seltype = 0;
7501
7502 // Try for AIS targets first
7503 int FoundAIS_MMSI = 0;
7504 if (pFindAIS) {
7505 FoundAIS_MMSI = pFindAIS->GetUserData();
7506
7507 // Make sure the target data is available
7508 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7509 seltype |= SELTYPE_AISTARGET;
7510 }
7511
7512 // Now the various Route Parts
7513
7514 RoutePoint *FoundRoutePoint = NULL;
7515 Route *SelectedRoute = NULL;
7516
7517 if (pFindRP) {
7518 RoutePoint *pFirstVizPoint = NULL;
7519 RoutePoint *pFoundActiveRoutePoint = NULL;
7520 RoutePoint *pFoundVizRoutePoint = NULL;
7521 Route *pSelectedActiveRoute = NULL;
7522 Route *pSelectedVizRoute = NULL;
7523
7524 // There is at least one routepoint, so get the whole list
7525 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7526 SelectableItemList SelList =
7527 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7528 for (SelectItem *pFindSel : SelList) {
7529 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7530
7531 // Get an array of all routes using this point
7532 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7533
7534 // Use route array (if any) to determine actual visibility for this point
7535 bool brp_viz = false;
7536 if (proute_array) {
7537 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7538 Route *pr = (Route *)proute_array->Item(ir);
7539 if (pr->IsVisible()) {
7540 brp_viz = true;
7541 break;
7542 }
7543 }
7544 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7545 // but still exists as a waypoint
7546 brp_viz = prp->IsVisible(); // so treat as isolated point
7547
7548 } else
7549 brp_viz = prp->IsVisible(); // isolated point
7550
7551 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7552
7553 // Use route array to choose the appropriate route
7554 // Give preference to any active route, otherwise select the first visible
7555 // route in the array for this point
7556 if (proute_array) {
7557 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7558 Route *pr = (Route *)proute_array->Item(ir);
7559 if (pr->m_bRtIsActive) {
7560 pSelectedActiveRoute = pr;
7561 pFoundActiveRoutePoint = prp;
7562 break;
7563 }
7564 }
7565
7566 if (NULL == pSelectedVizRoute) {
7567 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7568 Route *pr = (Route *)proute_array->Item(ir);
7569 if (pr->IsVisible()) {
7570 pSelectedVizRoute = pr;
7571 pFoundVizRoutePoint = prp;
7572 break;
7573 }
7574 }
7575 }
7576
7577 delete proute_array;
7578 }
7579 }
7580
7581 // Now choose the "best" selections
7582 if (pFoundActiveRoutePoint) {
7583 FoundRoutePoint = pFoundActiveRoutePoint;
7584 SelectedRoute = pSelectedActiveRoute;
7585 } else if (pFoundVizRoutePoint) {
7586 FoundRoutePoint = pFoundVizRoutePoint;
7587 SelectedRoute = pSelectedVizRoute;
7588 } else
7589 // default is first visible point in list
7590 FoundRoutePoint = pFirstVizPoint;
7591
7592 if (SelectedRoute) {
7593 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7594 } else if (FoundRoutePoint) {
7595 seltype |= SELTYPE_MARKPOINT;
7596 }
7597
7598 // Highlight the selected point, to verify the proper right click selection
7599#if 0
7600 if (m_pFoundRoutePoint) {
7601 m_pFoundRoutePoint->m_bPtIsSelected = true;
7602 wxRect wp_rect;
7603 RoutePointGui(*m_pFoundRoutePoint)
7604 .CalculateDCRect(m_dc_route, this, &wp_rect);
7605 RefreshRect(wp_rect, true);
7606 }
7607#endif
7608 }
7609
7610 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7611 // routes But call the popup handler with identifier appropriate to the type
7612 if (pFindRouteSeg) // there is at least one select item
7613 {
7614 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7615 SelectableItemList SelList =
7616 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7617
7618 if (NULL == SelectedRoute) // the case where a segment only is selected
7619 {
7620 // Choose the first visible route containing segment in the list
7621 for (SelectItem *pFindSel : SelList) {
7622 Route *pr = (Route *)pFindSel->m_pData3;
7623 if (pr->IsVisible()) {
7624 SelectedRoute = pr;
7625 break;
7626 }
7627 }
7628 }
7629
7630 if (SelectedRoute) {
7631 if (NULL == FoundRoutePoint)
7632 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7633
7634 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7635 seltype |= SELTYPE_ROUTESEGMENT;
7636 }
7637 }
7638
7639 if (pFindTrackSeg) {
7640 m_pSelectedTrack = NULL;
7641 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7642 SelectableItemList SelList =
7643 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7644
7645 // Choose the first visible track containing segment in the list
7646 for (SelectItem *pFindSel : SelList) {
7647 Track *pt = (Track *)pFindSel->m_pData3;
7648 if (pt->IsVisible()) {
7649 m_pSelectedTrack = pt;
7650 break;
7651 }
7652 }
7653 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7654 }
7655
7656 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7657
7658 // Populate the return struct
7659 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7660 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7661 rstruct->object_ident = "";
7662
7663 if (seltype == SELTYPE_AISTARGET) {
7664 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7665 wxString val;
7666 val.Printf("%d", FoundAIS_MMSI);
7667 rstruct->object_ident = val.ToStdString();
7668 } else if (seltype & SELTYPE_MARKPOINT) {
7669 if (FoundRoutePoint) {
7670 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7671 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7672 }
7673 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7674 if (SelectedRoute) {
7675 rstruct->object_type =
7676 HostApi121::PiContextObjectType::kObjectRoutesegment;
7677 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7678 }
7679 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7680 if (m_pSelectedTrack) {
7681 rstruct->object_type =
7682 HostApi121::PiContextObjectType::kObjectTracksegment;
7683 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7684 }
7685 }
7686
7687 return rstruct;
7688}
7689
7690void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7691 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7692 singleClickEventIsValid = false;
7693 m_DoubleClickTimer->Stop();
7694}
7695
7696bool leftIsDown;
7697
7698bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7699 if (!m_bChartDragging && !m_bDrawingRoute) {
7700 /*
7701 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7702 * mouse event coordinates are in logical pixels.
7703 */
7704 if (m_Compass && m_Compass->IsShown()) {
7705 wxRect logicalRect = m_Compass->GetLogicalRect();
7706 bool isInCompass = logicalRect.Contains(event.GetPosition());
7707 if (isInCompass || m_mouseWasInCompass) {
7708 if (m_Compass->MouseEvent(event)) {
7709 cursor_region = CENTER;
7710 if (!g_btouch) SetCanvasCursor(event);
7711 m_mouseWasInCompass = isInCompass;
7712 return true;
7713 }
7714 }
7715 m_mouseWasInCompass = isInCompass;
7716 }
7717
7718 if (m_notification_button && m_notification_button->IsShown()) {
7719 wxRect logicalRect = m_notification_button->GetLogicalRect();
7720 bool isinButton = logicalRect.Contains(event.GetPosition());
7721 if (isinButton) {
7722 SetCursor(*pCursorArrow);
7723 if (event.LeftDown()) HandleNotificationMouseClick();
7724 return true;
7725 }
7726 }
7727
7728 if (MouseEventToolbar(event)) return true;
7729
7730 if (MouseEventChartBar(event)) return true;
7731
7732 if (MouseEventMUIBar(event)) return true;
7733
7734 if (MouseEventIENCBar(event)) return true;
7735 }
7736 return false;
7737}
7738
7739void ChartCanvas::HandleNotificationMouseClick() {
7740 if (!m_NotificationsList) {
7741 m_NotificationsList = new NotificationsList(this);
7742
7743 // calculate best size for Notification list
7744 m_NotificationsList->RecalculateSize();
7745 m_NotificationsList->Hide();
7746 }
7747
7748 if (m_NotificationsList->IsShown()) {
7749 m_NotificationsList->Hide();
7750 } else {
7751 m_NotificationsList->RecalculateSize();
7752 m_NotificationsList->ReloadNotificationList();
7753 m_NotificationsList->Show();
7754 }
7755}
7756bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7757 if (!g_bShowChartBar) return false;
7758
7759 if (!m_Piano->MouseEvent(event)) return false;
7760
7761 cursor_region = CENTER;
7762 if (!g_btouch) SetCanvasCursor(event);
7763 return true;
7764}
7765
7766bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7767 if (!IsPrimaryCanvas()) return false;
7768
7769 if (g_MainToolbar) {
7770 if (!g_MainToolbar->MouseEvent(event))
7771 return false;
7772 else
7773 g_MainToolbar->RefreshToolbar();
7774 }
7775
7776 cursor_region = CENTER;
7777 if (!g_btouch) SetCanvasCursor(event);
7778 return true;
7779}
7780
7781bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7782 if (!IsPrimaryCanvas()) return false;
7783
7784 if (g_iENCToolbar) {
7785 if (!g_iENCToolbar->MouseEvent(event))
7786 return false;
7787 else {
7788 g_iENCToolbar->RefreshToolbar();
7789 return true;
7790 }
7791 }
7792 return false;
7793}
7794
7795bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7796 if (m_muiBar) {
7797 if (!m_muiBar->MouseEvent(event)) return false;
7798 }
7799
7800 cursor_region = CENTER;
7801 if (!g_btouch) SetCanvasCursor(event);
7802 if (m_muiBar)
7803 return true;
7804 else
7805 return false;
7806}
7807
7808bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7809 int x, y;
7810
7811 bool bret = false;
7812
7813 event.GetPosition(&x, &y);
7814
7815 x *= m_displayScale;
7816 y *= m_displayScale;
7817
7818 m_MouseDragging = event.Dragging();
7819
7820 // Some systems produce null drag events, where the pointer position has not
7821 // changed from the previous value. Detect this case, and abort further
7822 // processing (FS#1748)
7823#ifdef __WXMSW__
7824 if (event.Dragging()) {
7825 if ((x == mouse_x) && (y == mouse_y)) return true;
7826 }
7827#endif
7828
7829 mouse_x = x;
7830 mouse_y = y;
7831 mouse_leftisdown = event.LeftDown();
7833
7834 // Establish the event region
7835 cursor_region = CENTER;
7836
7837 int chartbar_height = GetChartbarHeight();
7838
7839 if (m_Compass && m_Compass->IsShown() &&
7840 m_Compass->GetRect().Contains(event.GetPosition())) {
7841 cursor_region = CENTER;
7842 } else if (x > xr_margin) {
7843 cursor_region = MID_RIGHT;
7844 } else if (x < xl_margin) {
7845 cursor_region = MID_LEFT;
7846 } else if (y > yb_margin - chartbar_height &&
7847 y < m_canvas_height - chartbar_height) {
7848 cursor_region = MID_TOP;
7849 } else if (y < yt_margin) {
7850 cursor_region = MID_BOT;
7851 } else {
7852 cursor_region = CENTER;
7853 }
7854
7855 if (!g_btouch) SetCanvasCursor(event);
7856
7857 // Protect from leftUp's coming from event handlers in child
7858 // windows who return focus to the canvas.
7859 leftIsDown = event.LeftDown();
7860
7861#ifndef __WXOSX__
7862 if (event.LeftDown()) {
7863 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7864 // The menu bar is temporarily visible due to alt having been pressed.
7865 // Clicking will hide it, and do nothing else.
7866 g_bTempShowMenuBar = false;
7867 top_frame::Get()->ApplyGlobalSettings(false);
7868 return (true);
7869 }
7870 }
7871#endif
7872
7873 // Update modifiers here; some window managers never send the key event
7874 m_modkeys = 0;
7875 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7876 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7877
7878#ifdef __WXMSW__
7879 // TODO Test carefully in other platforms, remove ifdef....
7880 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7881 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7882#endif
7883
7884 event.SetEventObject(this);
7885 if (SendMouseEventToPlugins(event))
7886 return (true); // PlugIn did something, and does not want the canvas to
7887 // do anything else
7888
7889 // Capture LeftUp's and time them, unless it already came from the timer.
7890
7891 // Detect end of chart dragging
7892 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7893 StartChartDragInertia();
7894 }
7895
7896 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7897 !singleClickEventIsValid) {
7898 // Ignore the second LeftUp after the DClick.
7899 if (m_DoubleClickTimer->IsRunning()) {
7900 m_DoubleClickTimer->Stop();
7901 return (true);
7902 }
7903
7904 // Save the event for later running if there is no DClick.
7905 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7906 singleClickEvent = event;
7907 singleClickEventIsValid = true;
7908 return (true);
7909 }
7910
7911 // This logic is necessary on MSW to handle the case where
7912 // a context (right-click) menu is dismissed without action
7913 // by clicking on the chart surface.
7914 // We need to avoid an unintentional pan by eating some clicks...
7915#ifdef __WXMSW__
7916 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7917 if (g_click_stop > 0) {
7918 g_click_stop--;
7919 return (true);
7920 }
7921 }
7922#endif
7923
7924 // Kick off the Rotation control timer
7925 if (GetUpMode() == COURSE_UP_MODE) {
7926 m_b_rot_hidef = false;
7927 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7928 } else
7929 pRotDefTimer->Stop();
7930
7931 // Retrigger the route leg / AIS target popup timer
7932 bool bRoll = !g_btouch;
7933#ifdef __ANDROID__
7934 bRoll = g_bRollover;
7935#endif
7936 if (bRoll) {
7937 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7938 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7939 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7940 m_RolloverPopupTimer.Start(
7941 10,
7942 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7943 else
7944 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7945 }
7946
7947 // Retrigger the cursor tracking timer
7948 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7949
7950// Show cursor position on Status Bar, if present
7951// except for GTK, under which status bar updates are very slow
7952// due to Update() call.
7953// In this case, as a workaround, update the status window
7954// after an interval timer (pCurTrackTimer) pops, which will happen
7955// whenever the mouse has stopped moving for specified interval.
7956// See the method OnCursorTrackTimerEvent()
7957#if !defined(__WXGTK__) && !defined(__WXQT__)
7958 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7959#endif
7960
7961 // Send the current cursor lat/lon to all PlugIns requesting it
7962 if (g_pi_manager) {
7963 // Occasionally, MSW will produce nonsense events on right click....
7964 // This results in an error in cursor geo position, so we skip this case
7965 if ((x >= 0) && (y >= 0))
7966 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7967 }
7968
7969 if (!g_btouch) {
7970 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7971 wxPoint p = ClientToScreen(wxPoint(x, y));
7972 }
7973 }
7974
7975 if (1 ) {
7976 // Route Creation Rubber Banding
7977 if (m_routeState >= 2) {
7978 r_rband.x = x;
7979 r_rband.y = y;
7980 m_bDrawingRoute = true;
7981
7982 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7983 Refresh(false);
7984 }
7985
7986 // Measure Tool Rubber Banding
7987 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7988 r_rband.x = x;
7989 r_rband.y = y;
7990 m_bDrawingRoute = true;
7991
7992 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7993 Refresh(false);
7994 }
7995 }
7996 return bret;
7997}
7998
7999int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8000 // On general Right Click
8001 // Look for selectable objects
8002 double slat = lat;
8003 double slon = lon;
8004
8005#if defined(__WXMAC__) || defined(__ANDROID__)
8006 wxScreenDC sdc;
8007 ocpnDC dc(sdc);
8008#else
8009 wxClientDC cdc(GetParent());
8010 ocpnDC dc(cdc);
8011#endif
8012
8013 SelectItem *pFindAIS;
8014 SelectItem *pFindRP;
8015 SelectItem *pFindRouteSeg;
8016 SelectItem *pFindTrackSeg;
8017 SelectItem *pFindCurrent = NULL;
8018 SelectItem *pFindTide = NULL;
8019
8020 // Deselect any current objects
8021 if (m_pSelectedRoute) {
8022 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8023 m_pSelectedRoute->DeSelectRoute();
8024#ifdef ocpnUSE_GL
8025 if (g_bopengl && m_glcc) {
8026 InvalidateGL();
8027 Update();
8028 } else
8029#endif
8030 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8031 }
8032
8033 if (m_pFoundRoutePoint) {
8034 m_pFoundRoutePoint->m_bPtIsSelected = false;
8035 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8036 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8037 }
8038
8041 if (g_btouch && m_pRoutePointEditTarget) {
8042 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8043 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8044 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8045 }
8046
8047 // Get all the selectable things at the cursor
8048 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8049 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8050 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8051 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8052 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8053
8054 if (m_bShowCurrent)
8055 pFindCurrent =
8056 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8057
8058 if (m_bShowTide) // look for tide stations
8059 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8060
8061 int seltype = 0;
8062
8063 // Try for AIS targets first
8064 if (pFindAIS) {
8065 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8066
8067 // Make sure the target data is available
8068 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8069 seltype |= SELTYPE_AISTARGET;
8070 }
8071
8072 // Now examine the various Route parts
8073
8074 m_pFoundRoutePoint = NULL;
8075 if (pFindRP) {
8076 RoutePoint *pFirstVizPoint = NULL;
8077 RoutePoint *pFoundActiveRoutePoint = NULL;
8078 RoutePoint *pFoundVizRoutePoint = NULL;
8079 Route *pSelectedActiveRoute = NULL;
8080 Route *pSelectedVizRoute = NULL;
8081
8082 // There is at least one routepoint, so get the whole list
8083 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8084 SelectableItemList SelList =
8085 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8086 for (SelectItem *pFindSel : SelList) {
8087 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8088
8089 // Get an array of all routes using this point
8090 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8091
8092 // Use route array (if any) to determine actual visibility for this point
8093 bool brp_viz = false;
8094 if (proute_array) {
8095 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8096 Route *pr = (Route *)proute_array->Item(ir);
8097 if (pr->IsVisible()) {
8098 brp_viz = true;
8099 break;
8100 }
8101 }
8102 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8103 // but still exists as a waypoint
8104 brp_viz = prp->IsVisible(); // so treat as isolated point
8105
8106 } else
8107 brp_viz = prp->IsVisible(); // isolated point
8108
8109 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8110
8111 // Use route array to choose the appropriate route
8112 // Give preference to any active route, otherwise select the first visible
8113 // route in the array for this point
8114 m_pSelectedRoute = NULL;
8115 if (proute_array) {
8116 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8117 Route *pr = (Route *)proute_array->Item(ir);
8118 if (pr->m_bRtIsActive) {
8119 pSelectedActiveRoute = pr;
8120 pFoundActiveRoutePoint = prp;
8121 break;
8122 }
8123 }
8124
8125 if (NULL == pSelectedVizRoute) {
8126 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8127 Route *pr = (Route *)proute_array->Item(ir);
8128 if (pr->IsVisible()) {
8129 pSelectedVizRoute = pr;
8130 pFoundVizRoutePoint = prp;
8131 break;
8132 }
8133 }
8134 }
8135
8136 delete proute_array;
8137 }
8138 }
8139
8140 // Now choose the "best" selections
8141 if (pFoundActiveRoutePoint) {
8142 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8143 m_pSelectedRoute = pSelectedActiveRoute;
8144 } else if (pFoundVizRoutePoint) {
8145 m_pFoundRoutePoint = pFoundVizRoutePoint;
8146 m_pSelectedRoute = pSelectedVizRoute;
8147 } else
8148 // default is first visible point in list
8149 m_pFoundRoutePoint = pFirstVizPoint;
8150
8151 if (m_pSelectedRoute) {
8152 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8153 } else if (m_pFoundRoutePoint) {
8154 seltype |= SELTYPE_MARKPOINT;
8155 }
8156
8157 // Highlight the selected point, to verify the proper right click selection
8158 if (m_pFoundRoutePoint) {
8159 m_pFoundRoutePoint->m_bPtIsSelected = true;
8160 wxRect wp_rect;
8161 RoutePointGui(*m_pFoundRoutePoint)
8162 .CalculateDCRect(m_dc_route, this, &wp_rect);
8163 RefreshRect(wp_rect, true);
8164 }
8165 }
8166
8167 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8168 // routes But call the popup handler with identifier appropriate to the type
8169 if (pFindRouteSeg) // there is at least one select item
8170 {
8171 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8172 SelectableItemList SelList =
8173 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8174
8175 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8176 {
8177 // Choose the first visible route containing segment in the list
8178 for (SelectItem *pFindSel : SelList) {
8179 Route *pr = (Route *)pFindSel->m_pData3;
8180 if (pr->IsVisible()) {
8181 m_pSelectedRoute = pr;
8182 break;
8183 }
8184 }
8185 }
8186
8187 if (m_pSelectedRoute) {
8188 if (NULL == m_pFoundRoutePoint)
8189 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8190
8191 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8192 if (m_pSelectedRoute->m_bRtIsSelected) {
8193#ifdef ocpnUSE_GL
8194 if (g_bopengl && m_glcc) {
8195 InvalidateGL();
8196 Update();
8197 } else
8198#endif
8199 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8200 }
8201 seltype |= SELTYPE_ROUTESEGMENT;
8202 }
8203 }
8204
8205 if (pFindTrackSeg) {
8206 m_pSelectedTrack = NULL;
8207 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8208 SelectableItemList SelList =
8209 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8210
8211 // Choose the first visible track containing segment in the list
8212 for (SelectItem *pFindSel : SelList) {
8213 Track *pt = (Track *)pFindSel->m_pData3;
8214 if (pt->IsVisible()) {
8215 m_pSelectedTrack = pt;
8216 break;
8217 }
8218 }
8219 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8220 }
8221
8222#if 0 // disable tide and current graph on right click
8223 {
8224 if (pFindCurrent) {
8225 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8226 seltype |= SELTYPE_CURRENTPOINT;
8227 }
8228
8229 else if (pFindTide) {
8230 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8231 seltype |= SELTYPE_TIDEPOINT;
8232 }
8233 }
8234#endif
8235
8236 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8237
8238 return seltype;
8239}
8240
8241IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8242 // There may be multiple current entries at the same point.
8243 // For example, there often is a current substation (with directions
8244 // specified) co-located with its master. We want to select the
8245 // substation, so that the direction will be properly indicated on the
8246 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8247 // substation)
8248 IDX_entry *pIDX_best_candidate;
8249
8250 SelectItem *pFind = NULL;
8251 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8252 SelectableItemList SelList =
8253 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8254
8255 // Default is first entry
8256 pFind = *SelList.begin();
8257 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8258
8259 auto node = SelList.begin();
8260 if (SelList.size() > 1) {
8261 for (++node; node != SelList.end(); ++node) {
8262 pFind = *node;
8263 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8264 if (pIDX_candidate->IDX_type == 'c') {
8265 pIDX_best_candidate = pIDX_candidate;
8266 break;
8267 }
8268 } // while (node)
8269 } else {
8270 pFind = *SelList.begin();
8271 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8272 }
8273
8274 return pIDX_best_candidate;
8275}
8276void ChartCanvas::CallPopupMenu(int x, int y) {
8277 last_drag.x = x;
8278 last_drag.y = y;
8279 if (m_routeState) { // creating route?
8280 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8281 return;
8282 }
8283
8285
8286 // If tide or current point is selected, then show the TC dialog immediately
8287 // without context menu
8288 if (SELTYPE_CURRENTPOINT == seltype) {
8289 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8290 Refresh(false);
8291 return;
8292 }
8293
8294 if (SELTYPE_TIDEPOINT == seltype) {
8295 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8296 Refresh(false);
8297 return;
8298 }
8299
8300 InvokeCanvasMenu(x, y, seltype);
8301
8302 // Clean up if not deleted in InvokeCanvasMenu
8303 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8304 m_pSelectedRoute->m_bRtIsSelected = false;
8305 }
8306
8307 m_pSelectedRoute = NULL;
8308
8309 if (m_pFoundRoutePoint) {
8310 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8311 m_pFoundRoutePoint->m_bPtIsSelected = false;
8312 }
8313 m_pFoundRoutePoint = NULL;
8314
8315 Refresh(true);
8316 // Refresh(false); // needed for MSW, not GTK Why??
8317}
8318
8319bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8320 // For now just bail out completely if the point clicked is not on the chart
8321 if (std::isnan(m_cursor_lat)) return false;
8322
8323 // Mouse Clicks
8324 bool ret = false; // return true if processed
8325
8326 int x, y, mx, my;
8327 event.GetPosition(&x, &y);
8328 mx = x;
8329 my = y;
8330
8331 // Calculate meaningful SelectRadius
8332 float SelectRadius;
8333 SelectRadius = g_Platform->GetSelectRadiusPix() /
8334 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8335
8337 // We start with Double Click processing. The first left click just starts a
8338 // timer and is remembered, then we actually do something if there is a
8339 // LeftDClick. If there is, the two single clicks are ignored.
8340
8341 if (event.LeftDClick() && (cursor_region == CENTER)) {
8342 m_DoubleClickTimer->Start();
8343 singleClickEventIsValid = false;
8344
8345 double zlat, zlon;
8347 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8348
8349 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8350 if (m_bShowAIS) {
8351 SelectItem *pFindAIS;
8352 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8353
8354 if (pFindAIS) {
8355 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8356 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8357 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8358 }
8359 return true;
8360 }
8361 }
8362
8363 SelectableItemList rpSelList =
8364 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8365 bool b_onRPtarget = false;
8366 for (SelectItem *pFind : rpSelList) {
8367 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8368 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8369 b_onRPtarget = true;
8370 break;
8371 }
8372 }
8373
8374 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8375
8376 // Get and honor the plugin API ContextMenuMask
8377 std::unique_ptr<HostApi> host_api = GetHostApi();
8378 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8379
8380 if (m_pRoutePointEditTarget) {
8381 if (b_onRPtarget) {
8382 if ((api_121->GetContextMenuMask() &
8383 api_121->kContextMenuDisableWaypoint))
8384 return true;
8385 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8386 return true;
8387 } else {
8388 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8389 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8390 if (g_btouch)
8391 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8392 wxRect wp_rect;
8393 RoutePointGui(*m_pRoutePointEditTarget)
8394 .CalculateDCRect(m_dc_route, this, &wp_rect);
8395 m_pRoutePointEditTarget = NULL; // cancel selection
8396 RefreshRect(wp_rect, true);
8397 return true;
8398 }
8399 } else {
8400 auto node = rpSelList.begin();
8401 if (node != rpSelList.end()) {
8402 SelectItem *pFind = *node;
8403 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8404 if (frp) {
8405 wxArrayPtrVoid *proute_array =
8407
8408 // Use route array (if any) to determine actual visibility for this
8409 // point
8410 bool brp_viz = false;
8411 if (proute_array) {
8412 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8413 Route *pr = (Route *)proute_array->Item(ir);
8414 if (pr->IsVisible()) {
8415 brp_viz = true;
8416 break;
8417 }
8418 }
8419 delete proute_array;
8420 if (!brp_viz &&
8421 frp->IsShared()) // is not visible as part of route, but
8422 // still exists as a waypoint
8423 brp_viz = frp->IsVisible(); // so treat as isolated point
8424 } else
8425 brp_viz = frp->IsVisible(); // isolated point
8426
8427 if (brp_viz) {
8428 if ((api_121->GetContextMenuMask() &
8429 api_121->kContextMenuDisableWaypoint))
8430 return true;
8431
8432 ShowMarkPropertiesDialog(frp);
8433 return true;
8434 }
8435 }
8436 }
8437 }
8438
8439 SelectItem *cursorItem;
8440
8441 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8442 if (cursorItem) {
8443 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8444 return true;
8445 Route *pr = (Route *)cursorItem->m_pData3;
8446 if (pr->IsVisible()) {
8447 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8448 return true;
8449 }
8450 }
8451
8452 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8453 if (cursorItem) {
8454 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8455 return true;
8456 Track *pt = (Track *)cursorItem->m_pData3;
8457 if (pt->IsVisible()) {
8458 ShowTrackPropertiesDialog(pt);
8459 return true;
8460 }
8461 }
8462
8463 // Tide and current points
8464 SelectItem *pFindCurrent = NULL;
8465 SelectItem *pFindTide = NULL;
8466
8467 if (m_bShowCurrent) { // look for current stations
8468 pFindCurrent =
8469 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8470 if (pFindCurrent) {
8471 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8472 // Check for plugin graphic override
8473 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8474 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8475 PlugInContainer *pic = plugin_array->Item(i);
8476 if (pic->m_enabled && pic->m_init_state &&
8477 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8478 if (ptcmgr) {
8479 TCClickInfo info;
8480 if (m_pIDXCandidate) {
8481 info.point_type = CURRENT_STATION;
8482 info.index = m_pIDXCandidate->IDX_rec_num;
8483 info.name = m_pIDXCandidate->IDX_station_name;
8484 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8485 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8486 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8487 };
8488 auto plugin =
8489 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8490 if (plugin) plugin->OnTideCurrentClick(info);
8491 return true;
8492 }
8493 }
8494 }
8495 }
8496
8497 // Default action
8498 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8499 Refresh(false);
8500 return true;
8501 }
8502 }
8503
8504 if (m_bShowTide) { // look for tide stations
8505 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8506 if (pFindTide) {
8507 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8508 // Check for plugin graphic override
8509 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8510 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8511 PlugInContainer *pic = plugin_array->Item(i);
8512 if (pic->m_enabled && pic->m_init_state &&
8513 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8514 if (ptcmgr) {
8515 TCClickInfo info;
8516 if (m_pIDXCandidate) {
8517 info.point_type = TIDE_STATION;
8518 info.index = m_pIDXCandidate->IDX_rec_num;
8519 info.name = m_pIDXCandidate->IDX_station_name;
8520 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8521 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8522 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8523 };
8524 auto plugin =
8525 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8526 if (plugin) plugin->OnTideCurrentClick(info);
8527 return true;
8528 }
8529 }
8530 }
8531 }
8532
8533 // Default action
8534 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8535 Refresh(false);
8536 return true;
8537 }
8538 }
8539
8540 // Found no object to act on, so show chart info.
8541 ShowObjectQueryWindow(x, y, zlat, zlon);
8542 return true;
8543 }
8544
8546 if (event.LeftDown()) {
8547 // This really should not be needed, but....
8548 // on Windows, when using wxAUIManager, sometimes the focus is lost
8549 // when clicking into another pane, e.g.the AIS target list, and then back
8550 // to this pane. Oddly, some mouse events are not lost, however. Like this
8551 // one....
8552 SetFocus();
8553
8554 last_drag.x = mx;
8555 last_drag.y = my;
8556 leftIsDown = true;
8557
8558 if (!g_btouch) {
8559 if (m_routeState) // creating route?
8560 {
8561 double rlat, rlon;
8562 bool appending = false;
8563 bool inserting = false;
8564 Route *tail = 0;
8565
8566 SetCursor(*pCursorPencil);
8567 rlat = m_cursor_lat;
8568 rlon = m_cursor_lon;
8569
8570 m_bRouteEditing = true;
8571
8572 if (m_routeState == 1) {
8573 m_pMouseRoute = new Route();
8574 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8575 pRouteList->push_back(m_pMouseRoute);
8576 r_rband.x = x;
8577 r_rband.y = y;
8578 }
8579
8580 // Check to see if there is a nearby point which may be reused
8581 RoutePoint *pMousePoint = NULL;
8582
8583 // Calculate meaningful SelectRadius
8584 double nearby_radius_meters =
8585 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8586
8587 RoutePoint *pNearbyPoint =
8588 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8589 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8590 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8591 wxArrayPtrVoid *proute_array =
8592 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8593
8594 // Use route array (if any) to determine actual visibility for this
8595 // point
8596 bool brp_viz = false;
8597 if (proute_array) {
8598 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8599 Route *pr = (Route *)proute_array->Item(ir);
8600 if (pr->IsVisible()) {
8601 brp_viz = true;
8602 break;
8603 }
8604 }
8605 delete proute_array;
8606 if (!brp_viz &&
8607 pNearbyPoint->IsShared()) // is not visible as part of route,
8608 // but still exists as a waypoint
8609 brp_viz =
8610 pNearbyPoint->IsVisible(); // so treat as isolated point
8611 } else
8612 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8613
8614 if (brp_viz) {
8615 wxString msg = _("Use nearby waypoint?");
8616 // Don't add a mark without name to the route. Name it if needed
8617 const bool noname(pNearbyPoint->GetName() == "");
8618 if (noname) {
8619 msg =
8620 _("Use nearby nameless waypoint and name it M with"
8621 " a unique number?");
8622 }
8623 // Avoid route finish on focus change for message dialog
8624 m_FinishRouteOnKillFocus = false;
8625 int dlg_return =
8626 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8627 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8628 m_FinishRouteOnKillFocus = true;
8629 if (dlg_return == wxID_YES) {
8630 if (noname) {
8631 if (m_pMouseRoute) {
8632 int last_wp_num = m_pMouseRoute->GetnPoints();
8633 // AP-ECRMB will truncate to 6 characters
8634 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8635 wxString wp_name = wxString::Format(
8636 "M%002i-%s", last_wp_num + 1, guid_short);
8637 pNearbyPoint->SetName(wp_name);
8638 } else
8639 pNearbyPoint->SetName("WPXX");
8640 }
8641 pMousePoint = pNearbyPoint;
8642
8643 // Using existing waypoint, so nothing to delete for undo.
8644 if (m_routeState > 1)
8645 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8646 Undo_HasParent, NULL);
8647
8648 tail =
8649 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8650 bool procede = false;
8651 if (tail) {
8652 procede = true;
8653 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8654 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8655 procede = false;
8656 }
8657
8658 if (procede) {
8659 int dlg_return;
8660 m_FinishRouteOnKillFocus = false;
8661 if (m_routeState ==
8662 1) { // first point in new route, preceeding route to be
8663 // added? Not touch case
8664
8665 wxString dmsg =
8666 _("Insert first part of this route in the new route?");
8667 if (tail->GetIndexOf(pMousePoint) ==
8668 tail->GetnPoints()) // Starting on last point of another
8669 // route?
8670 dmsg = _("Insert this route in the new route?");
8671
8672 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8673 dlg_return = OCPNMessageBox(
8674 this, dmsg, _("OpenCPN Route Create"),
8675 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8676 m_FinishRouteOnKillFocus = true;
8677
8678 if (dlg_return == wxID_YES) {
8679 inserting = true; // part of the other route will be
8680 // preceeding the new route
8681 }
8682 }
8683 } else {
8684 wxString dmsg =
8685 _("Append last part of this route to the new route?");
8686 if (tail->GetIndexOf(pMousePoint) == 1)
8687 dmsg = _(
8688 "Append this route to the new route?"); // Picking the
8689 // first point
8690 // of another
8691 // route?
8692
8693 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8694 dlg_return = OCPNMessageBox(
8695 this, dmsg, _("OpenCPN Route Create"),
8696 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8697 m_FinishRouteOnKillFocus = true;
8698
8699 if (dlg_return == wxID_YES) {
8700 appending = true; // part of the other route will be
8701 // appended to the new route
8702 }
8703 }
8704 }
8705 }
8706
8707 // check all other routes to see if this point appears in any
8708 // other route If it appears in NO other route, then it should e
8709 // considered an isolated mark
8710 if (!FindRouteContainingWaypoint(pMousePoint))
8711 pMousePoint->SetShared(true);
8712 }
8713 }
8714 }
8715
8716 if (NULL == pMousePoint) { // need a new point
8717 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8718 "", wxEmptyString);
8719 pMousePoint->SetNameShown(false);
8720
8721 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8722
8723 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8724
8725 if (m_routeState > 1)
8726 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8727 Undo_IsOrphanded, NULL);
8728 }
8729
8730 if (m_pMouseRoute) {
8731 if (m_routeState == 1) {
8732 // First point in the route.
8733 m_pMouseRoute->AddPoint(pMousePoint);
8734 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8735 } else {
8736 if (m_pMouseRoute->m_NextLegGreatCircle) {
8737 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8738 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8739 &rhumbBearing, &rhumbDist);
8740 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8741 rlat, &gcDist, &gcBearing, NULL);
8742 double gcDistNM = gcDist / 1852.0;
8743
8744 // Empirically found expression to get reasonable route segments.
8745 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8746 pow(rhumbDist - gcDistNM - 1, 0.5);
8747
8748 wxString msg;
8749 msg << _("For this leg the Great Circle route is ")
8750 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8751 << _(" shorter than rhumbline.\n\n")
8752 << _("Would you like include the Great Circle routing points "
8753 "for this leg?");
8754
8755 m_FinishRouteOnKillFocus = false;
8756 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8757 // does not fully capture mouse
8758
8759 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8760 wxYES_NO | wxNO_DEFAULT);
8761
8762 m_disable_edge_pan = false;
8763 m_FinishRouteOnKillFocus = true;
8764
8765 if (answer == wxID_YES) {
8766 RoutePoint *gcPoint;
8767 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8768 wxRealPoint gcCoord;
8769
8770 for (int i = 1; i <= segmentCount; i++) {
8771 double fraction = (double)i * (1.0 / (double)segmentCount);
8772 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8773 gcDist * fraction, gcBearing,
8774 &gcCoord.x, &gcCoord.y, NULL);
8775
8776 if (i < segmentCount) {
8777 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8778 wxEmptyString);
8779 gcPoint->SetNameShown(false);
8780 // pConfig->AddNewWayPoint(gcPoint, -1);
8781 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8782
8783 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8784 gcPoint);
8785 } else {
8786 gcPoint = pMousePoint; // Last point, previously exsisting!
8787 }
8788
8789 m_pMouseRoute->AddPoint(gcPoint);
8790 pSelect->AddSelectableRouteSegment(
8791 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8792 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8793 prevGcPoint = gcPoint;
8794 }
8795
8796 undo->CancelUndoableAction(true);
8797
8798 } else {
8799 m_pMouseRoute->AddPoint(pMousePoint);
8800 pSelect->AddSelectableRouteSegment(
8801 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8802 pMousePoint, m_pMouseRoute);
8803 undo->AfterUndoableAction(m_pMouseRoute);
8804 }
8805 } else {
8806 // Ordinary rhumblinesegment.
8807 m_pMouseRoute->AddPoint(pMousePoint);
8808 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8809 rlon, m_prev_pMousePoint,
8810 pMousePoint, m_pMouseRoute);
8811 undo->AfterUndoableAction(m_pMouseRoute);
8812 }
8813 }
8814 }
8815 m_prev_rlat = rlat;
8816 m_prev_rlon = rlon;
8817 m_prev_pMousePoint = pMousePoint;
8818 if (m_pMouseRoute)
8819 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8820
8821 m_routeState++;
8822
8823 if (appending ||
8824 inserting) { // Appending a route or making a new route
8825 int connect = tail->GetIndexOf(pMousePoint);
8826 if (connect == 1) {
8827 inserting = false; // there is nothing to insert
8828 appending = true; // so append
8829 }
8830 int length = tail->GetnPoints();
8831
8832 int i;
8833 int start, stop;
8834 if (appending) {
8835 start = connect + 1;
8836 stop = length;
8837 } else { // inserting
8838 start = 1;
8839 stop = connect;
8840 m_pMouseRoute->RemovePoint(
8841 m_pMouseRoute
8842 ->GetLastPoint()); // Remove the first and only point
8843 }
8844 for (i = start; i <= stop; i++) {
8845 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8846 if (m_pMouseRoute)
8847 m_pMouseRoute->m_lastMousePointIndex =
8848 m_pMouseRoute->GetnPoints();
8849 m_routeState++;
8850 top_frame::Get()->RefreshAllCanvas();
8851 ret = true;
8852 }
8853 m_prev_rlat =
8854 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8855 m_prev_rlon =
8856 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8857 m_pMouseRoute->FinalizeForRendering();
8858 }
8859 top_frame::Get()->RefreshAllCanvas();
8860 ret = true;
8861 }
8862
8863 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8864 {
8865 SetCursor(*pCursorPencil);
8866
8867 if (!m_pMeasureRoute) {
8868 m_pMeasureRoute = new Route();
8869 pRouteList->push_back(m_pMeasureRoute);
8870 }
8871
8872 if (m_nMeasureState == 1) {
8873 r_rband.x = x;
8874 r_rband.y = y;
8875 }
8876
8877 RoutePoint *pMousePoint =
8878 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8879 wxEmptyString, wxEmptyString);
8880 pMousePoint->m_bShowName = false;
8881 pMousePoint->SetShowWaypointRangeRings(false);
8882
8883 m_pMeasureRoute->AddPoint(pMousePoint);
8884
8885 m_prev_rlat = m_cursor_lat;
8886 m_prev_rlon = m_cursor_lon;
8887 m_prev_pMousePoint = pMousePoint;
8888 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8889
8890 m_nMeasureState++;
8891 top_frame::Get()->RefreshAllCanvas();
8892 ret = true;
8893 }
8894
8895 else {
8896 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8897 }
8898 } // !g_btouch
8899 else { // g_btouch
8900 m_last_touch_down_pos = event.GetPosition();
8901
8902 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8903 // if near screen edge, pan with injection
8904 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8905 // return;
8906 // }
8907 }
8908 }
8909
8910 if (ret) return true;
8911 }
8912
8913 if (event.Dragging()) {
8914 // in touch screen mode ensure the finger/cursor is on the selected point's
8915 // radius to allow dragging
8916 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8917 if (g_btouch) {
8918 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8919 SelectItem *pFind = NULL;
8920 SelectableItemList SelList = pSelect->FindSelectionList(
8921 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8922 for (SelectItem *pFind : SelList) {
8923 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8924 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8925 }
8926 }
8927
8928 // Check for use of dragHandle
8929 if (m_pRoutePointEditTarget &&
8930 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8931 SelectItem *pFind = NULL;
8932 SelectableItemList SelList = pSelect->FindSelectionList(
8933 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8934 for (SelectItem *pFind : SelList) {
8935 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8936 if (m_pRoutePointEditTarget == frp) {
8937 m_bIsInRadius = true;
8938 break;
8939 }
8940 }
8941
8942 if (!m_dragoffsetSet) {
8943 RoutePointGui(*m_pRoutePointEditTarget)
8944 .PresetDragOffset(this, mouse_x, mouse_y);
8945 m_dragoffsetSet = true;
8946 }
8947 }
8948 }
8949
8950 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8951 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8952
8953 if (NULL == g_pMarkInfoDialog) {
8954 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8955 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8956 DraggingAllowed = false;
8957
8958 if (m_pRoutePointEditTarget &&
8959 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8960 DraggingAllowed = false;
8961
8962 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8963
8964 if (DraggingAllowed) {
8965 if (!undo->InUndoableAction()) {
8966 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8967 Undo_NeedsCopy, m_pFoundPoint);
8968 }
8969
8970 // Get the update rectangle for the union of the un-edited routes
8971 wxRect pre_rect;
8972
8973 if (!g_bopengl && m_pEditRouteArray) {
8974 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8975 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8976 // Need to validate route pointer
8977 // Route may be gone due to drgging close to ownship with
8978 // "Delete On Arrival" state set, as in the case of
8979 // navigating to an isolated waypoint on a temporary route
8980 if (g_pRouteMan->IsRouteValid(pr)) {
8981 wxRect route_rect;
8982 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8983 pre_rect.Union(route_rect);
8984 }
8985 }
8986 }
8987
8988 double new_cursor_lat = m_cursor_lat;
8989 double new_cursor_lon = m_cursor_lon;
8990
8991 if (CheckEdgePan(x, y, true, 5, 2))
8992 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8993
8994 // update the point itself
8995 if (g_btouch) {
8996 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8997 // new_cursor_lat, new_cursor_lon);
8998 RoutePointGui(*m_pRoutePointEditTarget)
8999 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9000 // update the Drag Handle entry in the pSelect list
9001 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9002 m_pRoutePointEditTarget,
9003 SELTYPE_DRAGHANDLE);
9004 m_pFoundPoint->m_slat =
9005 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9006 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9007 } else {
9008 m_pRoutePointEditTarget->m_lat =
9009 new_cursor_lat; // update the RoutePoint entry
9010 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9011 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9012 m_pFoundPoint->m_slat =
9013 new_cursor_lat; // update the SelectList entry
9014 m_pFoundPoint->m_slon = new_cursor_lon;
9015 }
9016
9017 // Update the MarkProperties Dialog, if currently shown
9018 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9019 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9020 g_pMarkInfoDialog->UpdateProperties(true);
9021 }
9022
9023 if (g_bopengl) {
9024 // InvalidateGL();
9025 Refresh(false);
9026 } else {
9027 // Get the update rectangle for the edited route
9028 wxRect post_rect;
9029
9030 if (m_pEditRouteArray) {
9031 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9032 ir++) {
9033 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9034 if (g_pRouteMan->IsRouteValid(pr)) {
9035 wxRect route_rect;
9036 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9037 post_rect.Union(route_rect);
9038 }
9039 }
9040 }
9041
9042 // Invalidate the union region
9043 pre_rect.Union(post_rect);
9044 RefreshRect(pre_rect, false);
9045 }
9046 top_frame::Get()->RefreshCanvasOther(this);
9047 m_bRoutePoinDragging = true;
9048 }
9049 ret = true;
9050 } // if Route Editing
9051
9052 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9053 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9054
9055 if (NULL == g_pMarkInfoDialog) {
9056 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9057 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9058 DraggingAllowed = false;
9059
9060 if (m_pRoutePointEditTarget &&
9061 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9062 DraggingAllowed = false;
9063
9064 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9065
9066 if (DraggingAllowed) {
9067 if (!undo->InUndoableAction()) {
9068 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9069 Undo_NeedsCopy, m_pFoundPoint);
9070 }
9071
9072 // The mark may be an anchorwatch
9073 double lpp1 = 0.;
9074 double lpp2 = 0.;
9075 double lppmax;
9076
9077 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9078 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9079 }
9080 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9081 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9082 }
9083 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9084
9085 // Get the update rectangle for the un-edited mark
9086 wxRect pre_rect;
9087 if (!g_bopengl) {
9088 RoutePointGui(*m_pRoutePointEditTarget)
9089 .CalculateDCRect(m_dc_route, this, &pre_rect);
9090 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9091 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9092 (int)(lppmax - (pre_rect.height / 2)));
9093 }
9094
9095 // update the point itself
9096 if (g_btouch) {
9097 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9098 // m_cursor_lat, m_cursor_lon);
9099 RoutePointGui(*m_pRoutePointEditTarget)
9100 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9101 // update the Drag Handle entry in the pSelect list
9102 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9103 m_pRoutePointEditTarget,
9104 SELTYPE_DRAGHANDLE);
9105 m_pFoundPoint->m_slat =
9106 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9107 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9108 } else {
9109 m_pRoutePointEditTarget->m_lat =
9110 m_cursor_lat; // update the RoutePoint entry
9111 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9112 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9113 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9114 m_pFoundPoint->m_slon = m_cursor_lon;
9115 }
9116
9117 // Update the MarkProperties Dialog, if currently shown
9118 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9119 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9120 g_pMarkInfoDialog->UpdateProperties(true);
9121 }
9122
9123 // Invalidate the union region
9124 if (g_bopengl) {
9125 if (!g_btouch) InvalidateGL();
9126 Refresh(false);
9127 } else {
9128 // Get the update rectangle for the edited mark
9129 wxRect post_rect;
9130 RoutePointGui(*m_pRoutePointEditTarget)
9131 .CalculateDCRect(m_dc_route, this, &post_rect);
9132 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9133 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9134 (int)(lppmax - (post_rect.height / 2)));
9135
9136 // Invalidate the union region
9137 pre_rect.Union(post_rect);
9138 RefreshRect(pre_rect, false);
9139 }
9140 top_frame::Get()->RefreshCanvasOther(this);
9141 m_bRoutePoinDragging = true;
9142 }
9143 ret = g_btouch ? m_bRoutePoinDragging : true;
9144 }
9145
9146 if (ret) return true;
9147 } // dragging
9148
9149 if (event.LeftUp()) {
9150 bool b_startedit_route = false;
9151 m_dragoffsetSet = false;
9152
9153 if (g_btouch) {
9154 m_bChartDragging = false;
9155 m_bIsInRadius = false;
9156
9157 if (m_routeState) // creating route?
9158 {
9159 if (m_ignore_next_leftup) {
9160 m_ignore_next_leftup = false;
9161 return false;
9162 }
9163
9164 if (m_bedge_pan) {
9165 m_bedge_pan = false;
9166 return false;
9167 }
9168
9169 double rlat, rlon;
9170 bool appending = false;
9171 bool inserting = false;
9172 Route *tail = 0;
9173
9174 rlat = m_cursor_lat;
9175 rlon = m_cursor_lon;
9176
9177 if (m_pRoutePointEditTarget) {
9178 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9179 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9180 if (!g_bopengl) {
9181 wxRect wp_rect;
9182 RoutePointGui(*m_pRoutePointEditTarget)
9183 .CalculateDCRect(m_dc_route, this, &wp_rect);
9184 RefreshRect(wp_rect, true);
9185 }
9186 m_pRoutePointEditTarget = NULL;
9187 }
9188 m_bRouteEditing = true;
9189
9190 if (m_routeState == 1) {
9191 m_pMouseRoute = new Route();
9192 m_pMouseRoute->SetHiLite(50);
9193 pRouteList->push_back(m_pMouseRoute);
9194 r_rband.x = x;
9195 r_rband.y = y;
9196 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9197 }
9198
9199 // Check to see if there is a nearby point which may be reused
9200 RoutePoint *pMousePoint = NULL;
9201
9202 // Calculate meaningful SelectRadius
9203 double nearby_radius_meters =
9204 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9205
9206 RoutePoint *pNearbyPoint =
9207 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9208 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9209 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9210 int dlg_return;
9211#ifndef __WXOSX__
9212 m_FinishRouteOnKillFocus =
9213 false; // Avoid route finish on focus change for message dialog
9214 dlg_return = OCPNMessageBox(
9215 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9216 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9217 m_FinishRouteOnKillFocus = true;
9218#else
9219 dlg_return = wxID_YES;
9220#endif
9221 if (dlg_return == wxID_YES) {
9222 pMousePoint = pNearbyPoint;
9223
9224 // Using existing waypoint, so nothing to delete for undo.
9225 if (m_routeState > 1)
9226 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9227 Undo_HasParent, NULL);
9228 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9229
9230 bool procede = false;
9231 if (tail) {
9232 procede = true;
9233 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9234 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9235 procede = false;
9236 }
9237
9238 if (procede) {
9239 int dlg_return;
9240 m_FinishRouteOnKillFocus = false;
9241 if (m_routeState == 1) { // first point in new route, preceeding
9242 // route to be added? touch case
9243
9244 wxString dmsg =
9245 _("Insert first part of this route in the new route?");
9246 if (tail->GetIndexOf(pMousePoint) ==
9247 tail->GetnPoints()) // Starting on last point of another
9248 // route?
9249 dmsg = _("Insert this route in the new route?");
9250
9251 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9252 dlg_return =
9253 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9254 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9255 m_FinishRouteOnKillFocus = true;
9256
9257 if (dlg_return == wxID_YES) {
9258 inserting = true; // part of the other route will be
9259 // preceeding the new route
9260 }
9261 }
9262 } else {
9263 wxString dmsg =
9264 _("Append last part of this route to the new route?");
9265 if (tail->GetIndexOf(pMousePoint) == 1)
9266 dmsg = _(
9267 "Append this route to the new route?"); // Picking the
9268 // first point of
9269 // another route?
9270
9271 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9272 dlg_return =
9273 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9274 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9275 m_FinishRouteOnKillFocus = true;
9276
9277 if (dlg_return == wxID_YES) {
9278 appending = true; // part of the other route will be
9279 // appended to the new route
9280 }
9281 }
9282 }
9283 }
9284
9285 // check all other routes to see if this point appears in any other
9286 // route If it appears in NO other route, then it should e
9287 // considered an isolated mark
9288 if (!FindRouteContainingWaypoint(pMousePoint))
9289 pMousePoint->SetShared(true);
9290 }
9291 }
9292
9293 if (NULL == pMousePoint) { // need a new point
9294 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9295 "", wxEmptyString);
9296 pMousePoint->SetNameShown(false);
9297
9298 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9299
9300 if (m_routeState > 1)
9301 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9302 Undo_IsOrphanded, NULL);
9303 }
9304
9305 if (m_routeState == 1) {
9306 // First point in the route.
9307 m_pMouseRoute->AddPoint(pMousePoint);
9308 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9309
9310 } else {
9311 if (m_pMouseRoute->m_NextLegGreatCircle) {
9312 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9313 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9314 &rhumbBearing, &rhumbDist);
9315 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9316 &gcDist, &gcBearing, NULL);
9317 double gcDistNM = gcDist / 1852.0;
9318
9319 // Empirically found expression to get reasonable route segments.
9320 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9321 pow(rhumbDist - gcDistNM - 1, 0.5);
9322
9323 wxString msg;
9324 msg << _("For this leg the Great Circle route is ")
9325 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9326 << _(" shorter than rhumbline.\n\n")
9327 << _("Would you like include the Great Circle routing points "
9328 "for this leg?");
9329
9330#ifndef __WXOSX__
9331 m_FinishRouteOnKillFocus = false;
9332 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9333 wxYES_NO | wxNO_DEFAULT);
9334 m_FinishRouteOnKillFocus = true;
9335#else
9336 int answer = wxID_NO;
9337#endif
9338
9339 if (answer == wxID_YES) {
9340 RoutePoint *gcPoint;
9341 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9342 wxRealPoint gcCoord;
9343
9344 for (int i = 1; i <= segmentCount; i++) {
9345 double fraction = (double)i * (1.0 / (double)segmentCount);
9346 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9347 gcDist * fraction, gcBearing,
9348 &gcCoord.x, &gcCoord.y, NULL);
9349
9350 if (i < segmentCount) {
9351 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9352 wxEmptyString);
9353 gcPoint->SetNameShown(false);
9354 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9355 gcPoint);
9356 } else {
9357 gcPoint = pMousePoint; // Last point, previously exsisting!
9358 }
9359
9360 m_pMouseRoute->AddPoint(gcPoint);
9361 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9362
9363 pSelect->AddSelectableRouteSegment(
9364 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9365 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9366 prevGcPoint = gcPoint;
9367 }
9368
9369 undo->CancelUndoableAction(true);
9370
9371 } else {
9372 m_pMouseRoute->AddPoint(pMousePoint);
9373 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9374 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9375 rlon, m_prev_pMousePoint,
9376 pMousePoint, m_pMouseRoute);
9377 undo->AfterUndoableAction(m_pMouseRoute);
9378 }
9379 } else {
9380 // Ordinary rhumblinesegment.
9381 m_pMouseRoute->AddPoint(pMousePoint);
9382 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9383
9384 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9385 rlon, m_prev_pMousePoint,
9386 pMousePoint, m_pMouseRoute);
9387 undo->AfterUndoableAction(m_pMouseRoute);
9388 }
9389 }
9390
9391 m_prev_rlat = rlat;
9392 m_prev_rlon = rlon;
9393 m_prev_pMousePoint = pMousePoint;
9394 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9395
9396 m_routeState++;
9397
9398 if (appending ||
9399 inserting) { // Appending a route or making a new route
9400 int connect = tail->GetIndexOf(pMousePoint);
9401 if (connect == 1) {
9402 inserting = false; // there is nothing to insert
9403 appending = true; // so append
9404 }
9405 int length = tail->GetnPoints();
9406
9407 int i;
9408 int start, stop;
9409 if (appending) {
9410 start = connect + 1;
9411 stop = length;
9412 } else { // inserting
9413 start = 1;
9414 stop = connect;
9415 m_pMouseRoute->RemovePoint(
9416 m_pMouseRoute
9417 ->GetLastPoint()); // Remove the first and only point
9418 }
9419 for (i = start; i <= stop; i++) {
9420 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9421 if (m_pMouseRoute)
9422 m_pMouseRoute->m_lastMousePointIndex =
9423 m_pMouseRoute->GetnPoints();
9424 m_routeState++;
9425 top_frame::Get()->RefreshAllCanvas();
9426 ret = true;
9427 }
9428 m_prev_rlat =
9429 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9430 m_prev_rlon =
9431 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9432 m_pMouseRoute->FinalizeForRendering();
9433 }
9434
9435 Refresh(true);
9436 ret = true;
9437 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9438 {
9439 if (m_bedge_pan) {
9440 m_bedge_pan = false;
9441 return false;
9442 }
9443
9444 if (m_ignore_next_leftup) {
9445 m_ignore_next_leftup = false;
9446 return false;
9447 }
9448
9449 if (m_nMeasureState == 1) {
9450 m_pMeasureRoute = new Route();
9451 pRouteList->push_back(m_pMeasureRoute);
9452 r_rband.x = x;
9453 r_rband.y = y;
9454 }
9455
9456 if (m_pMeasureRoute) {
9457 RoutePoint *pMousePoint =
9458 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9459 wxEmptyString, wxEmptyString);
9460 pMousePoint->m_bShowName = false;
9461
9462 m_pMeasureRoute->AddPoint(pMousePoint);
9463
9464 m_prev_rlat = m_cursor_lat;
9465 m_prev_rlon = m_cursor_lon;
9466 m_prev_pMousePoint = pMousePoint;
9467 m_pMeasureRoute->m_lastMousePointIndex =
9468 m_pMeasureRoute->GetnPoints();
9469
9470 m_nMeasureState++;
9471 } else {
9472 CancelMeasureRoute();
9473 }
9474
9475 Refresh(true);
9476 ret = true;
9477 } else {
9478 bool bSelectAllowed = true;
9479 if (NULL == g_pMarkInfoDialog) {
9480 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9481 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9482 bSelectAllowed = false;
9483
9484 // Avoid accidental selection of routepoint if last touchdown started
9485 // a significant chart drag operation
9486 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9487 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9488 significant_drag) ||
9489 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9490 significant_drag)) {
9491 bSelectAllowed = false;
9492 }
9493
9494 /*if this left up happens at the end of a route point dragging and if
9495 the cursor/thumb is on the draghandle icon, not on the point iself a new
9496 selection will select nothing and the drag will never be ended, so the
9497 legs around this point never selectable. At this step we don't need a
9498 new selection, just keep the previoulsly selected and dragged point */
9499 if (m_bRoutePoinDragging) bSelectAllowed = false;
9500
9501 if (bSelectAllowed) {
9502 bool b_was_editing_mark = m_bMarkEditing;
9503 bool b_was_editing_route = m_bRouteEditing;
9504 FindRoutePointsAtCursor(SelectRadius,
9505 true); // Possibly selecting a point in a
9506 // route for later dragging
9507
9508 /*route and a mark points in layer can't be dragged so should't be
9509 * selected and no draghandle icon*/
9510 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9511 m_pRoutePointEditTarget = NULL;
9512
9513 if (!b_was_editing_route) {
9514 if (m_pEditRouteArray) {
9515 b_startedit_route = true;
9516
9517 // Hide the track and route rollover during route point edit, not
9518 // needed, and may be confusing
9519 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9520 m_pTrackRolloverWin->IsActive(false);
9521 }
9522 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9523 m_pRouteRolloverWin->IsActive(false);
9524 }
9525
9526 wxRect pre_rect;
9527 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9528 ir++) {
9529 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9530 // Need to validate route pointer
9531 // Route may be gone due to drgging close to ownship with
9532 // "Delete On Arrival" state set, as in the case of
9533 // navigating to an isolated waypoint on a temporary route
9534 if (g_pRouteMan->IsRouteValid(pr)) {
9535 // pr->SetHiLite(50);
9536 wxRect route_rect;
9537 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9538 pre_rect.Union(route_rect);
9539 }
9540 }
9541 RefreshRect(pre_rect, true);
9542 }
9543 } else {
9544 b_startedit_route = false;
9545 }
9546
9547 // Mark editing in touch mode, left-up event.
9548 if (m_pRoutePointEditTarget) {
9549 if (b_was_editing_mark ||
9550 b_was_editing_route) { // kill previous hilight
9551 if (m_lastRoutePointEditTarget) {
9552 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9553 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9554 RoutePointGui(*m_lastRoutePointEditTarget)
9555 .EnableDragHandle(false);
9556 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9557 SELTYPE_DRAGHANDLE);
9558 }
9559 }
9560
9561 if (m_pRoutePointEditTarget) {
9562 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9563 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9564 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9565 wxPoint2DDouble dragHandlePoint =
9566 RoutePointGui(*m_pRoutePointEditTarget)
9567 .GetDragHandlePoint(this);
9568 pSelect->AddSelectablePoint(
9569 dragHandlePoint.m_y, dragHandlePoint.m_x,
9570 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9571 }
9572 } else { // Deselect everything
9573 if (m_lastRoutePointEditTarget) {
9574 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9575 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9576 RoutePointGui(*m_lastRoutePointEditTarget)
9577 .EnableDragHandle(false);
9578 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9579 SELTYPE_DRAGHANDLE);
9580
9581 // Clear any routes being edited, probably orphans
9582 wxArrayPtrVoid *lastEditRouteArray =
9584 m_lastRoutePointEditTarget);
9585 if (lastEditRouteArray) {
9586 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9587 ir++) {
9588 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9589 if (g_pRouteMan->IsRouteValid(pr)) {
9590 pr->m_bIsBeingEdited = false;
9591 }
9592 }
9593 delete lastEditRouteArray;
9594 }
9595 }
9596 }
9597
9598 // Do the refresh
9599
9600 if (g_bopengl) {
9601 InvalidateGL();
9602 Refresh(false);
9603 } else {
9604 if (m_lastRoutePointEditTarget) {
9605 wxRect wp_rect;
9606 RoutePointGui(*m_lastRoutePointEditTarget)
9607 .CalculateDCRect(m_dc_route, this, &wp_rect);
9608 RefreshRect(wp_rect, true);
9609 }
9610
9611 if (m_pRoutePointEditTarget) {
9612 wxRect wp_rect;
9613 RoutePointGui(*m_pRoutePointEditTarget)
9614 .CalculateDCRect(m_dc_route, this, &wp_rect);
9615 RefreshRect(wp_rect, true);
9616 }
9617 }
9618 }
9619 } // bSelectAllowed
9620
9621 // Check to see if there is a route or AIS target under the cursor
9622 // If so, start the rollover timer which creates the popup
9623 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9624 bool b_start_rollover = false;
9625 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9626 SelectItem *pFind = pSelectAIS->FindSelection(
9627 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9628 if (pFind) b_start_rollover = true;
9629 }
9630
9631 if (!b_start_rollover && !b_startedit_route) {
9632 SelectableItemList SelList = pSelect->FindSelectionList(
9633 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9634 for (SelectItem *pFindSel : SelList) {
9635 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9636 if (pr && pr->IsVisible()) {
9637 b_start_rollover = true;
9638 break;
9639 }
9640 } // while
9641 }
9642
9643 if (!b_start_rollover && !b_startedit_route) {
9644 SelectableItemList SelList = pSelect->FindSelectionList(
9645 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9646 for (SelectItem *pFindSel : SelList) {
9647 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9648 if (tr && tr->IsVisible()) {
9649 b_start_rollover = true;
9650 break;
9651 }
9652 } // while
9653 }
9654
9655 if (b_start_rollover)
9656 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9657 wxTIMER_ONE_SHOT);
9658 Route *tail = 0;
9659 Route *current = 0;
9660 bool appending = false;
9661 bool inserting = false;
9662 int connect = 0;
9663 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9664 // drag
9665 if (m_pRoutePointEditTarget) {
9666 // Check to see if there is a nearby point which may replace the
9667 // dragged one
9668 RoutePoint *pMousePoint = NULL;
9669
9670 int index_last;
9671 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9672 double nearby_radius_meters =
9673 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9674 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9675 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9676 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9677 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9678 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9679 bool duplicate =
9680 false; // ensure we won't create duplicate point in routes
9681 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9682 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9683 ir++) {
9684 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9685 if (pr && pr->pRoutePointList) {
9686 auto *list = pr->pRoutePointList;
9687 auto pos =
9688 std::find(list->begin(), list->end(), pNearbyPoint);
9689 if (pos != list->end()) {
9690 duplicate = true;
9691 break;
9692 }
9693 }
9694 }
9695 }
9696
9697 // Special case:
9698 // Allow "re-use" of a route's waypoints iff it is a simple
9699 // isolated route. This allows, for instance, creation of a closed
9700 // polygon route
9701 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9702
9703 if (!duplicate) {
9704 int dlg_return;
9705 dlg_return =
9706 OCPNMessageBox(this,
9707 _("Replace this RoutePoint by the nearby "
9708 "Waypoint?"),
9709 _("OpenCPN RoutePoint change"),
9710 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9711 if (dlg_return == wxID_YES) {
9712 /*double confirmation if the dragged point has been manually
9713 * created which can be important and could be deleted
9714 * unintentionally*/
9715
9716 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9717 pNearbyPoint);
9718 current =
9719 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9720
9721 if (tail && current && (tail != current)) {
9722 int dlg_return1;
9723 connect = tail->GetIndexOf(pNearbyPoint);
9724 int index_current_route =
9725 current->GetIndexOf(m_pRoutePointEditTarget);
9726 index_last = current->GetIndexOf(current->GetLastPoint());
9727 dlg_return1 = wxID_NO;
9728 if (index_last ==
9729 index_current_route) { // we are dragging the last
9730 // point of the route
9731 if (connect != tail->GetnPoints()) { // anything to do?
9732
9733 wxString dmsg(
9734 _("Last part of route to be appended to dragged "
9735 "route?"));
9736 if (connect == 1)
9737 dmsg =
9738 _("Full route to be appended to dragged route?");
9739
9740 dlg_return1 = OCPNMessageBox(
9741 this, dmsg, _("OpenCPN Route Create"),
9742 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9743 if (dlg_return1 == wxID_YES) {
9744 appending = true;
9745 }
9746 }
9747 } else if (index_current_route ==
9748 1) { // dragging the first point of the route
9749 if (connect != 1) { // anything to do?
9750
9751 wxString dmsg(
9752 _("First part of route to be inserted into dragged "
9753 "route?"));
9754 if (connect == tail->GetnPoints())
9755 dmsg = _(
9756 "Full route to be inserted into dragged route?");
9757
9758 dlg_return1 = OCPNMessageBox(
9759 this, dmsg, _("OpenCPN Route Create"),
9760 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9761 if (dlg_return1 == wxID_YES) {
9762 inserting = true;
9763 }
9764 }
9765 }
9766 }
9767
9768 if (m_pRoutePointEditTarget->IsShared()) {
9769 // dlg_return = wxID_NO;
9770 dlg_return = OCPNMessageBox(
9771 this,
9772 _("Do you really want to delete and replace this "
9773 "WayPoint") +
9774 "\n" + _("which has been created manually?"),
9775 ("OpenCPN RoutePoint warning"),
9776 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9777 }
9778 }
9779 if (dlg_return == wxID_YES) {
9780 pMousePoint = pNearbyPoint;
9781 if (pMousePoint->m_bIsolatedMark) {
9782 pMousePoint->SetShared(true);
9783 }
9784 pMousePoint->m_bIsolatedMark =
9785 false; // definitely no longer isolated
9786 pMousePoint->m_bIsInRoute = true;
9787 }
9788 }
9789 }
9790 }
9791 if (!pMousePoint)
9792 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9793
9794 if (m_pEditRouteArray) {
9795 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9796 ir++) {
9797 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9798 if (g_pRouteMan->IsRouteValid(pr)) {
9799 if (pMousePoint) { // remove the dragged point and insert the
9800 // nearby
9801 auto *list = pr->pRoutePointList;
9802 auto pos = std::find(list->begin(), list->end(),
9803 m_pRoutePointEditTarget);
9804
9805 pSelect->DeleteAllSelectableRoutePoints(pr);
9806 pSelect->DeleteAllSelectableRouteSegments(pr);
9807
9808 pr->pRoutePointList->insert(pos, pMousePoint);
9809 pos = std::find(list->begin(), list->end(),
9810 m_pRoutePointEditTarget);
9811 pr->pRoutePointList->erase(pos);
9812
9813 pSelect->AddAllSelectableRouteSegments(pr);
9814 pSelect->AddAllSelectableRoutePoints(pr);
9815 }
9816 pr->FinalizeForRendering();
9817 pr->UpdateSegmentDistances();
9818 if (m_bRoutePoinDragging) {
9819 // pConfig->UpdateRoute(pr);
9820 NavObj_dB::GetInstance().UpdateRoute(pr);
9821 }
9822 }
9823 }
9824 }
9825
9826 // Update the RouteProperties Dialog, if currently shown
9827 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9828 if (m_pEditRouteArray) {
9829 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9830 ir++) {
9831 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9832 if (g_pRouteMan->IsRouteValid(pr)) {
9833 if (pRoutePropDialog->GetRoute() == pr) {
9834 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9835 }
9836 /* cannot edit track points anyway
9837 else if ( ( NULL !=
9838 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9839 pTrackPropDialog->m_pTrack == pr ) {
9840 pTrackPropDialog->SetTrackAndUpdate(
9841 pr );
9842 }
9843 */
9844 }
9845 }
9846 }
9847 }
9848 if (pMousePoint) { // clear all about the dragged point
9849 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9850 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9851 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9852 // Hide mark properties dialog if open on the replaced point
9853 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9854 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9855 g_pMarkInfoDialog->Hide();
9856
9857 delete m_pRoutePointEditTarget;
9858 m_lastRoutePointEditTarget = NULL;
9859 m_pRoutePointEditTarget = NULL;
9860 undo->AfterUndoableAction(pMousePoint);
9861 undo->InvalidateUndo();
9862 }
9863 }
9864 }
9865
9866 else if (m_bMarkEditing) { // End of way point drag
9867 if (m_pRoutePointEditTarget)
9868 if (m_bRoutePoinDragging) {
9869 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9870 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9871 }
9872 }
9873
9874 if (m_pRoutePointEditTarget)
9875 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9876
9877 if (!m_pRoutePointEditTarget) {
9878 delete m_pEditRouteArray;
9879 m_pEditRouteArray = NULL;
9880 m_bRouteEditing = false;
9881 }
9882 m_bRoutePoinDragging = false;
9883
9884 if (appending) { // Appending to the route of which the last point is
9885 // dragged onto another route
9886
9887 // copy tail from connect until length to end of current after dragging
9888
9889 int length = tail->GetnPoints();
9890 for (int i = connect + 1; i <= length; i++) {
9891 current->AddPointAndSegment(tail->GetPoint(i), false);
9892 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9893 m_routeState++;
9894 top_frame::Get()->RefreshAllCanvas();
9895 ret = true;
9896 }
9897 current->FinalizeForRendering();
9898 current->m_bIsBeingEdited = false;
9899 FinishRoute();
9900 g_pRouteMan->DeleteRoute(tail);
9901 }
9902 if (inserting) {
9903 pSelect->DeleteAllSelectableRoutePoints(current);
9904 pSelect->DeleteAllSelectableRouteSegments(current);
9905 for (int i = 1; i < connect; i++) { // numbering in the tail route
9906 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9907 }
9908 pSelect->AddAllSelectableRouteSegments(current);
9909 pSelect->AddAllSelectableRoutePoints(current);
9910 current->FinalizeForRendering();
9911 current->m_bIsBeingEdited = false;
9912 g_pRouteMan->DeleteRoute(tail);
9913 }
9914
9915 // Update the RouteProperties Dialog, if currently shown
9916 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9917 if (m_pEditRouteArray) {
9918 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9919 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9920 if (g_pRouteMan->IsRouteValid(pr)) {
9921 if (pRoutePropDialog->GetRoute() == pr) {
9922 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9923 }
9924 }
9925 }
9926 }
9927 }
9928
9929 } // g_btouch
9930
9931 else { // !g_btouch
9932 if (m_bRouteEditing) { // End of RoutePoint drag
9933 Route *tail = 0;
9934 Route *current = 0;
9935 bool appending = false;
9936 bool inserting = false;
9937 int connect = 0;
9938 int index_last;
9939 if (m_pRoutePointEditTarget) {
9940 m_pRoutePointEditTarget->m_bBlink = false;
9941 // Check to see if there is a nearby point which may replace the
9942 // dragged one
9943 RoutePoint *pMousePoint = NULL;
9944 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9945 double nearby_radius_meters =
9946 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9947 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9948 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9949 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9950 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9951 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9952 bool duplicate = false; // don't create duplicate point in routes
9953 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9954 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9955 ir++) {
9956 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9957 if (pr && pr->pRoutePointList) {
9958 auto *list = pr->pRoutePointList;
9959 auto pos =
9960 std::find(list->begin(), list->end(), pNearbyPoint);
9961 if (pos != list->end()) {
9962 duplicate = true;
9963 break;
9964 }
9965 }
9966 }
9967 }
9968
9969 // Special case:
9970 // Allow "re-use" of a route's waypoints iff it is a simple
9971 // isolated route. This allows, for instance, creation of a closed
9972 // polygon route
9973 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9974
9975 if (!duplicate) {
9976 int dlg_return;
9977 dlg_return =
9978 OCPNMessageBox(this,
9979 _("Replace this RoutePoint by the nearby "
9980 "Waypoint?"),
9981 _("OpenCPN RoutePoint change"),
9982 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9983 if (dlg_return == wxID_YES) {
9984 /*double confirmation if the dragged point has been manually
9985 * created which can be important and could be deleted
9986 * unintentionally*/
9987 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9988 pNearbyPoint);
9989 current =
9990 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9991
9992 if (tail && current && (tail != current)) {
9993 int dlg_return1;
9994 connect = tail->GetIndexOf(pNearbyPoint);
9995 int index_current_route =
9996 current->GetIndexOf(m_pRoutePointEditTarget);
9997 index_last = current->GetIndexOf(current->GetLastPoint());
9998 dlg_return1 = wxID_NO;
9999 if (index_last ==
10000 index_current_route) { // we are dragging the last
10001 // point of the route
10002 if (connect != tail->GetnPoints()) { // anything to do?
10003
10004 wxString dmsg(
10005 _("Last part of route to be appended to dragged "
10006 "route?"));
10007 if (connect == 1)
10008 dmsg =
10009 _("Full route to be appended to dragged route?");
10010
10011 dlg_return1 = OCPNMessageBox(
10012 this, dmsg, _("OpenCPN Route Create"),
10013 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10014 if (dlg_return1 == wxID_YES) {
10015 appending = true;
10016 }
10017 }
10018 } else if (index_current_route ==
10019 1) { // dragging the first point of the route
10020 if (connect != 1) { // anything to do?
10021
10022 wxString dmsg(
10023 _("First part of route to be inserted into dragged "
10024 "route?"));
10025 if (connect == tail->GetnPoints())
10026 dmsg = _(
10027 "Full route to be inserted into dragged route?");
10028
10029 dlg_return1 = OCPNMessageBox(
10030 this, dmsg, _("OpenCPN Route Create"),
10031 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10032 if (dlg_return1 == wxID_YES) {
10033 inserting = true;
10034 }
10035 }
10036 }
10037 }
10038
10039 if (m_pRoutePointEditTarget->IsShared()) {
10040 dlg_return = wxID_NO;
10041 dlg_return = OCPNMessageBox(
10042 this,
10043 _("Do you really want to delete and replace this "
10044 "WayPoint") +
10045 "\n" + _("which has been created manually?"),
10046 ("OpenCPN RoutePoint warning"),
10047 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10048 }
10049 }
10050 if (dlg_return == wxID_YES) {
10051 pMousePoint = pNearbyPoint;
10052 if (pMousePoint->m_bIsolatedMark) {
10053 pMousePoint->SetShared(true);
10054 }
10055 pMousePoint->m_bIsolatedMark =
10056 false; // definitely no longer isolated
10057 pMousePoint->m_bIsInRoute = true;
10058 }
10059 }
10060 }
10061 }
10062 if (!pMousePoint)
10063 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10064
10065 if (m_pEditRouteArray) {
10066 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10067 ir++) {
10068 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10069 if (g_pRouteMan->IsRouteValid(pr)) {
10070 if (pMousePoint) { // replace dragged point by nearby one
10071 auto *list = pr->pRoutePointList;
10072 auto pos = std::find(list->begin(), list->end(),
10073 m_pRoutePointEditTarget);
10074
10075 pSelect->DeleteAllSelectableRoutePoints(pr);
10076 pSelect->DeleteAllSelectableRouteSegments(pr);
10077
10078 pr->pRoutePointList->insert(pos, pMousePoint);
10079 pos = std::find(list->begin(), list->end(),
10080 m_pRoutePointEditTarget);
10081 if (pos != list->end()) list->erase(pos);
10082 // pr->pRoutePointList->erase(pos + 1);
10083
10084 pSelect->AddAllSelectableRouteSegments(pr);
10085 pSelect->AddAllSelectableRoutePoints(pr);
10086 }
10087 pr->FinalizeForRendering();
10088 pr->UpdateSegmentDistances();
10089 pr->m_bIsBeingEdited = false;
10090
10091 if (m_bRoutePoinDragging) {
10092 // Special case optimization.
10093 // Dragging a single point of a route
10094 // without any point additions or re-ordering
10095 if (!pMousePoint)
10096 NavObj_dB::GetInstance().UpdateRoutePoint(
10097 m_pRoutePointEditTarget);
10098 else
10099 NavObj_dB::GetInstance().UpdateRoute(pr);
10100 }
10101 pr->SetHiLite(0);
10102 }
10103 }
10104 Refresh(false);
10105 }
10106
10107 if (appending) {
10108 // copy tail from connect until length to end of current after
10109 // dragging
10110
10111 int length = tail->GetnPoints();
10112 for (int i = connect + 1; i <= length; i++) {
10113 current->AddPointAndSegment(tail->GetPoint(i), false);
10114 if (current)
10115 current->m_lastMousePointIndex = current->GetnPoints();
10116 m_routeState++;
10117 top_frame::Get()->RefreshAllCanvas();
10118 ret = true;
10119 }
10120 current->FinalizeForRendering();
10121 current->m_bIsBeingEdited = false;
10122 FinishRoute();
10123 g_pRouteMan->DeleteRoute(tail);
10124 }
10125 if (inserting) {
10126 pSelect->DeleteAllSelectableRoutePoints(current);
10127 pSelect->DeleteAllSelectableRouteSegments(current);
10128 for (int i = 1; i < connect; i++) { // numbering in the tail route
10129 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10130 }
10131 pSelect->AddAllSelectableRouteSegments(current);
10132 pSelect->AddAllSelectableRoutePoints(current);
10133 current->FinalizeForRendering();
10134 current->m_bIsBeingEdited = false;
10135 g_pRouteMan->DeleteRoute(tail);
10136 }
10137
10138 // Update the RouteProperties Dialog, if currently shown
10139 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10140 if (m_pEditRouteArray) {
10141 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10142 ir++) {
10143 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10144 if (g_pRouteMan->IsRouteValid(pr)) {
10145 if (pRoutePropDialog->GetRoute() == pr) {
10146 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10147 }
10148 }
10149 }
10150 }
10151 }
10152
10153 if (pMousePoint) {
10154 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10155 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10156 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10157 // Hide mark properties dialog if open on the replaced point
10158 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10159 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10160 g_pMarkInfoDialog->Hide();
10161
10162 delete m_pRoutePointEditTarget;
10163 m_lastRoutePointEditTarget = NULL;
10164 undo->AfterUndoableAction(pMousePoint);
10165 undo->InvalidateUndo();
10166 } else {
10167 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10168 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10169
10170 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10171 }
10172
10173 delete m_pEditRouteArray;
10174 m_pEditRouteArray = NULL;
10175 }
10176
10177 InvalidateGL();
10178 m_bRouteEditing = false;
10179 m_pRoutePointEditTarget = NULL;
10180
10181 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10182 ret = true;
10183 }
10184
10185 else if (m_bMarkEditing) { // end of Waypoint drag
10186 if (m_pRoutePointEditTarget) {
10187 if (m_bRoutePoinDragging) {
10188 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10189 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10190 }
10191 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10192 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10193 if (!g_bopengl) {
10194 wxRect wp_rect;
10195 RoutePointGui(*m_pRoutePointEditTarget)
10196 .CalculateDCRect(m_dc_route, this, &wp_rect);
10197 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10198 RefreshRect(wp_rect, true);
10199 }
10200 }
10201 m_pRoutePointEditTarget = NULL;
10202 m_bMarkEditing = false;
10203 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10204 ret = true;
10205 }
10206
10207 else if (leftIsDown) { // left click for chart center
10208 leftIsDown = false;
10209 ret = false;
10210
10211 if (!g_btouch) {
10212 if (!m_bChartDragging && !m_bMeasure_Active) {
10213 } else {
10214 m_bChartDragging = false;
10215 }
10216 }
10217 }
10218 m_bRoutePoinDragging = false;
10219 } // !btouch
10220
10221 if (ret) return true;
10222 } // left up
10223
10224 if (event.RightDown()) {
10225 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10226 last_drag.x = mx;
10227 last_drag.y = my;
10228
10229 if (g_btouch) {
10230 // if( m_pRoutePointEditTarget )
10231 // return false;
10232 }
10233
10234 ret = true;
10235 m_FinishRouteOnKillFocus = false;
10236 CallPopupMenu(mx, my);
10237 m_FinishRouteOnKillFocus = true;
10238 } // Right down
10239
10240 return ret;
10241}
10242
10243bool panleftIsDown;
10244bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10245 // Skip all mouse processing if shift is held.
10246 // This allows plugins to implement shift+drag behaviors.
10247 if (event.ShiftDown()) {
10248 return false;
10249 }
10250 int x, y;
10251 event.GetPosition(&x, &y);
10252
10253 x *= m_displayScale;
10254 y *= m_displayScale;
10255
10256 // Check for wheel rotation
10257 // ideally, should be just longer than the time between
10258 // processing accumulated mouse events from the event queue
10259 // as would happen during screen redraws.
10260 int wheel_dir = event.GetWheelRotation();
10261
10262 if (wheel_dir) {
10263 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10264 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10265
10266 double factor = g_mouse_zoom_sensitivity;
10267 if (wheel_dir < 0) factor = 1 / factor;
10268
10269 if (g_bsmoothpanzoom) {
10270 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10271 if (wheel_dir == m_last_wheel_dir) {
10272 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10273 // m_zoom_target /= factor;
10274 } else
10275 StopMovement();
10276 } else {
10277 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10278 m_wheelstopwatch.Start(0);
10279 // m_zoom_target = VPoint.chart_scale / factor;
10280 }
10281 }
10282
10283 m_last_wheel_dir = wheel_dir;
10284
10285 ZoomCanvas(factor, true, false);
10286 }
10287
10288 if (event.LeftDown()) {
10289 // Skip the first left click if it will cause a canvas focus shift
10290 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10291 return false;
10292 }
10293
10294 last_drag.x = x, last_drag.y = y;
10295 panleftIsDown = true;
10296 }
10297
10298 if (event.LeftUp()) {
10299 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10300 // seen here.
10301 panleftIsDown = false;
10302
10303 if (!g_btouch) {
10304 if (!m_bChartDragging && !m_bMeasure_Active) {
10305 switch (cursor_region) {
10306 case MID_RIGHT: {
10307 PanCanvas(100, 0);
10308 break;
10309 }
10310
10311 case MID_LEFT: {
10312 PanCanvas(-100, 0);
10313 break;
10314 }
10315
10316 case MID_TOP: {
10317 PanCanvas(0, 100);
10318 break;
10319 }
10320
10321 case MID_BOT: {
10322 PanCanvas(0, -100);
10323 break;
10324 }
10325
10326 case CENTER: {
10327 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10328 break;
10329 }
10330 }
10331 } else {
10332 m_bChartDragging = false;
10333 }
10334 }
10335 }
10336 }
10337
10338 if (event.Dragging() && event.LeftIsDown()) {
10339 /*
10340 * fixed dragging.
10341 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10342 * before the drag event. Hence, as there is no mouse down event, last_drag
10343 * is not reset before the drag. And that results in one single drag
10344 * session, meaning you cannot drag the map a few miles north, lift your
10345 * finger, and the go even further north. Instead, the map resets itself
10346 * always to the very first drag start (since there is not reset of
10347 * last_drag).
10348 *
10349 * Besides, should not left down and dragging be enough of a situation to
10350 * start a drag procedure?
10351 *
10352 * Anyways, guarded it to be active in touch situations only.
10353 */
10354 if (g_btouch && !m_inPinch) {
10355 struct timespec now;
10356 clock_gettime(CLOCK_MONOTONIC, &now);
10357 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10358
10359 bool trigger_hold = false;
10360 if (false == m_bChartDragging) {
10361 if (m_DragTrigger < 0) {
10362 // printf("\ntrigger1\n");
10363 m_DragTrigger = 0;
10364 m_DragTriggerStartTime = tnow;
10365 trigger_hold = true;
10366 } else {
10367 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10368 m_DragTrigger = -1; // Reset trigger
10369 // printf("trigger fired\n");
10370 }
10371 }
10372 }
10373 if (trigger_hold) return true;
10374
10375 if (false == m_bChartDragging) {
10376 // printf("starting drag\n");
10377 // Reset drag calculation members
10378 last_drag.x = x - 1, last_drag.y = y - 1;
10379 m_bChartDragging = true;
10380 m_chart_drag_total_time = 0;
10381 m_chart_drag_total_x = 0;
10382 m_chart_drag_total_y = 0;
10383 m_inertia_last_drag_x = x;
10384 m_inertia_last_drag_y = y;
10385 m_drag_vec_x.clear();
10386 m_drag_vec_y.clear();
10387 m_drag_vec_t.clear();
10388 m_last_drag_time = tnow;
10389 }
10390
10391 // Calculate and store drag dynamics.
10392 uint64_t delta_t = tnow - m_last_drag_time;
10393 double delta_tf = delta_t / 1e9;
10394
10395 m_chart_drag_total_time += delta_tf;
10396 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10397 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10398
10399 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10400 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10401 m_drag_vec_t.push_back(delta_tf);
10402
10403 m_inertia_last_drag_x = x;
10404 m_inertia_last_drag_y = y;
10405 m_last_drag_time = tnow;
10406
10407 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10408 m_bChartDragging = true;
10409 StartTimedMovement();
10410 m_pan_drag.x += last_drag.x - x;
10411 m_pan_drag.y += last_drag.y - y;
10412 last_drag.x = x, last_drag.y = y;
10413 }
10414 } else if (!g_btouch) {
10415 if ((last_drag.x != x) || (last_drag.y != y)) {
10416 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10417 // dragging on route create.
10418 // github #2994
10419 m_bChartDragging = true;
10420 StartTimedMovement();
10421 m_pan_drag.x += last_drag.x - x;
10422 m_pan_drag.y += last_drag.y - y;
10423 last_drag.x = x, last_drag.y = y;
10424 }
10425 }
10426 }
10427
10428 // Handle some special cases
10429 if (g_btouch) {
10430 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10431 // deactivate next LeftUp to ovoid creating an unexpected point
10432 m_ignore_next_leftup = true;
10433 m_DoubleClickTimer->Start();
10434 singleClickEventIsValid = false;
10435 }
10436 }
10437 }
10438
10439 return true;
10440}
10441
10442void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10443 if (MouseEventOverlayWindows(event)) return;
10444
10445 if (MouseEventSetup(event)) return; // handled, no further action required
10446
10447 bool nm = MouseEventProcessObjects(event);
10448 if (!nm) MouseEventProcessCanvas(event);
10449}
10450
10451void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10452 // Switch to the appropriate cursor on mouse movement
10453
10454 wxCursor *ptarget_cursor = pCursorArrow;
10455 if (!pPlugIn_Cursor) {
10456 ptarget_cursor = pCursorArrow;
10457 if ((!m_routeState) &&
10458 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10459 if (cursor_region == MID_RIGHT) {
10460 ptarget_cursor = pCursorRight;
10461 } else if (cursor_region == MID_LEFT) {
10462 ptarget_cursor = pCursorLeft;
10463 } else if (cursor_region == MID_TOP) {
10464 ptarget_cursor = pCursorDown;
10465 } else if (cursor_region == MID_BOT) {
10466 ptarget_cursor = pCursorUp;
10467 } else {
10468 ptarget_cursor = pCursorArrow;
10469 }
10470 } else if (m_bMeasure_Active ||
10471 m_routeState) // If Measure tool use Pencil Cursor
10472 ptarget_cursor = pCursorPencil;
10473 } else {
10474 ptarget_cursor = pPlugIn_Cursor;
10475 }
10476
10477 SetCursor(*ptarget_cursor);
10478}
10479
10480void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10481 SetCursor(*pCursorArrow);
10482}
10483
10484void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10485 ChartPlugInWrapper *target_plugin_chart = NULL;
10486 s57chart *Chs57 = NULL;
10487 wxFileName file;
10488 wxArrayString files;
10489
10490 ChartBase *target_chart = GetChartAtCursor();
10491 if (target_chart) {
10492 file.Assign(target_chart->GetFullPath());
10493 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10494 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10495 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10496 else
10497 Chs57 = dynamic_cast<s57chart *>(target_chart);
10498 } else { // target_chart = null, might be mbtiles
10499 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10500 unsigned int im = stackIndexArray.size();
10501 int scale = 2147483647; // max 32b integer
10502 if (VPoint.b_quilt && im > 0) {
10503 for (unsigned int is = 0; is < im; is++) {
10504 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10505 CHART_TYPE_MBTILES) {
10506 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10507 double lat, lon;
10508 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10509 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10510 .GetBBox()
10511 .Contains(lat, lon)) {
10512 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10513 scale) {
10514 scale =
10515 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10516 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10517 }
10518 }
10519 }
10520 }
10521 }
10522 }
10523
10524 std::vector<Ais8_001_22 *> area_notices;
10525
10526 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10527 float vp_scale = GetVPScale();
10528
10529 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10530 auto target_data = target.second;
10531 if (!target_data->area_notices.empty()) {
10532 for (auto &ani : target_data->area_notices) {
10533 Ais8_001_22 &area_notice = ani.second;
10534
10535 BoundingBox bbox;
10536
10537 for (Ais8_001_22_SubAreaList::iterator sa =
10538 area_notice.sub_areas.begin();
10539 sa != area_notice.sub_areas.end(); ++sa) {
10540 switch (sa->shape) {
10541 case AIS8_001_22_SHAPE_CIRCLE: {
10542 wxPoint target_point;
10543 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10544 bbox.Expand(target_point);
10545 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10546 break;
10547 }
10548 case AIS8_001_22_SHAPE_RECT: {
10549 wxPoint target_point;
10550 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10551 bbox.Expand(target_point);
10552 if (sa->e_dim_m > sa->n_dim_m)
10553 bbox.EnLarge(sa->e_dim_m * vp_scale);
10554 else
10555 bbox.EnLarge(sa->n_dim_m * vp_scale);
10556 break;
10557 }
10558 case AIS8_001_22_SHAPE_POLYGON:
10559 case AIS8_001_22_SHAPE_POLYLINE: {
10560 for (int i = 0; i < 4; ++i) {
10561 double lat = sa->latitude;
10562 double lon = sa->longitude;
10563 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10564 &lat, &lon);
10565 wxPoint target_point;
10566 GetCanvasPointPix(lat, lon, &target_point);
10567 bbox.Expand(target_point);
10568 }
10569 break;
10570 }
10571 case AIS8_001_22_SHAPE_SECTOR: {
10572 double lat1 = sa->latitude;
10573 double lon1 = sa->longitude;
10574 double lat, lon;
10575 wxPoint target_point;
10576 GetCanvasPointPix(lat1, lon1, &target_point);
10577 bbox.Expand(target_point);
10578 for (int i = 0; i < 18; ++i) {
10579 ll_gc_ll(
10580 lat1, lon1,
10581 sa->left_bound_deg +
10582 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10583 sa->radius_m / 1852.0, &lat, &lon);
10584 GetCanvasPointPix(lat, lon, &target_point);
10585 bbox.Expand(target_point);
10586 }
10587 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10588 &lat, &lon);
10589 GetCanvasPointPix(lat, lon, &target_point);
10590 bbox.Expand(target_point);
10591 break;
10592 }
10593 }
10594 }
10595
10596 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10597 area_notices.push_back(&area_notice);
10598 }
10599 }
10600 }
10601 }
10602 }
10603
10604 if (target_chart || !area_notices.empty() || file.HasName()) {
10605 // Go get the array of all objects at the cursor lat/lon
10606 int sel_rad_pix = 5;
10607 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10608
10609 // Make sure we always get the lights from an object, even if we are
10610 // currently not displaying lights on the chart.
10611
10612 SetCursor(wxCURSOR_WAIT);
10613 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10614 if (!lightsVis) SetShowENCLights(true);
10615 ;
10616
10617 ListOfObjRazRules *rule_list = NULL;
10618 ListOfPI_S57Obj *pi_rule_list = NULL;
10619 if (Chs57)
10620 rule_list =
10621 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10622 else if (target_plugin_chart)
10623 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10624 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10625
10626 ListOfObjRazRules *overlay_rule_list = NULL;
10627 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10628 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10629
10630 if (CHs57_Overlay) {
10631 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10632 zlat, zlon, SelectRadius, &GetVP());
10633 }
10634
10635 if (!lightsVis) SetShowENCLights(false);
10636
10637 wxString objText;
10638 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10639 wxString face = dFont->GetFaceName();
10640
10641 if (NULL == g_pObjectQueryDialog) {
10643 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10644 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10645 }
10646
10647 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10648 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10649
10650#ifdef __WXOSX__
10651 // Auto Adjustment for dark mode
10652 fg = g_pObjectQueryDialog->GetForegroundColour();
10653#endif
10654
10655 objText.Printf(
10656 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10657 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10658
10659#ifdef __WXOSX__
10660 int points = dFont->GetPointSize();
10661#else
10662 int points = dFont->GetPointSize() + 1;
10663#endif
10664
10665 int sizes[7];
10666 for (int i = -2; i < 5; i++) {
10667 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10668 }
10669 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10670
10671 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10672
10673 if (overlay_rule_list && CHs57_Overlay) {
10674 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10675 objText << "<hr noshade>";
10676 }
10677
10678 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10679 an != area_notices.end(); ++an) {
10680 objText << "<b>AIS Area Notice:</b> ";
10681 objText << ais8_001_22_notice_names[(*an)->notice_type];
10682 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10683 (*an)->sub_areas.begin();
10684 sa != (*an)->sub_areas.end(); ++sa)
10685 if (!sa->text.empty()) objText << sa->text;
10686 objText << "<br>expires: " << (*an)->expiry_time.Format();
10687 objText << "<hr noshade>";
10688 }
10689
10690 if (Chs57)
10691 objText << Chs57->CreateObjDescriptions(rule_list);
10692 else if (target_plugin_chart)
10693 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10694 pi_rule_list);
10695
10696 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10697
10698 // Add the additional info files
10699 wxString AddFiles, filenameOK;
10700 int filecount = 0;
10701 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10702 // plugin
10703
10704 AddFiles = wxString::Format(
10705 "<hr noshade><br><b>Additional info files attached to: </b> "
10706 "<font "
10707 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10708 "cellpadding=3>",
10709 file.GetFullName());
10710 file.Normalize();
10711 file.Assign(file.GetPath(), "");
10712 wxDir dir(file.GetFullPath());
10713 wxString filename;
10714 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10715 while (cont) {
10716 file.Assign(dir.GetNameWithSep().append(filename));
10717 wxString FormatString =
10718 "<td valign=top><font size=-2><a "
10719 "href=\"%s\">%s</a></font></td>";
10720 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10721 filenameOK = file.GetFullPath(); // remember last valid name
10722 // we are making a 3 columns table. New row only every third file
10723 if (3 * ((int)filecount / 3) == filecount)
10724 FormatString.Prepend("<tr>"); // new row
10725 else
10726 FormatString.Prepend(
10727 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10728 // spacer column
10729
10730 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10731 file.GetFullName());
10732 filecount++;
10733 }
10734 cont = dir.GetNext(&filename);
10735 }
10736 objText << AddFiles << "</table>";
10737 }
10738 objText << "</font>";
10739 objText << "</body></html>";
10740
10741 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10742 g_pObjectQueryDialog->SetHTMLPage(objText);
10743 g_pObjectQueryDialog->Show();
10744 }
10745 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10746 // generate an event to avoid double code
10747 wxHtmlLinkInfo hli(filenameOK);
10748 wxHtmlLinkEvent hle(1, hli);
10749 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10750 }
10751
10752 if (rule_list) rule_list->Clear();
10753 delete rule_list;
10754
10755 if (overlay_rule_list) overlay_rule_list->Clear();
10756 delete overlay_rule_list;
10757
10758 if (pi_rule_list) pi_rule_list->Clear();
10759 delete pi_rule_list;
10760
10761 SetCursor(wxCURSOR_ARROW);
10762 }
10763}
10764
10765void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10766 bool bNew = false;
10767 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10768 // Dialog
10769 g_pMarkInfoDialog = new MarkInfoDlg(this);
10770 bNew = true;
10771 }
10772
10773 if (1 /*g_bresponsive*/) {
10774 wxSize canvas_size = GetSize();
10775
10776 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10777 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10778
10779 g_pMarkInfoDialog->Layout();
10780
10781 wxPoint canvas_pos = GetPosition();
10782 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10783
10784 bool newFit = false;
10785 if (canvas_size.x < fitted_size.x) {
10786 fitted_size.x = canvas_size.x - 40;
10787 if (canvas_size.y < fitted_size.y)
10788 fitted_size.y -= 40; // scrollbar added
10789 }
10790 if (canvas_size.y < fitted_size.y) {
10791 fitted_size.y = canvas_size.y - 40;
10792 if (canvas_size.x < fitted_size.x)
10793 fitted_size.x -= 40; // scrollbar added
10794 }
10795
10796 if (newFit) {
10797 g_pMarkInfoDialog->SetSize(fitted_size);
10798 g_pMarkInfoDialog->Centre();
10799 }
10800 }
10801
10802 markPoint->m_bRPIsBeingEdited = false;
10803
10804 wxString title_base = _("Mark Properties");
10805 if (markPoint->m_bIsInRoute) {
10806 title_base = _("Waypoint Properties");
10807 }
10808 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10809 g_pMarkInfoDialog->UpdateProperties();
10810 if (markPoint->m_bIsInLayer) {
10811 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10812 GetLayerName(markPoint->m_LayerID)));
10813 g_pMarkInfoDialog->SetDialogTitle(caption);
10814 } else
10815 g_pMarkInfoDialog->SetDialogTitle(title_base);
10816
10817 g_pMarkInfoDialog->Show();
10818 g_pMarkInfoDialog->Raise();
10819 g_pMarkInfoDialog->InitialFocus();
10820 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10821}
10822
10823void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10824 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10825 pRoutePropDialog->SetRouteAndUpdate(selected);
10826 // pNew->UpdateProperties();
10827 pRoutePropDialog->Show();
10828 pRoutePropDialog->Raise();
10829 return;
10830 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10831 this); // There is one global instance of the RouteProp Dialog
10832
10833 if (g_bresponsive) {
10834 wxSize canvas_size = GetSize();
10835 wxPoint canvas_pos = GetPosition();
10836 wxSize fitted_size = pRoutePropDialog->GetSize();
10837 ;
10838
10839 if (canvas_size.x < fitted_size.x) {
10840 fitted_size.x = canvas_size.x;
10841 if (canvas_size.y < fitted_size.y)
10842 fitted_size.y -= 20; // scrollbar added
10843 }
10844 if (canvas_size.y < fitted_size.y) {
10845 fitted_size.y = canvas_size.y;
10846 if (canvas_size.x < fitted_size.x)
10847 fitted_size.x -= 20; // scrollbar added
10848 }
10849
10850 pRoutePropDialog->SetSize(fitted_size);
10851 pRoutePropDialog->Centre();
10852
10853 // int xp = (canvas_size.x - fitted_size.x)/2;
10854 // int yp = (canvas_size.y - fitted_size.y)/2;
10855
10856 wxPoint xxp = ClientToScreen(canvas_pos);
10857 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10858 }
10859
10860 pRoutePropDialog->SetRouteAndUpdate(selected);
10861
10862 pRoutePropDialog->Show();
10863
10864 Refresh(false);
10865}
10866
10867void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10868 pTrackPropDialog = TrackPropDlg::getInstance(
10869 this); // There is one global instance of the RouteProp Dialog
10870
10871 pTrackPropDialog->SetTrackAndUpdate(selected);
10873
10874 pTrackPropDialog->Show();
10875
10876 Refresh(false);
10877}
10878
10879void pupHandler_PasteWaypoint() {
10880 Kml kml;
10881
10882 int pasteBuffer = kml.ParsePasteBuffer();
10883 RoutePoint *pasted = kml.GetParsedRoutePoint();
10884 if (!pasted) return;
10885
10886 double nearby_radius_meters =
10887 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10888
10889 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10890 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10891
10892 int answer = wxID_NO;
10893 if (nearPoint && !nearPoint->m_bIsInLayer) {
10894 wxString msg;
10895 msg << _(
10896 "There is an existing waypoint at the same location as the one you are "
10897 "pasting. Would you like to merge the pasted data with it?\n\n");
10898 msg << _("Answering 'No' will create a new waypoint at the same location.");
10899 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10900 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10901 }
10902
10903 if (answer == wxID_YES) {
10904 nearPoint->SetName(pasted->GetName());
10905 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10906 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10907 pRouteManagerDialog->UpdateWptListCtrl();
10908 }
10909
10910 if (answer == wxID_NO) {
10911 RoutePoint *newPoint = new RoutePoint(pasted);
10912 newPoint->m_bIsolatedMark = true;
10913 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10914 newPoint);
10915 // pConfig->AddNewWayPoint(newPoint, -1);
10916 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10917
10918 pWayPointMan->AddRoutePoint(newPoint);
10919 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10920 pRouteManagerDialog->UpdateWptListCtrl();
10921 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10922 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10923 }
10924
10925 top_frame::Get()->InvalidateAllGL();
10926 top_frame::Get()->RefreshAllCanvas(false);
10927}
10928
10929void pupHandler_PasteRoute() {
10930 Kml kml;
10931
10932 int pasteBuffer = kml.ParsePasteBuffer();
10933 Route *pasted = kml.GetParsedRoute();
10934 if (!pasted) return;
10935
10936 double nearby_radius_meters =
10937 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10938
10939 RoutePoint *curPoint;
10940 RoutePoint *nearPoint;
10941 RoutePoint *prevPoint = NULL;
10942
10943 bool mergepoints = false;
10944 bool createNewRoute = true;
10945 int existingWaypointCounter = 0;
10946
10947 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10948 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10949 nearPoint = pWayPointMan->GetNearbyWaypoint(
10950 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10951 if (nearPoint) {
10952 mergepoints = true;
10953 existingWaypointCounter++;
10954 // Small hack here to avoid both extending RoutePoint and repeating all
10955 // the GetNearbyWaypoint calculations. Use existin data field in
10956 // RoutePoint as temporary storage.
10957 curPoint->m_bPtIsSelected = true;
10958 }
10959 }
10960
10961 int answer = wxID_NO;
10962 if (mergepoints) {
10963 wxString msg;
10964 msg << _(
10965 "There are existing waypoints at the same location as some of the ones "
10966 "you are pasting. Would you like to just merge the pasted data into "
10967 "them?\n\n");
10968 msg << _("Answering 'No' will create all new waypoints for this route.");
10969 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10970 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10971
10972 if (answer == wxID_CANCEL) {
10973 return;
10974 }
10975 }
10976
10977 // If all waypoints exist since before, and a route with the same name, we
10978 // don't create a new route.
10979 if (mergepoints && answer == wxID_YES &&
10980 existingWaypointCounter == pasted->GetnPoints()) {
10981 for (Route *proute : *pRouteList) {
10982 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10983 createNewRoute = false;
10984 break;
10985 }
10986 }
10987 }
10988
10989 Route *newRoute = 0;
10990 RoutePoint *newPoint = 0;
10991
10992 if (createNewRoute) {
10993 newRoute = new Route();
10994 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10995 }
10996
10997 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10998 curPoint = pasted->GetPoint(i);
10999 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11000 curPoint->m_bPtIsSelected = false;
11001 newPoint = pWayPointMan->GetNearbyWaypoint(
11002 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11003 newPoint->SetName(curPoint->GetName());
11004 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11005
11006 if (createNewRoute) newRoute->AddPoint(newPoint);
11007 } else {
11008 curPoint->m_bPtIsSelected = false;
11009
11010 newPoint = new RoutePoint(curPoint);
11011 newPoint->m_bIsolatedMark = false;
11012 newPoint->SetIconName("circle");
11013 newPoint->m_bIsVisible = true;
11014 newPoint->m_bShowName = false;
11015 newPoint->SetShared(false);
11016
11017 newRoute->AddPoint(newPoint);
11018 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11019 newPoint);
11020 // pConfig->AddNewWayPoint(newPoint, -1);
11021 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11022 pWayPointMan->AddRoutePoint(newPoint);
11023 }
11024 if (i > 1 && createNewRoute)
11025 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11026 curPoint->m_lat, curPoint->m_lon,
11027 prevPoint, newPoint, newRoute);
11028 prevPoint = newPoint;
11029 }
11030
11031 if (createNewRoute) {
11032 pRouteList->push_back(newRoute);
11033 // pConfig->AddNewRoute(newRoute); // use auto next num
11034 NavObj_dB::GetInstance().InsertRoute(newRoute);
11035
11036 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11037 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11038 }
11039
11040 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11041 pRouteManagerDialog->UpdateRouteListCtrl();
11042 pRouteManagerDialog->UpdateWptListCtrl();
11043 }
11044 top_frame::Get()->InvalidateAllGL();
11045 top_frame::Get()->RefreshAllCanvas(false);
11046 }
11047 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11048 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11049}
11050
11051void pupHandler_PasteTrack() {
11052 Kml kml;
11053
11054 int pasteBuffer = kml.ParsePasteBuffer();
11055 Track *pasted = kml.GetParsedTrack();
11056 if (!pasted) return;
11057
11058 TrackPoint *curPoint;
11059
11060 Track *newTrack = new Track();
11061 TrackPoint *newPoint;
11062 TrackPoint *prevPoint = NULL;
11063
11064 newTrack->SetName(pasted->GetName());
11065
11066 for (int i = 0; i < pasted->GetnPoints(); i++) {
11067 curPoint = pasted->GetPoint(i);
11068
11069 newPoint = new TrackPoint(curPoint);
11070
11071 wxDateTime now = wxDateTime::Now();
11072 newPoint->SetCreateTime(curPoint->GetCreateTime());
11073
11074 newTrack->AddPoint(newPoint);
11075
11076 if (prevPoint)
11077 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11078 newPoint->m_lat, newPoint->m_lon,
11079 prevPoint, newPoint, newTrack);
11080
11081 prevPoint = newPoint;
11082 }
11083
11084 g_TrackList.push_back(newTrack);
11085 // pConfig->AddNewTrack(newTrack);
11086 NavObj_dB::GetInstance().InsertTrack(newTrack);
11087
11088 top_frame::Get()->InvalidateAllGL();
11089 top_frame::Get()->RefreshAllCanvas(false);
11090}
11091
11092bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11093 wxJSONValue v;
11094 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11095 v["CursorPosition_x"] = x;
11096 v["CursorPosition_y"] = y;
11097 // Send a limited set of selection types depending on what is
11098 // found under the mouse point.
11099 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11100 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11101 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11102
11103 wxJSONWriter w;
11104 wxString out;
11105 w.Write(v, out);
11106 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11107
11108 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11109
11110#if 0
11111#define SELTYPE_UNKNOWN 0x0001
11112#define SELTYPE_ROUTEPOINT 0x0002
11113#define SELTYPE_ROUTESEGMENT 0x0004
11114#define SELTYPE_TIDEPOINT 0x0008
11115#define SELTYPE_CURRENTPOINT 0x0010
11116#define SELTYPE_ROUTECREATE 0x0020
11117#define SELTYPE_AISTARGET 0x0040
11118#define SELTYPE_MARKPOINT 0x0080
11119#define SELTYPE_TRACKSEGMENT 0x0100
11120#define SELTYPE_DRAGHANDLE 0x0200
11121#endif
11122
11123 if (g_bhide_context_menus) return true;
11124 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11125 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11126 m_pIDXCandidate, m_nmea_log);
11127
11128 Connect(
11129 wxEVT_COMMAND_MENU_SELECTED,
11130 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11131
11132#ifdef __WXGTK__
11133 // Funny requirement here for gtk, to clear the menu trigger event
11134 // TODO
11135 // Causes a slight "flasH" of the menu,
11136 if (m_inLongPress) {
11137 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11138 m_inLongPress = false;
11139 }
11140#endif
11141
11142 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11143
11144 Disconnect(
11145 wxEVT_COMMAND_MENU_SELECTED,
11146 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11147
11148 delete m_canvasMenu;
11149 m_canvasMenu = NULL;
11150
11151#ifdef __WXQT__
11152 // gFrame->SurfaceToolbar();
11153 // g_MainToolbar->Raise();
11154#endif
11155
11156 return true;
11157}
11158
11159void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11160 // Pass menu events from the canvas to the menu handler
11161 // This is necessarily in ChartCanvas since that is the menu's parent.
11162 if (m_canvasMenu) {
11163 m_canvasMenu->PopupMenuHandler(event);
11164 }
11165 return;
11166}
11167
11168void ChartCanvas::StartRoute() {
11169 // Do not allow more than one canvas to create a route at one time.
11170 if (g_brouteCreating) return;
11171
11172 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11173
11174 g_brouteCreating = true;
11175 m_routeState = 1;
11176 m_bDrawingRoute = false;
11177 SetCursor(*pCursorPencil);
11178 // SetCanvasToolbarItemState(ID_ROUTE, true);
11179 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11180
11181 HideGlobalToolbar();
11182
11183#ifdef __ANDROID__
11184 androidSetRouteAnnunciator(true);
11185#endif
11186}
11187
11188wxString ChartCanvas::FinishRoute() {
11189 m_routeState = 0;
11190 m_prev_pMousePoint = NULL;
11191 m_bDrawingRoute = false;
11192 wxString rv = "";
11193 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11194
11195 // SetCanvasToolbarItemState(ID_ROUTE, false);
11196 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11197#ifdef __ANDROID__
11198 androidSetRouteAnnunciator(false);
11199#endif
11200
11201 SetCursor(*pCursorArrow);
11202
11203 if (m_pMouseRoute) {
11204 if (m_bAppendingRoute) {
11205 // pConfig->UpdateRoute(m_pMouseRoute);
11206 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11207 } else {
11208 if (m_pMouseRoute->GetnPoints() > 1) {
11209 // pConfig->AddNewRoute(m_pMouseRoute);
11210 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11211 } else {
11212 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11213 m_pMouseRoute = NULL;
11214 }
11215 }
11216 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11217
11218 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11219 (pRoutePropDialog->IsShown())) {
11220 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11221 }
11222
11223 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11224 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11225 pRouteManagerDialog->UpdateRouteListCtrl();
11226 }
11227 }
11228 m_bAppendingRoute = false;
11229 m_pMouseRoute = NULL;
11230
11231 m_pSelectedRoute = NULL;
11232
11233 undo->InvalidateUndo();
11234 top_frame::Get()->RefreshAllCanvas(true);
11235
11236 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11237
11238 ShowGlobalToolbar();
11239
11240 g_brouteCreating = false;
11241
11242 return rv;
11243}
11244
11245void ChartCanvas::HideGlobalToolbar() {
11246 if (m_canvasIndex == 0) {
11247 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11248 }
11249}
11250
11251void ChartCanvas::ShowGlobalToolbar() {
11252 if (m_canvasIndex == 0) {
11253 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11254 }
11255}
11256
11257void ChartCanvas::ShowAISTargetList() {
11258 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11259 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11260 }
11261
11262 g_pAISTargetList->UpdateAISTargetList();
11263}
11264
11265void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11266 if (!m_bShowOutlines) return;
11267
11268 if (!ChartData) return;
11269
11270 int nEntry = ChartData->GetChartTableEntries();
11271
11272 for (int i = 0; i < nEntry; i++) {
11273 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11274
11275 // Check to see if the candidate chart is in the currently active group
11276 bool b_group_draw = false;
11277 if (m_groupIndex > 0) {
11278 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11279 int index = pt->GetGroupArray()[ig];
11280 if (m_groupIndex == index) {
11281 b_group_draw = true;
11282 break;
11283 }
11284 }
11285 } else
11286 b_group_draw = true;
11287
11288 if (b_group_draw) RenderChartOutline(dc, i, vp);
11289 }
11290
11291 // On CM93 Composite Charts, draw the outlines of the next smaller
11292 // scale cell
11293 cm93compchart *pcm93 = NULL;
11294 if (VPoint.b_quilt) {
11295 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11296 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11297 pcm93 = (cm93compchart *)pch;
11298 break;
11299 }
11300 } else if (m_singleChart &&
11301 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11302 pcm93 = (cm93compchart *)m_singleChart;
11303
11304 if (pcm93) {
11305 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11306 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11307
11308 if (zoom_factor > 8.0) {
11309 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11310 dc.SetPen(mPen);
11311 } else {
11312 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11313 dc.SetPen(mPen);
11314 }
11315
11316 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11317 }
11318}
11319
11320void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11321#ifdef ocpnUSE_GL
11322 if (g_bopengl && m_glcc) {
11323 /* opengl version specially optimized */
11324 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11325 return;
11326 }
11327#endif
11328
11329 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11330 if (!ChartData->IsChartAvailable(dbIndex)) return;
11331 }
11332
11333 float plylat, plylon;
11334 float plylat1, plylon1;
11335
11336 int pixx, pixy, pixx1, pixy1;
11337
11338 LLBBox box;
11339 ChartData->GetDBBoundingBox(dbIndex, box);
11340
11341 // Don't draw an outline in the case where the chart covers the entire world
11342 // */
11343 if (box.GetLonRange() == 360) return;
11344
11345 double lon_bias = 0;
11346 // chart is outside of viewport lat/lon bounding box
11347 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11348
11349 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11350
11351 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11352 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11353
11354 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11355 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11356
11357 else
11358 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11359
11360 // Are there any aux ply entries?
11361 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11362 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11363 {
11364 wxPoint r, r1;
11365
11366 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11367 plylon += lon_bias;
11368
11369 GetCanvasPointPix(plylat, plylon, &r);
11370 pixx = r.x;
11371 pixy = r.y;
11372
11373 for (int i = 0; i < nPly - 1; i++) {
11374 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11375 plylon1 += lon_bias;
11376
11377 GetCanvasPointPix(plylat1, plylon1, &r1);
11378 pixx1 = r1.x;
11379 pixy1 = r1.y;
11380
11381 int pixxs1 = pixx1;
11382 int pixys1 = pixy1;
11383
11384 bool b_skip = false;
11385
11386 if (vp.chart_scale > 5e7) {
11387 // calculate projected distance between these two points in meters
11388 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11389 pow((double)(pixy1 - pixy), 2)) /
11390 vp.view_scale_ppm;
11391
11392 if (dist > 0.0) {
11393 // calculate GC distance between these two points in meters
11394 double distgc =
11395 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11396
11397 // If the distances are nonsense, it means that the scale is very
11398 // small and the segment wrapped the world So skip it....
11399 // TODO improve this to draw two segments
11400 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11401 b_skip = true;
11402 } else
11403 b_skip = true;
11404 }
11405
11406 ClipResult res = cohen_sutherland_line_clip_i(
11407 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11408 if (res != Invisible && !b_skip)
11409 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11410
11411 plylat = plylat1;
11412 plylon = plylon1;
11413 pixx = pixxs1;
11414 pixy = pixys1;
11415 }
11416
11417 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11418 plylon1 += lon_bias;
11419
11420 GetCanvasPointPix(plylat1, plylon1, &r1);
11421 pixx1 = r1.x;
11422 pixy1 = r1.y;
11423
11424 ClipResult res = cohen_sutherland_line_clip_i(
11425 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11426 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11427 }
11428
11429 else // Use Aux PlyPoints
11430 {
11431 wxPoint r, r1;
11432
11433 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11434 for (int j = 0; j < nAuxPlyEntries; j++) {
11435 int nAuxPly =
11436 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11437 GetCanvasPointPix(plylat, plylon, &r);
11438 pixx = r.x;
11439 pixy = r.y;
11440
11441 for (int i = 0; i < nAuxPly - 1; i++) {
11442 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11443
11444 GetCanvasPointPix(plylat1, plylon1, &r1);
11445 pixx1 = r1.x;
11446 pixy1 = r1.y;
11447
11448 int pixxs1 = pixx1;
11449 int pixys1 = pixy1;
11450
11451 bool b_skip = false;
11452
11453 if (vp.chart_scale > 5e7) {
11454 // calculate projected distance between these two points in meters
11455 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11456 ((pixy1 - pixy) * (pixy1 - pixy))) /
11457 vp.view_scale_ppm;
11458 if (dist > 0.0) {
11459 // calculate GC distance between these two points in meters
11460 double distgc =
11461 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11462
11463 // If the distances are nonsense, it means that the scale is very
11464 // small and the segment wrapped the world So skip it....
11465 // TODO improve this to draw two segments
11466 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11467 b_skip = true;
11468 } else
11469 b_skip = true;
11470 }
11471
11472 ClipResult res = cohen_sutherland_line_clip_i(
11473 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11474 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11475
11476 plylat = plylat1;
11477 plylon = plylon1;
11478 pixx = pixxs1;
11479 pixy = pixys1;
11480 }
11481
11482 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11483 GetCanvasPointPix(plylat1, plylon1, &r1);
11484 pixx1 = r1.x;
11485 pixy1 = r1.y;
11486
11487 ClipResult res = cohen_sutherland_line_clip_i(
11488 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11489 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11490 }
11491 }
11492}
11493
11494static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11495 const wxArrayString &legend) {
11496 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11497
11498 int pointsize = dFont->GetPointSize();
11499 pointsize /= OCPN_GetWinDIPScaleFactor();
11500
11501 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11502 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11503 false, dFont->GetFaceName());
11504
11505 dc.SetFont(*psRLI_font);
11506
11507 int h = 0;
11508 int w = 0;
11509 int hl, wl;
11510
11511 int xp, yp;
11512 int hilite_offset = 3;
11513
11514 for (wxString line : legend) {
11515#ifdef __WXMAC__
11516 wxScreenDC sdc;
11517 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11518#else
11519 dc.GetTextExtent(line, &wl, &hl);
11520 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11521 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11522#endif
11523 h += hl;
11524 w = wxMax(w, wl);
11525 }
11526 w += (hl / 2); // Add a little right pad
11527
11528 xp = ref_point.x - w;
11529 yp = ref_point.y;
11530 yp += hilite_offset;
11531
11532 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11533
11534 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11535 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11536
11537 for (wxString line : legend) {
11538 dc.DrawText(line, xp, yp);
11539 yp += hl;
11540 }
11541}
11542
11543void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11544 if (!g_bAllowShipToActive) return;
11545
11546 Route *rt = g_pRouteMan->GetpActiveRoute();
11547 if (!rt) return;
11548
11549 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11550 wxPoint2DDouble pa, pb;
11552 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11553
11554 // set pen
11555 int width =
11556 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11557 if (rt->m_width != wxPENSTYLE_INVALID)
11558 width = rt->m_width; // set route pen style if any
11559 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11560 g_shipToActiveStyle, 5)]; // get setting pen style
11561 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11562 wxColour color =
11563 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11564 : // set setting route pen color
11565 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11566 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11567
11568 dc.SetPen(*mypen);
11569 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11570
11571 if (!Use_Opengl)
11572 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11573 (int)pb.m_y, GetVP(), true);
11574
11575#ifdef ocpnUSE_GL
11576 else {
11577#ifdef USE_ANDROID_GLES2
11578 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11579#else
11580 if (style != wxPENSTYLE_SOLID) {
11581 if (glChartCanvas::dash_map.find(style) !=
11582 glChartCanvas::dash_map.end()) {
11583 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11584 dc.SetPen(*mypen);
11585 }
11586 }
11587 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11588#endif
11589
11590 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11591 (int)pb.m_x, (int)pb.m_y, GetVP());
11592 }
11593#endif
11594 }
11595}
11596
11597void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11598 Route *route = 0;
11599 if (m_routeState >= 2) route = m_pMouseRoute;
11600 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11601 route = m_pMeasureRoute;
11602
11603 if (!route) return;
11604
11605 // Validate route pointer
11606 if (!g_pRouteMan->IsRouteValid(route)) return;
11607
11608 double render_lat = m_cursor_lat;
11609 double render_lon = m_cursor_lon;
11610
11611 int np = route->GetnPoints();
11612 if (np) {
11613 if (g_btouch && (np > 1)) np--;
11614 RoutePoint rp = route->GetPoint(np);
11615 render_lat = rp.m_lat;
11616 render_lon = rp.m_lon;
11617 }
11618
11619 double rhumbBearing, rhumbDist;
11620 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11621 &rhumbBearing, &rhumbDist);
11622 double brg = rhumbBearing;
11623 double dist = rhumbDist;
11624
11625 // Skip GreatCircle rubberbanding on touch devices.
11626 if (!g_btouch) {
11627 double gcBearing, gcBearing2, gcDist;
11628 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11629 m_cursor_lat, &gcDist, &gcBearing,
11630 &gcBearing2);
11631 double gcDistm = gcDist / 1852.0;
11632
11633 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11634 rhumbBearing = 90.;
11635
11636 wxPoint destPoint, lastPoint;
11637
11638 route->m_NextLegGreatCircle = false;
11639 int milesDiff = rhumbDist - gcDistm;
11640 if (milesDiff > 1) {
11641 brg = gcBearing;
11642 dist = gcDistm;
11643 route->m_NextLegGreatCircle = true;
11644 }
11645
11646 // FIXME (MacOS, the first segment is rendered wrong)
11647 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11648 &lastPoint);
11649
11650 if (route->m_NextLegGreatCircle) {
11651 for (int i = 1; i <= milesDiff; i++) {
11652 double p = (double)i * (1.0 / (double)milesDiff);
11653 double pLat, pLon;
11654 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11655 &pLon, &pLat, &gcBearing2);
11656 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11657 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11658 false);
11659 lastPoint = destPoint;
11660 }
11661 } else {
11662 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11663 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11664 false);
11665 if (m_bMeasure_DistCircle) {
11666 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11667 powf((float)(r_rband.y - lastPoint.y), 2));
11668
11669 dc.SetPen(*g_pRouteMan->GetRoutePen());
11670 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11671 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11672 }
11673 }
11674 }
11675 }
11676
11677 wxString routeInfo;
11678 wxArrayString infoArray;
11679 double varBrg = 0;
11680 if (g_bShowTrue)
11681 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11682 0x00B0);
11683
11684 if (g_bShowMag) {
11685 double latAverage = (m_cursor_lat + render_lat) / 2;
11686 double lonAverage = (m_cursor_lon + render_lon) / 2;
11687 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11688
11689 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11690 (int)varBrg, 0x00B0);
11691 }
11692 routeInfo << " " << FormatDistanceAdaptive(dist);
11693 infoArray.Add(routeInfo);
11694 routeInfo.Clear();
11695
11696 // To make it easier to use a route as a bearing on a charted object add for
11697 // the first leg also the reverse bearing.
11698 if (np == 1) {
11699 routeInfo << "Reverse: ";
11700 if (g_bShowTrue)
11701 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11702 (int)(brg + 180.) % 360, 0x00B0);
11703 if (g_bShowMag)
11704 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11705 (int)(varBrg + 180.) % 360, 0x00B0);
11706 infoArray.Add(routeInfo);
11707 routeInfo.Clear();
11708 }
11709
11710 wxString s0;
11711 if (!route->m_bIsInLayer)
11712 s0.Append(_("Route") + ": ");
11713 else
11714 s0.Append(_("Layer Route: "));
11715
11716 double disp_length = route->m_route_length;
11717 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11718 s0 += FormatDistanceAdaptive(disp_length);
11719
11720 infoArray.Add(s0);
11721 routeInfo.Clear();
11722
11723 RouteLegInfo(dc, r_rband, infoArray);
11724
11725 m_brepaint_piano = true;
11726}
11727
11728void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11729 if (!m_bShowVisibleSectors) return;
11730
11731 if (g_bDeferredInitDone) {
11732 // need to re-evaluate sectors?
11733 double rhumbBearing, rhumbDist;
11734 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11735 &rhumbBearing, &rhumbDist);
11736
11737 if (rhumbDist > 0.05) // miles
11738 {
11739 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11740 m_sectorlegsVisible);
11741 m_sector_glat = gLat;
11742 m_sector_glon = gLon;
11743 }
11744 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11745 }
11746}
11747
11748void ChartCanvas::WarpPointerDeferred(int x, int y) {
11749 warp_x = x;
11750 warp_y = y;
11751 warp_flag = true;
11752}
11753
11754int s_msg;
11755
11756void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11757 if (!ps52plib) return;
11758
11759 if (VPoint.b_quilt) { // quilted
11760 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11761
11762 if (m_pQuilt->IsQuiltVector()) {
11763 if (ps52plib->GetStateHash() != m_s52StateHash) {
11764 UpdateS52State();
11765 m_s52StateHash = ps52plib->GetStateHash();
11766 }
11767 }
11768 } else {
11769 if (ps52plib->GetStateHash() != m_s52StateHash) {
11770 UpdateS52State();
11771 m_s52StateHash = ps52plib->GetStateHash();
11772 }
11773 }
11774
11775 // Plugin charts
11776 bool bSendPlibState = true;
11777 if (VPoint.b_quilt) { // quilted
11778 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11779 }
11780
11781 if (bSendPlibState) {
11782 wxJSONValue v;
11783 v["OpenCPN Version Major"] = VERSION_MAJOR;
11784 v["OpenCPN Version Minor"] = VERSION_MINOR;
11785 v["OpenCPN Version Patch"] = VERSION_PATCH;
11786 v["OpenCPN Version Date"] = VERSION_DATE;
11787 v["OpenCPN Version Full"] = VERSION_FULL;
11788
11789 // S52PLIB state
11790 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11791 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11792 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11793 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11794 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11795 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11796 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11797
11798 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11799
11800 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11801 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11802
11803 // Global S52 options
11804
11805 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11806 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11807 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11808 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11809 ps52plib->m_bShowS57ImportantTextOnly;
11810 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11811 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11812 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11813 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11814 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11815
11816 // Some global GUI parameters, for completeness
11817 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11818 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11819 v["OpenCPN Scale Factor Exp"] =
11820 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11821 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11822
11823 wxJSONWriter w;
11824 wxString out;
11825 w.Write(v, out);
11826
11827 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11828 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11829 g_lastS52PLIBPluginMessage = out;
11830 }
11831 }
11832}
11833int spaint;
11834int s_in_update;
11835void ChartCanvas::OnPaint(wxPaintEvent &event) {
11836 wxPaintDC dc(this);
11837
11838 // GetToolbar()->Show( m_bToolbarEnable );
11839
11840 // Paint updates may have been externally disabled (temporarily, to avoid
11841 // Yield() recursion performance loss) It is important that the wxPaintDC is
11842 // built, even if we elect to not process this paint message. Otherwise, the
11843 // paint message may not be removed from the message queue, esp on Windows.
11844 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11845
11846 if (!m_b_paint_enable) {
11847 return;
11848 }
11849
11850 // If necessary, reconfigure the S52 PLIB
11852
11853#ifdef ocpnUSE_GL
11854 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11855
11856 if (m_glcc && g_bopengl) {
11857 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11858 s_in_update++;
11859 m_glcc->Update();
11860 s_in_update--;
11861 }
11862
11863 return;
11864 }
11865#endif
11866
11867 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11868
11869 wxRegion ru = GetUpdateRegion();
11870
11871 int rx, ry, rwidth, rheight;
11872 ru.GetBox(rx, ry, rwidth, rheight);
11873
11874#ifdef ocpnUSE_DIBSECTION
11875 ocpnMemDC temp_dc;
11876#else
11877 wxMemoryDC temp_dc;
11878#endif
11879
11880 long height = GetVP().pix_height;
11881
11882#ifdef __WXMAC__
11883 // On OS X we have to explicitly extend the region for the piano area
11884 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11885 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11886 height += m_Piano->GetHeight();
11887#endif // __WXMAC__
11888 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11889
11890 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11891 if (pthumbwin) {
11892 int thumbx, thumby, thumbsx, thumbsy;
11893 pthumbwin->GetPosition(&thumbx, &thumby);
11894 pthumbwin->GetSize(&thumbsx, &thumbsy);
11895 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11896
11897 if (pthumbwin->IsShown()) {
11898 rgn_chart.Subtract(rgn_thumbwin);
11899 ru.Subtract(rgn_thumbwin);
11900 }
11901 }
11902
11903 // subtract the chart bar if it isn't transparent, and determine if we need to
11904 // paint it
11905 wxRegion rgn_blit = ru;
11906 if (g_bShowChartBar) {
11907 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11908 GetClientSize().x, m_Piano->GetHeight());
11909
11910 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11911 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11912 if (style->chartStatusWindowTransparent)
11913 m_brepaint_piano = true;
11914 else
11915 ru.Subtract(chart_bar_rect);
11916 }
11917 }
11918
11919 if (m_Compass && m_Compass->IsShown()) {
11920 wxRect compassRect = m_Compass->GetRect();
11921 if (ru.Contains(compassRect) != wxOutRegion) {
11922 ru.Subtract(compassRect);
11923 }
11924 }
11925
11926 if (m_notification_button) {
11927 wxRect noteRect = m_notification_button->GetRect();
11928 if (ru.Contains(noteRect) != wxOutRegion) {
11929 ru.Subtract(noteRect);
11930 }
11931 }
11932
11933 // Is this viewpoint the same as the previously painted one?
11934 bool b_newview = true;
11935
11936 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11937 (m_cache_vp.rotation == VPoint.rotation) &&
11938 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11939 m_cache_vp.IsValid()) {
11940 b_newview = false;
11941 }
11942
11943 // If the ViewPort is skewed or rotated, we may be able to use the cached
11944 // rotated bitmap.
11945 bool b_rcache_ok = false;
11946 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11947 b_rcache_ok = !b_newview;
11948
11949 // Make a special VP
11950 if (VPoint.b_MercatorProjectionOverride)
11951 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11952 ViewPort svp = VPoint;
11953
11954 svp.pix_width = svp.rv_rect.width;
11955 svp.pix_height = svp.rv_rect.height;
11956
11957 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11958 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11959 // VPoint.rv_rect.height);
11960
11961 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11962
11963 // If we are going to use the cached rotated image, there is no need to fetch
11964 // any chart data and this will do it...
11965 if (b_rcache_ok) chart_get_region.Clear();
11966
11967 // Blit pan acceleration
11968 if (VPoint.b_quilt) // quilted
11969 {
11970 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11971
11972 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11973
11974 bool busy = false;
11975 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11976 m_cache_vp.rotation != VPoint.rotation)) {
11977 AbstractPlatform::ShowBusySpinner();
11978 busy = true;
11979 }
11980
11981 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11982 (m_working_bm.GetHeight() != svp.pix_height))
11983 m_working_bm.Create(svp.pix_width, svp.pix_height,
11984 -1); // make sure the target is big enoug
11985
11986 if (fabs(VPoint.rotation) < 0.01) {
11987 bool b_save = true;
11988
11989 if (g_SencThreadManager) {
11990 if (g_SencThreadManager->GetJobCount()) {
11991 b_save = false;
11992 m_cache_vp.Invalidate();
11993 }
11994 }
11995
11996 // If the saved wxBitmap from last OnPaint is useable
11997 // calculate the blit parameters
11998
11999 // We can only do screen blit painting if subsequent ViewPorts differ by
12000 // whole pixels So, in small scale bFollow mode, force the full screen
12001 // render. This seems a hack....There may be better logic here.....
12002
12003 // if(m_bFollow)
12004 // b_save = false;
12005
12006 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12007 if (b_newview) {
12008 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12009 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12010
12011 int dy = c_new.y - c_old.y;
12012 int dx = c_new.x - c_old.x;
12013
12014 // printf("In OnPaint Trying Blit dx: %d
12015 // dy:%d\n\n", dx, dy);
12016
12017 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12018 if (dx || dy) {
12019 // Blit the reuseable portion of the cached wxBitmap to a working
12020 // bitmap
12021 temp_dc.SelectObject(m_working_bm);
12022
12023 wxMemoryDC cache_dc;
12024 cache_dc.SelectObject(m_cached_chart_bm);
12025
12026 if (dy > 0) {
12027 if (dx > 0) {
12028 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12029 VPoint.pix_height - dy, &cache_dc, dx, dy);
12030 } else {
12031 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12032 VPoint.pix_height - dy, &cache_dc, 0, dy);
12033 }
12034
12035 } else {
12036 if (dx > 0) {
12037 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12038 VPoint.pix_height + dy, &cache_dc, dx, 0);
12039 } else {
12040 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12041 VPoint.pix_height + dy, &cache_dc, 0, 0);
12042 }
12043 }
12044
12045 OCPNRegion update_region;
12046 if (dy) {
12047 if (dy > 0)
12048 update_region.Union(
12049 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12050 else
12051 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12052 }
12053
12054 if (dx) {
12055 if (dx > 0)
12056 update_region.Union(
12057 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12058 else
12059 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12060 }
12061
12062 // Render the new region
12063 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12064 update_region);
12065 cache_dc.SelectObject(wxNullBitmap);
12066 } else {
12067 // No sensible (dx, dy) change in the view, so use the cached
12068 // member bitmap
12069 temp_dc.SelectObject(m_cached_chart_bm);
12070 b_save = false;
12071 }
12072 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12073
12074 } else // not blitable
12075 {
12076 temp_dc.SelectObject(m_working_bm);
12077 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12078 chart_get_region);
12079 }
12080 } else {
12081 // No change in the view, so use the cached member bitmap2
12082 temp_dc.SelectObject(m_cached_chart_bm);
12083 b_save = false;
12084 }
12085 } else // cached bitmap is not yet valid
12086 {
12087 temp_dc.SelectObject(m_working_bm);
12088 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12089 chart_get_region);
12090 }
12091
12092 // Save the fully rendered quilt image as a wxBitmap member of this class
12093 if (b_save) {
12094 // if((m_cached_chart_bm.GetWidth() !=
12095 // svp.pix_width) ||
12096 // (m_cached_chart_bm.GetHeight() !=
12097 // svp.pix_height))
12098 // m_cached_chart_bm.Create(svp.pix_width,
12099 // svp.pix_height, -1); // target wxBitmap
12100 // is big enough
12101 wxMemoryDC scratch_dc_0;
12102 scratch_dc_0.SelectObject(m_cached_chart_bm);
12103 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12104
12105 scratch_dc_0.SelectObject(wxNullBitmap);
12106
12107 m_bm_cache_vp =
12108 VPoint; // save the ViewPort associated with the cached wxBitmap
12109 }
12110 }
12111
12112 else // quilted, rotated
12113 {
12114 temp_dc.SelectObject(m_working_bm);
12115 OCPNRegion chart_get_all_region(
12116 wxRect(0, 0, svp.pix_width, svp.pix_height));
12117 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12118 chart_get_all_region);
12119 }
12120
12121 AbstractPlatform::HideBusySpinner();
12122
12123 }
12124
12125 else // not quilted
12126 {
12127 if (!m_singleChart) {
12128 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12129 dc.Clear();
12130 return;
12131 }
12132
12133 if (!chart_get_region.IsEmpty()) {
12134 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12135 }
12136 }
12137
12138 if (temp_dc.IsOk()) {
12139 // Arrange to render the World Chart vector data behind the rendered
12140 // current chart so that uncovered canvas areas show at least the world
12141 // chart.
12142 OCPNRegion chartValidRegion;
12143 if (!VPoint.b_quilt) {
12144 // Make a region covering the current chart on the canvas
12145
12146 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12147 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12148 else {
12149 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12150 // require that the viewport passed here have pix_width and pix_height
12151 // set to the actual display, not the virtual (rv_rect) sizes
12152 // (the vector calculations require the virtual sizes in svp)
12153
12154 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12155 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12156 }
12157 } else
12158 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12159
12160 temp_dc.DestroyClippingRegion();
12161
12162 // Copy current chart region
12163 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12164
12165 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12166
12167 if (!backgroundRegion.IsEmpty()) {
12168 // Draw the Background Chart only in the areas NOT covered by the
12169 // current chart view
12170
12171 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12172 clipping regions with more than 1 rectangle so... */
12173 wxColour water = pWorldBackgroundChart->water;
12174 if (water.IsOk()) {
12175 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12176 temp_dc.SetBrush(wxBrush(water));
12177 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12178 while (upd.HaveRects()) {
12179 wxRect rect = upd.GetRect();
12180 temp_dc.DrawRectangle(rect);
12181 upd.NextRect();
12182 }
12183 }
12184 // Associate with temp_dc
12185 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12186 temp_dc.SetDeviceClippingRegion(*clip_region);
12187 delete clip_region;
12188
12189 ocpnDC bgdc(temp_dc);
12190 double r = VPoint.rotation;
12191 SetVPRotation(VPoint.skew);
12192
12193 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12194 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12195
12196 SetVPRotation(r);
12197 }
12198 } // temp_dc.IsOk();
12199
12200 wxMemoryDC *pChartDC = &temp_dc;
12201 wxMemoryDC rotd_dc;
12202
12203 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12204 // Can we use the current rotated image cache?
12205 if (!b_rcache_ok) {
12206#ifdef __WXMSW__
12207 wxMemoryDC tbase_dc;
12208 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12209 tbase_dc.SelectObject(bm_base);
12210 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12211 tbase_dc.SelectObject(wxNullBitmap);
12212#else
12213 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12214#endif
12215
12216 wxImage base_image;
12217 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12218
12219 // Use a local static image rotator to improve wxWidgets code profile
12220 // Especially, on GTK the wxRound and wxRealPoint functions are very
12221 // expensive.....
12222
12223 double angle = GetVP().skew - GetVP().rotation;
12224 wxImage ri;
12225 bool b_rot_ok = false;
12226 if (base_image.IsOk()) {
12227 ViewPort rot_vp = GetVP();
12228
12229 m_b_rot_hidef = false;
12230
12231 ri = Image_Rotate(
12232 base_image, angle,
12233 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12234 m_b_rot_hidef, &m_roffset);
12235
12236 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12237 (rot_vp.rotation == VPoint.rotation) &&
12238 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12239 rot_vp.IsValid() && (ri.IsOk())) {
12240 b_rot_ok = true;
12241 }
12242 }
12243
12244 if (b_rot_ok) {
12245 delete m_prot_bm;
12246 m_prot_bm = new wxBitmap(ri);
12247 }
12248
12249 m_roffset.x += VPoint.rv_rect.x;
12250 m_roffset.y += VPoint.rv_rect.y;
12251 }
12252
12253 if (m_prot_bm && m_prot_bm->IsOk()) {
12254 rotd_dc.SelectObject(*m_prot_bm);
12255 pChartDC = &rotd_dc;
12256 } else {
12257 pChartDC = &temp_dc;
12258 m_roffset = wxPoint(0, 0);
12259 }
12260 } else { // unrotated
12261 pChartDC = &temp_dc;
12262 m_roffset = wxPoint(0, 0);
12263 }
12264
12265 wxPoint offset = m_roffset;
12266
12267 // Save the PixelCache viewpoint for next time
12268 m_cache_vp = VPoint;
12269
12270 // Set up a scratch DC for overlay objects
12271 wxMemoryDC mscratch_dc;
12272 mscratch_dc.SelectObject(*pscratch_bm);
12273
12274 mscratch_dc.ResetBoundingBox();
12275 mscratch_dc.DestroyClippingRegion();
12276 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12277
12278 // Blit the externally invalidated areas of the chart onto the scratch dc
12279 wxRegionIterator upd(rgn_blit); // get the update rect list
12280 while (upd) {
12281 wxRect rect = upd.GetRect();
12282
12283 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12284 rect.x - offset.x, rect.y - offset.y);
12285 upd++;
12286 }
12287
12288 // If multi-canvas, indicate which canvas has keyboard focus
12289 // by drawing a simple blue bar at the top.
12290 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12291 if (this == wxWindow::FindFocus()) {
12292 g_focusCanvas = this;
12293
12294 wxColour colour = GetGlobalColor("BLUE4");
12295 mscratch_dc.SetPen(wxPen(colour));
12296 mscratch_dc.SetBrush(wxBrush(colour));
12297
12298 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12299 mscratch_dc.DrawRectangle(activeRect);
12300 }
12301 }
12302
12303 // Any MBtiles?
12304 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12305 unsigned int im = stackIndexArray.size();
12306 if (VPoint.b_quilt && im > 0) {
12307 std::vector<int> tiles_to_show;
12308 for (unsigned int is = 0; is < im; is++) {
12309 const ChartTableEntry &cte =
12310 ChartData->GetChartTableEntry(stackIndexArray[is]);
12311 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12312 continue;
12313 }
12314 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12315 tiles_to_show.push_back(stackIndexArray[is]);
12316 }
12317 }
12318
12319 if (tiles_to_show.size())
12320 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12321 }
12322
12323 // May get an unexpected OnPaint call while switching display modes
12324 // Guard for that.
12325 if (!g_bopengl) {
12326 ocpnDC scratch_dc(mscratch_dc);
12327 RenderAlertMessage(mscratch_dc, GetVP());
12328 }
12329
12330#if 0
12331 // quiting?
12332 if (g_bquiting) {
12333#ifdef ocpnUSE_DIBSECTION
12334 ocpnMemDC q_dc;
12335#else
12336 wxMemoryDC q_dc;
12337#endif
12338 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12339 q_dc.SelectObject(qbm);
12340
12341 // Get a copy of the screen
12342 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12343
12344 // Draw a rectangle over the screen with a stipple brush
12345 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12346 q_dc.SetBrush(qbr);
12347 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12348
12349 // Blit back into source
12350 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12351 wxCOPY);
12352
12353 q_dc.SelectObject(wxNullBitmap);
12354 }
12355#endif
12356
12357#if 0
12358 // It is possible that this two-step method may be reuired for some platforms.
12359 // So, retain in the code base to aid recovery if necessary
12360
12361 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12362 if( VPoint.b_quilt ) {
12363 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12364 ChartBase *chart = m_pQuilt->GetRefChart();
12365 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12366
12367 // Clear the text Global declutter list
12368 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12369 if(ChPI)
12370 ChPI->ClearPLIBTextList();
12371 else{
12372 if(ps52plib)
12373 ps52plib->ClearTextList();
12374 }
12375
12376 wxMemoryDC t_dc;
12377 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12378
12379 wxColor maskBackground = wxColour(1,0,0);
12380 t_dc.SelectObject( qbm );
12381 t_dc.SetBackground(wxBrush(maskBackground));
12382 t_dc.Clear();
12383
12384 // Copy the scratch DC into the new bitmap
12385 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12386
12387 // Render the text to the new bitmap
12388 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12389 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12390
12391 // Copy the new bitmap back to the scratch dc
12392 wxRegionIterator upd_final( ru );
12393 while( upd_final ) {
12394 wxRect rect = upd_final.GetRect();
12395 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12396 upd_final++;
12397 }
12398
12399 t_dc.SelectObject( wxNullBitmap );
12400 }
12401 }
12402 }
12403#endif
12404 // Direct rendering model...
12405 if (VPoint.b_quilt) {
12406 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12407 ChartBase *chart = m_pQuilt->GetRefChart();
12408 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12409 // Clear the text Global declutter list
12410 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12411 if (ChPI)
12412 ChPI->ClearPLIBTextList();
12413 else {
12414 if (ps52plib) ps52plib->ClearTextList();
12415 }
12416
12417 // Render the text directly to the scratch bitmap
12418 OCPNRegion chart_all_text_region(
12419 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12420
12421 if (g_bShowChartBar && m_Piano) {
12422 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12423 GetVP().pix_width, m_Piano->GetHeight());
12424
12425 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12426 if (!style->chartStatusWindowTransparent)
12427 chart_all_text_region.Subtract(chart_bar_rect);
12428 }
12429
12430 if (m_Compass && m_Compass->IsShown()) {
12431 wxRect compassRect = m_Compass->GetRect();
12432 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12433 chart_all_text_region.Subtract(compassRect);
12434 }
12435 }
12436
12437 mscratch_dc.DestroyClippingRegion();
12438
12439 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12440 chart_all_text_region);
12441 }
12442 }
12443 }
12444
12445 // Now that charts are fully rendered, apply the overlay objects as decals.
12446 ocpnDC scratch_dc(mscratch_dc);
12447 DrawOverlayObjects(scratch_dc, ru);
12448
12449 // And finally, blit the scratch dc onto the physical dc
12450 wxRegionIterator upd_final(rgn_blit);
12451 while (upd_final) {
12452 wxRect rect = upd_final.GetRect();
12453 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12454 rect.y);
12455 upd_final++;
12456 }
12457
12458 // Deselect the chart bitmap from the temp_dc, so that it will not be
12459 // destroyed in the temp_dc dtor
12460 temp_dc.SelectObject(wxNullBitmap);
12461 // And for the scratch bitmap
12462 mscratch_dc.SelectObject(wxNullBitmap);
12463
12464 dc.DestroyClippingRegion();
12465
12466 PaintCleanup();
12467}
12468
12469void ChartCanvas::PaintCleanup() {
12470 // Handle the current graphic window, if present
12471 if (m_inPinch) return;
12472
12473 if (pCwin) {
12474 pCwin->Show();
12475 if (m_bTCupdate) {
12476 pCwin->Refresh();
12477 pCwin->Update();
12478 }
12479 }
12480
12481 // And set flags for next time
12482 m_bTCupdate = false;
12483
12484 // Handle deferred WarpPointer
12485 if (warp_flag) {
12486 WarpPointer(warp_x, warp_y);
12487 warp_flag = false;
12488 }
12489
12490 // Start movement timers, this runs nearly immediately.
12491 // the reason we cannot simply call it directly is the
12492 // refresh events it emits may be blocked from this paint event
12493 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12494 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12495}
12496
12497#if 0
12498wxColour GetErrorGraphicColor(double val)
12499{
12500 /*
12501 double valm = wxMin(val_max, val);
12502
12503 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12504 unsigned char red = (unsigned char)(255 * (valm/val_max));
12505
12506 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12507
12508 hv.saturation = 1.0;
12509 hv.value = 1.0;
12510
12511 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12512 return wxColour(rv.red, rv.green, rv.blue);
12513 */
12514
12515 // HTML colors taken from NOAA WW3 Web representation
12516 wxColour c;
12517 if((val > 0) && (val < 1)) c.Set("#002ad9");
12518 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12519 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12520 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12521 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12522 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12523 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12524 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12525 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12526 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12527 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12528 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12529 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12530 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12531 else if((val >= 30) && (val < 36)) c.Set("#870000");
12532 else if((val >= 36) && (val < 42)) c.Set("#690000");
12533 else if((val >= 42) && (val < 48)) c.Set("#550000");
12534 else if( val >= 48) c.Set("#410000");
12535
12536 return c;
12537}
12538
12539void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12540{
12541 wxImage gr_image(vp->pix_width, vp->pix_height);
12542 gr_image.InitAlpha();
12543
12544 double maxval = -10000;
12545 double minval = 10000;
12546
12547 double rlat, rlon;
12548 double glat, glon;
12549
12550 GetCanvasPixPoint(0, 0, rlat, rlon);
12551
12552 for(int i=1; i < vp->pix_height-1; i++)
12553 {
12554 for(int j=0; j < vp->pix_width; j++)
12555 {
12556 // Reference mercator value
12557// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12558
12559 // Georef value
12560 GetCanvasPixPoint(j, i, glat, glon);
12561
12562 maxval = wxMax(maxval, (glat - rlat));
12563 minval = wxMin(minval, (glat - rlat));
12564
12565 }
12566 rlat = glat;
12567 }
12568
12569 GetCanvasPixPoint(0, 0, rlat, rlon);
12570 for(int i=1; i < vp->pix_height-1; i++)
12571 {
12572 for(int j=0; j < vp->pix_width; j++)
12573 {
12574 // Reference mercator value
12575// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12576
12577 // Georef value
12578 GetCanvasPixPoint(j, i, glat, glon);
12579
12580 double f = ((glat - rlat)-minval)/(maxval - minval);
12581
12582 double dy = (f * 40);
12583
12584 wxColour c = GetErrorGraphicColor(dy);
12585 unsigned char r = c.Red();
12586 unsigned char g = c.Green();
12587 unsigned char b = c.Blue();
12588
12589 gr_image.SetRGB(j, i, r,g,b);
12590 if((glat - rlat )!= 0)
12591 gr_image.SetAlpha(j, i, 128);
12592 else
12593 gr_image.SetAlpha(j, i, 255);
12594
12595 }
12596 rlat = glat;
12597 }
12598
12599 // Create a Bitmap
12600 wxBitmap *pbm = new wxBitmap(gr_image);
12601 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12602 pbm->SetMask(gr_mask);
12603
12604 pmdc->DrawBitmap(*pbm, 0,0);
12605
12606 delete pbm;
12607
12608}
12609
12610#endif
12611
12612void ChartCanvas::CancelMouseRoute() {
12613 m_routeState = 0;
12614 m_pMouseRoute = NULL;
12615 m_bDrawingRoute = false;
12616}
12617
12618int ChartCanvas::GetNextContextMenuId() {
12619 return CanvasMenuHandler::GetNextContextMenuId();
12620}
12621
12622bool ChartCanvas::SetCursor(const wxCursor &c) {
12623#ifdef ocpnUSE_GL
12624 if (g_bopengl && m_glcc)
12625 return m_glcc->SetCursor(c);
12626 else
12627#endif
12628 return wxWindow::SetCursor(c);
12629}
12630
12631void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12632 if (g_bquiting) return;
12633 // Keep the mouse position members up to date
12634 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12635
12636 // Retrigger the route leg popup timer
12637 // This handles the case when the chart is moving in auto-follow mode,
12638 // but no user mouse input is made. The timer handler may Hide() the
12639 // popup if the chart moved enough n.b. We use slightly longer oneshot
12640 // value to allow this method's Refresh() to complete before potentially
12641 // getting another Refresh() in the popup timer handler.
12642 if (!m_RolloverPopupTimer.IsRunning() &&
12643 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12644 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12645 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12646 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12647
12648#ifdef ocpnUSE_GL
12649 if (m_glcc && g_bopengl) {
12650 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12651 // overlay objects.
12652 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12653
12654 m_glcc->Refresh(eraseBackground,
12655 NULL); // We always are going to render the entire screen
12656 // anyway, so make
12657 // sure that the window managers understand the invalid area
12658 // is actually the entire client area.
12659
12660 // We need to selectively Refresh some child windows, if they are visible.
12661 // Note that some children are refreshed elsewhere on timer ticks, so don't
12662 // need attention here.
12663
12664 // Thumbnail chart
12665 if (pthumbwin && pthumbwin->IsShown()) {
12666 pthumbwin->Raise();
12667 pthumbwin->Refresh(false);
12668 }
12669
12670 // ChartInfo window
12671 if (m_pCIWin && m_pCIWin->IsShown()) {
12672 m_pCIWin->Raise();
12673 m_pCIWin->Refresh(false);
12674 }
12675
12676 // if(g_MainToolbar)
12677 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12678
12679 } else
12680#endif
12681 wxWindow::Refresh(eraseBackground, rect);
12682}
12683
12684void ChartCanvas::Update() {
12685 if (m_glcc && g_bopengl) {
12686#ifdef ocpnUSE_GL
12687 m_glcc->Update();
12688#endif
12689 } else
12690 wxWindow::Update();
12691}
12692
12693void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12694 if (!pemboss) return;
12695 int x = pemboss->x, y = pemboss->y;
12696 const double factor = 200;
12697
12698 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12699 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12700 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12701
12702 // Grab a snipped image out of the chart
12703 wxMemoryDC snip_dc;
12704 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12705 snip_dc.SelectObject(snip_bmp);
12706
12707 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12708 snip_dc.SelectObject(wxNullBitmap);
12709
12710 wxImage snip_img = snip_bmp.ConvertToImage();
12711
12712 // Apply Emboss map to the snip image
12713 unsigned char *pdata = snip_img.GetData();
12714 if (pdata) {
12715 for (int y = 0; y < pemboss->height; y++) {
12716 int map_index = (y * pemboss->width);
12717 for (int x = 0; x < pemboss->width; x++) {
12718 double val = (pemboss->pmap[map_index] * factor) / 256.;
12719
12720 int nred = (int)((*pdata) + val);
12721 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12722 *pdata++ = (unsigned char)nred;
12723
12724 int ngreen = (int)((*pdata) + val);
12725 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12726 *pdata++ = (unsigned char)ngreen;
12727
12728 int nblue = (int)((*pdata) + val);
12729 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12730 *pdata++ = (unsigned char)nblue;
12731
12732 map_index++;
12733 }
12734 }
12735 }
12736
12737 // Convert embossed snip to a bitmap
12738 wxBitmap emb_bmp(snip_img);
12739
12740 // Map to another memoryDC
12741 wxMemoryDC result_dc;
12742 result_dc.SelectObject(emb_bmp);
12743
12744 // Blit to target
12745 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12746
12747 result_dc.SelectObject(wxNullBitmap);
12748}
12749
12750emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12751 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12752
12753 if (GetQuiltMode()) {
12754 // disable Overzoom indicator for MBTiles
12755 int refIndex = GetQuiltRefChartdbIndex();
12756 if (refIndex >= 0) {
12757 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12758 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12759 if (current_type == CHART_TYPE_MBTILES) {
12760 ChartBase *pChart = m_pQuilt->GetRefChart();
12761 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12762 if (ptc) {
12763 zoom_factor = ptc->GetZoomFactor();
12764 }
12765 }
12766 }
12767
12768 if (zoom_factor <= 3.9) return NULL;
12769 } else {
12770 if (m_singleChart) {
12771 if (zoom_factor <= 3.9) return NULL;
12772 } else
12773 return NULL;
12774 }
12775
12776 if (m_pEM_OverZoom) {
12777 m_pEM_OverZoom->x = 4;
12778 m_pEM_OverZoom->y = 0;
12779 if (g_MainToolbar && IsPrimaryCanvas()) {
12780 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12781 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12782 }
12783 }
12784 return m_pEM_OverZoom;
12785}
12786
12787void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12788 GridDraw(dc);
12789
12790 // bool pluginOverlayRender = true;
12791 //
12792 // if(g_canvasConfig > 0){ // Multi canvas
12793 // if(IsPrimaryCanvas())
12794 // pluginOverlayRender = false;
12795 // }
12796
12797 g_overlayCanvas = this;
12798
12799 if (g_pi_manager) {
12800 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12801 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12803 }
12804
12805 AISDrawAreaNotices(dc, GetVP(), this);
12806
12807 wxDC *pdc = dc.GetDC();
12808 if (pdc) {
12809 pdc->DestroyClippingRegion();
12810 wxDCClipper(*pdc, ru);
12811 }
12812
12813 if (m_bShowNavobjects) {
12814 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12815 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12816 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12817 DrawAnchorWatchPoints(dc);
12818 } else {
12819 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12820 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12821 }
12822
12823 AISDraw(dc, GetVP(), this);
12824 ShipDraw(dc);
12825 AlertDraw(dc);
12826
12827 RenderVisibleSectorLights(dc);
12828
12829 RenderAllChartOutlines(dc, GetVP());
12830 RenderRouteLegs(dc);
12831 RenderShipToActive(dc, false);
12832 ScaleBarDraw(dc);
12833 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12834 if (g_pi_manager) {
12835 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12837 }
12838
12839 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12840 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12841
12842 if (g_pi_manager) {
12843 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12845 }
12846
12847 if (m_bShowTide) {
12848 RebuildTideSelectList(GetVP().GetBBox());
12849 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12850 }
12851
12852 if (m_bShowCurrent) {
12853 RebuildCurrentSelectList(GetVP().GetBBox());
12854 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12855 }
12856
12857 if (!g_PrintingInProgress) {
12858 if (IsPrimaryCanvas()) {
12859 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12860 }
12861
12862 if (IsPrimaryCanvas()) {
12863 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12864 }
12865
12866 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12867
12868 if (m_pTrackRolloverWin) {
12869 m_pTrackRolloverWin->Draw(dc);
12870 m_brepaint_piano = true;
12871 }
12872
12873 if (m_pRouteRolloverWin) {
12874 m_pRouteRolloverWin->Draw(dc);
12875 m_brepaint_piano = true;
12876 }
12877
12878 if (m_pAISRolloverWin) {
12879 m_pAISRolloverWin->Draw(dc);
12880 m_brepaint_piano = true;
12881 }
12882 if (m_brepaint_piano && g_bShowChartBar) {
12883 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12884 }
12885
12886 if (m_Compass) m_Compass->Paint(dc);
12887
12888 if (!g_CanvasHideNotificationIcon) {
12889 if (IsPrimaryCanvas()) {
12890 auto &noteman = NotificationManager::GetInstance();
12891 if (noteman.GetNotificationCount()) {
12892 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12893 if (m_notification_button->UpdateStatus()) Refresh();
12894 m_notification_button->Show(true);
12895 m_notification_button->Paint(dc);
12896 } else {
12897 m_notification_button->Show(false);
12898 }
12899 }
12900 }
12901 }
12902 if (g_pi_manager) {
12903 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12905 }
12906}
12907
12908emboss_data *ChartCanvas::EmbossDepthScale() {
12909 if (!m_bShowDepthUnits) return NULL;
12910
12911 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12912
12913 if (GetQuiltMode()) {
12914 wxString s = m_pQuilt->GetQuiltDepthUnit();
12915 s.MakeUpper();
12916 if (s == "FEET")
12917 depth_unit_type = DEPTH_UNIT_FEET;
12918 else if (s.StartsWith("FATHOMS"))
12919 depth_unit_type = DEPTH_UNIT_FATHOMS;
12920 else if (s.StartsWith("METERS"))
12921 depth_unit_type = DEPTH_UNIT_METERS;
12922 else if (s.StartsWith("METRES"))
12923 depth_unit_type = DEPTH_UNIT_METERS;
12924 else if (s.StartsWith("METRIC"))
12925 depth_unit_type = DEPTH_UNIT_METERS;
12926 else if (s.StartsWith("METER"))
12927 depth_unit_type = DEPTH_UNIT_METERS;
12928
12929 } else {
12930 if (m_singleChart) {
12931 depth_unit_type = m_singleChart->GetDepthUnitType();
12932 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12933 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12934 }
12935 }
12936
12937 emboss_data *ped = NULL;
12938 switch (depth_unit_type) {
12939 case DEPTH_UNIT_FEET:
12940 ped = m_pEM_Feet;
12941 break;
12942 case DEPTH_UNIT_METERS:
12943 ped = m_pEM_Meters;
12944 break;
12945 case DEPTH_UNIT_FATHOMS:
12946 ped = m_pEM_Fathoms;
12947 break;
12948 default:
12949 return NULL;
12950 }
12951
12952 ped->x = (GetVP().pix_width - ped->width);
12953
12954 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12955 wxRect r = m_Compass->GetRect();
12956 ped->y = r.y + r.height;
12957 } else {
12958 ped->y = 40;
12959 }
12960 return ped;
12961}
12962
12963void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12964 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12965 wxFont font;
12966 if (style->embossFont == wxEmptyString) {
12967 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12968 font = *dFont;
12969 font.SetPointSize(60);
12970 font.SetWeight(wxFONTWEIGHT_BOLD);
12971 } else
12972 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12973 wxFONTWEIGHT_BOLD, false, style->embossFont);
12974
12975 int emboss_width = 500;
12976 int emboss_height = 200;
12977
12978 // Free any existing emboss maps
12979 delete m_pEM_Feet;
12980 delete m_pEM_Meters;
12981 delete m_pEM_Fathoms;
12982
12983 // Create the 3 DepthUnit emboss map structures
12984 m_pEM_Feet =
12985 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12986 m_pEM_Meters =
12987 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12988 m_pEM_Fathoms =
12989 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12990}
12991
12992#define OVERZOOM_TEXT _("OverZoom")
12993
12994void ChartCanvas::SetOverzoomFont() {
12995 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12996 int w, h;
12997
12998 wxFont font;
12999 if (style->embossFont == wxEmptyString) {
13000 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13001 font = *dFont;
13002 font.SetPointSize(40);
13003 font.SetWeight(wxFONTWEIGHT_BOLD);
13004 } else
13005 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13006 wxFONTWEIGHT_BOLD, false, style->embossFont);
13007
13008 wxClientDC dc(this);
13009 dc.SetFont(font);
13010 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13011
13012 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13013 font.SetPointSize(font.GetPointSize() - 1);
13014 dc.SetFont(font);
13015 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13016 }
13017 m_overzoomFont = font;
13018 m_overzoomTextWidth = w;
13019 m_overzoomTextHeight = h;
13020}
13021
13022void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13023 delete m_pEM_OverZoom;
13024
13025 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13026 m_pEM_OverZoom =
13027 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13028 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13029}
13030
13031emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13032 int height, const wxString &str,
13033 ColorScheme cs) {
13034 int *pmap;
13035
13036 // Create a temporary bitmap
13037 wxBitmap bmp(width, height, -1);
13038
13039 // Create a memory DC
13040 wxMemoryDC temp_dc;
13041 temp_dc.SelectObject(bmp);
13042
13043 // Paint on it
13044 temp_dc.SetBackground(*wxWHITE_BRUSH);
13045 temp_dc.SetTextBackground(*wxWHITE);
13046 temp_dc.SetTextForeground(*wxBLACK);
13047
13048 temp_dc.Clear();
13049
13050 temp_dc.SetFont(font);
13051
13052 int str_w, str_h;
13053 temp_dc.GetTextExtent(str, &str_w, &str_h);
13054 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13055 temp_dc.DrawText(str, 1, 1);
13056
13057 // Deselect the bitmap
13058 temp_dc.SelectObject(wxNullBitmap);
13059
13060 // Convert bitmap the wxImage for manipulation
13061 wxImage img = bmp.ConvertToImage();
13062
13063 int image_width = str_w * 105 / 100;
13064 int image_height = str_h * 105 / 100;
13065 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13066 wxMin(image_height, img.GetHeight()));
13067 wxImage imgs = img.GetSubImage(r);
13068
13069 double val_factor;
13070 switch (cs) {
13071 case GLOBAL_COLOR_SCHEME_DAY:
13072 default:
13073 val_factor = 1;
13074 break;
13075 case GLOBAL_COLOR_SCHEME_DUSK:
13076 val_factor = .5;
13077 break;
13078 case GLOBAL_COLOR_SCHEME_NIGHT:
13079 val_factor = .25;
13080 break;
13081 }
13082
13083 int val;
13084 int index;
13085 const int w = imgs.GetWidth();
13086 const int h = imgs.GetHeight();
13087 pmap = (int *)calloc(w * h * sizeof(int), 1);
13088 // Create emboss map by differentiating the emboss image
13089 // and storing integer results in pmap
13090 // n.b. since the image is B/W, it is sufficient to check
13091 // one channel (i.e. red) only
13092 for (int y = 1; y < h - 1; y++) {
13093 for (int x = 1; x < w - 1; x++) {
13094 val =
13095 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13096 val = (int)(val * val_factor);
13097 index = (y * w) + x;
13098 pmap[index] = val;
13099 }
13100 }
13101
13102 emboss_data *pret = new emboss_data;
13103 pret->pmap = pmap;
13104 pret->width = w;
13105 pret->height = h;
13106
13107 return pret;
13108}
13109
13110void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13111 Track *active_track = NULL;
13112 for (Track *pTrackDraw : g_TrackList) {
13113 if (g_pActiveTrack == pTrackDraw) {
13114 active_track = pTrackDraw;
13115 continue;
13116 }
13117
13118 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13119 }
13120
13121 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13122}
13123
13124void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13125 Track *active_track = NULL;
13126 for (Track *pTrackDraw : g_TrackList) {
13127 if (g_pActiveTrack == pTrackDraw) {
13128 active_track = pTrackDraw;
13129 break;
13130 }
13131 }
13132 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13133}
13134
13135void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13136 Route *active_route = NULL;
13137 for (Route *pRouteDraw : *pRouteList) {
13138 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13139 active_route = pRouteDraw;
13140 continue;
13141 }
13142
13143 // if(m_canvasIndex == 1)
13144 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13145 }
13146
13147 // Draw any active or selected route (or track) last, so that is is always on
13148 // top
13149 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13150}
13151
13152void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13153 Route *active_route = NULL;
13154
13155 for (Route *pRouteDraw : *pRouteList) {
13156 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13157 active_route = pRouteDraw;
13158 break;
13159 }
13160 }
13161 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13162}
13163
13164void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13165 if (!pWayPointMan) return;
13166
13167 auto node = pWayPointMan->GetWaypointList()->begin();
13168
13169 while (node != pWayPointMan->GetWaypointList()->end()) {
13170 RoutePoint *pWP = *node;
13171 if (pWP) {
13172 if (pWP->m_bIsInRoute) {
13173 ++node;
13174 continue;
13175 }
13176
13177 /* technically incorrect... waypoint has bounding box */
13178 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13179 RoutePointGui(*pWP).Draw(dc, this, NULL);
13180 else {
13181 // Are Range Rings enabled?
13182 if (pWP->GetShowWaypointRangeRings() &&
13183 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13184 double factor = 1.00;
13185 if (pWP->GetWaypointRangeRingsStepUnits() ==
13186 1) // convert kilometers to NMi
13187 factor = 1 / 1.852;
13188
13189 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13190 pWP->GetWaypointRangeRingsStep() / 60.;
13191 radius *= 2; // Fudge factor
13192
13193 LLBBox radar_box;
13194 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13195 pWP->m_lat + radius, pWP->m_lon + radius);
13196 if (!BltBBox.IntersectOut(radar_box)) {
13197 RoutePointGui(*pWP).Draw(dc, this, NULL);
13198 }
13199 }
13200 }
13201 }
13202
13203 ++node;
13204 }
13205}
13206
13207void ChartCanvas::DrawBlinkObjects() {
13208 // All RoutePoints
13209 wxRect update_rect;
13210
13211 if (!pWayPointMan) return;
13212
13213 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13214 if (pWP) {
13215 if (pWP->m_bBlink) {
13216 update_rect.Union(pWP->CurrentRect_in_DC);
13217 }
13218 }
13219 }
13220 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13221}
13222
13223void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13224 // draw anchor watch rings, if activated
13225
13227 wxPoint r1, r2;
13228 wxPoint lAnchorPoint1, lAnchorPoint2;
13229 double lpp1 = 0.0;
13230 double lpp2 = 0.0;
13231 if (pAnchorWatchPoint1) {
13232 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13234 &lAnchorPoint1);
13235 }
13236 if (pAnchorWatchPoint2) {
13237 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13239 &lAnchorPoint2);
13240 }
13241
13242 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13243 wxPen ppPenr(GetGlobalColor("URED"), 2);
13244
13245 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13246 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13247 dc.SetBrush(*ppBrush);
13248
13249 if (lpp1 > 0) {
13250 dc.SetPen(ppPeng);
13251 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13252 }
13253
13254 if (lpp2 > 0) {
13255 dc.SetPen(ppPeng);
13256 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13257 }
13258
13259 if (lpp1 < 0) {
13260 dc.SetPen(ppPenr);
13261 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13262 }
13263
13264 if (lpp2 < 0) {
13265 dc.SetPen(ppPenr);
13266 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13267 }
13268 }
13269}
13270
13271double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13272 double lpp = 0.;
13273 wxPoint r1;
13274 wxPoint lAnchorPoint;
13275 double d1 = 0.0;
13276 double dabs;
13277 double tlat1, tlon1;
13278
13279 if (pAnchorWatchPoint) {
13280 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13281 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13282 dabs = fabs(d1 / 1852.);
13283 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13284 &tlat1, &tlon1);
13285 GetCanvasPointPix(tlat1, tlon1, &r1);
13286 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13287 &lAnchorPoint);
13288 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13289 pow((double)(lAnchorPoint.y - r1.y), 2));
13290
13291 // This is an entry watch
13292 if (d1 < 0) lpp = -lpp;
13293 }
13294 return lpp;
13295}
13296
13297//------------------------------------------------------------------------------------------
13298// Tides Support
13299//------------------------------------------------------------------------------------------
13300void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13301 if (!ptcmgr) return;
13302
13303 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13304
13305 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13306 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13307 double lon = pIDX->IDX_lon;
13308 double lat = pIDX->IDX_lat;
13309
13310 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13311 if ((type == 't') || (type == 'T')) {
13312 if (BBox.Contains(lat, lon)) {
13313 // Manage the point selection list
13314 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13315 }
13316 }
13317 }
13318}
13319
13320void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13321 if (!ptcmgr) return;
13322
13323 wxDateTime this_now = gTimeSource;
13324 bool cur_time = !gTimeSource.IsValid();
13325 if (cur_time) this_now = wxDateTime::Now();
13326 time_t t_this_now = this_now.GetTicks();
13327
13328 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13329 wxPENSTYLE_SOLID);
13330 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13331 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13332 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13333 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13334
13335 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13336 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13337 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13338 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13339 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13340 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13341
13342 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13343 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13344 int font_size = wxMax(10, dFont->GetPointSize());
13345 font_size /= g_Platform->GetDisplayDIPMult(this);
13346 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13347 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13348 false, dFont->GetFaceName());
13349
13350 dc.SetPen(*pblack_pen);
13351 dc.SetBrush(*pgreen_brush);
13352
13353 wxBitmap bm;
13354 switch (m_cs) {
13355 case GLOBAL_COLOR_SCHEME_DAY:
13356 bm = m_bmTideDay;
13357 break;
13358 case GLOBAL_COLOR_SCHEME_DUSK:
13359 bm = m_bmTideDusk;
13360 break;
13361 case GLOBAL_COLOR_SCHEME_NIGHT:
13362 bm = m_bmTideNight;
13363 break;
13364 default:
13365 bm = m_bmTideDay;
13366 break;
13367 }
13368
13369 int bmw = bm.GetWidth();
13370 int bmh = bm.GetHeight();
13371
13372 float scale_factor = 1.0;
13373
13374 // Set the onscreen size of the symbol
13375 // Compensate for various display resolutions
13376 float icon_pixelRefDim = 45;
13377
13378 // Tidal report graphic is scaled by the text size of the label in use
13379 wxScreenDC sdc;
13380 int height;
13381 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13382 height *= g_Platform->GetDisplayDIPMult(this);
13383 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13384
13385 scale_factor *= pix_factor;
13386
13387 float user_scale_factor = g_ChartScaleFactorExp;
13388 if (g_ChartScaleFactorExp > 1.0)
13389 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13390 1.2; // soften the scale factor a bit
13391
13392 scale_factor *= user_scale_factor;
13393 scale_factor *= GetContentScaleFactor();
13394
13395 {
13396 double marge = 0.05;
13397 std::vector<LLBBox> drawn_boxes;
13398 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13399 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13400
13401 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13402 if ((type == 't') || (type == 'T')) // only Tides
13403 {
13404 double lon = pIDX->IDX_lon;
13405 double lat = pIDX->IDX_lat;
13406
13407 if (BBox.ContainsMarge(lat, lon, marge)) {
13408 // Avoid drawing detailed graphic for duplicate tide stations
13409 if (GetVP().chart_scale < 500000) {
13410 bool bdrawn = false;
13411 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13412 if (drawn_boxes[i].Contains(lat, lon)) {
13413 bdrawn = true;
13414 break;
13415 }
13416 }
13417 if (bdrawn) continue; // the station loop
13418
13419 LLBBox this_box;
13420 this_box.Set(lat, lon, lat, lon);
13421 this_box.EnLarge(.005);
13422 drawn_boxes.push_back(this_box);
13423 }
13424
13425 wxPoint r;
13426 GetCanvasPointPix(lat, lon, &r);
13427 // draw standard icons
13428 if (GetVP().chart_scale > 500000) {
13429 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13430 }
13431 // draw "extended" icons
13432 else {
13433 dc.SetFont(*plabelFont);
13434 {
13435 {
13436 float val, nowlev;
13437 float ltleve = 0.;
13438 float htleve = 0.;
13439 time_t tctime;
13440 time_t lttime = 0;
13441 time_t httime = 0;
13442 bool wt;
13443 // define if flood or ebb in the last ten minutes and verify if
13444 // data are useable
13445 if (ptcmgr->GetTideFlowSens(
13446 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13447 pIDX->IDX_rec_num, nowlev, val, wt)) {
13448 // search forward the first HW or LW near "now" ( starting at
13449 // "now" - ten minutes )
13450 ptcmgr->GetHightOrLowTide(
13451 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13452 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13453 wt, pIDX->IDX_rec_num, val, tctime);
13454 if (wt) {
13455 httime = tctime;
13456 htleve = val;
13457 } else {
13458 lttime = tctime;
13459 ltleve = val;
13460 }
13461 wt = !wt;
13462
13463 // then search opposite tide near "now"
13464 if (tctime > t_this_now) // search backward
13465 ptcmgr->GetHightOrLowTide(
13466 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13467 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13468 pIDX->IDX_rec_num, val, tctime);
13469 else
13470 // or search forward
13471 ptcmgr->GetHightOrLowTide(
13472 t_this_now, FORWARD_TEN_MINUTES_STEP,
13473 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13474 val, tctime);
13475 if (wt) {
13476 httime = tctime;
13477 htleve = val;
13478 } else {
13479 lttime = tctime;
13480 ltleve = val;
13481 }
13482
13483 // draw the tide rectangle:
13484
13485 // tide icon rectangle has default pre-scaled width = 12 ,
13486 // height = 45
13487 int width = (int)(12 * scale_factor + 0.5);
13488 int height = (int)(45 * scale_factor + 0.5);
13489 int linew = wxMax(1, (int)(scale_factor));
13490 int xDraw = r.x - (width / 2);
13491 int yDraw = r.y - (height / 2);
13492
13493 // process tide state ( %height and flow sens )
13494 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13495 int hs = (httime > lttime) ? -4 : 4;
13496 hs *= (int)(scale_factor + 0.5);
13497 if (ts > 0.995 || ts < 0.005) hs = 0;
13498 int ht_y = (int)(height * ts);
13499
13500 // draw yellow tide rectangle outlined in black
13501 pblack_pen->SetWidth(linew);
13502 dc.SetPen(*pblack_pen);
13503 dc.SetBrush(*pyelo_brush);
13504 dc.DrawRectangle(xDraw, yDraw, width, height);
13505
13506 // draw blue rectangle as water height, smaller in width than
13507 // yellow rectangle
13508 dc.SetPen(*pblue_pen);
13509 dc.SetBrush(*pblue_brush);
13510 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13511 (width - (4 * linew)), height - ht_y);
13512
13513 // draw sens arrows (ensure they are not "under-drawn" by top
13514 // line of blue rectangle )
13515 int hl;
13516 wxPoint arrow[3];
13517 arrow[0].x = xDraw + 2 * linew;
13518 arrow[1].x = xDraw + width / 2;
13519 arrow[2].x = xDraw + width - 2 * linew;
13520 pyelo_pen->SetWidth(linew);
13521 pblue_pen->SetWidth(linew);
13522 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13523 {
13524 hl = (int)(height * 0.25) + yDraw;
13525 arrow[0].y = hl;
13526 arrow[1].y = hl + hs;
13527 arrow[2].y = hl;
13528 if (ts < 0.15)
13529 dc.SetPen(*pyelo_pen);
13530 else
13531 dc.SetPen(*pblue_pen);
13532 dc.DrawLines(3, arrow);
13533 }
13534 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13535 {
13536 hl = (int)(height * 0.5) + yDraw;
13537 arrow[0].y = hl;
13538 arrow[1].y = hl + hs;
13539 arrow[2].y = hl;
13540 if (ts < 0.40)
13541 dc.SetPen(*pyelo_pen);
13542 else
13543 dc.SetPen(*pblue_pen);
13544 dc.DrawLines(3, arrow);
13545 }
13546 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13547 {
13548 hl = (int)(height * 0.75) + yDraw;
13549 arrow[0].y = hl;
13550 arrow[1].y = hl + hs;
13551 arrow[2].y = hl;
13552 if (ts < 0.65)
13553 dc.SetPen(*pyelo_pen);
13554 else
13555 dc.SetPen(*pblue_pen);
13556 dc.DrawLines(3, arrow);
13557 }
13558 // draw tide level text
13559 wxString s;
13560 s.Printf("%3.1f", nowlev);
13561 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13562 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13563 int wx1;
13564 dc.GetTextExtent(s, &wx1, NULL);
13565 wx1 *= g_Platform->GetDisplayDIPMult(this);
13566 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13567 }
13568 }
13569 }
13570 }
13571 }
13572 }
13573 }
13574 }
13575}
13576
13577//------------------------------------------------------------------------------------------
13578// Currents Support
13579//------------------------------------------------------------------------------------------
13580
13581void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13582 if (!ptcmgr) return;
13583
13584 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13585
13586 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13587 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13588 double lon = pIDX->IDX_lon;
13589 double lat = pIDX->IDX_lat;
13590
13591 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13592 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13593 if ((BBox.Contains(lat, lon))) {
13594 // Manage the point selection list
13595 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13596 }
13597 }
13598 }
13599}
13600
13601void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13602 if (!ptcmgr) return;
13603
13604 float tcvalue, dir;
13605 bool bnew_val;
13606 char sbuf[20];
13607 wxFont *pTCFont;
13608 double lon_last = 0.;
13609 double lat_last = 0.;
13610 // arrow size for Raz Blanchard : 12 knots north
13611 double marge = 0.2;
13612 bool cur_time = !gTimeSource.IsValid();
13613
13614 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13615 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13616
13617 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13618 wxPENSTYLE_SOLID);
13619 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13620 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13621 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13622 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13623 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13624 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13625 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13626 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13627
13628 double skew_angle = GetVPRotation();
13629
13630 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13631 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13632 int font_size = wxMax(10, dFont->GetPointSize());
13633 font_size /= g_Platform->GetDisplayDIPMult(this);
13634 pTCFont = FontMgr::Get().FindOrCreateFont(
13635 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13636 false, dFont->GetFaceName());
13637
13638 float scale_factor = 1.0;
13639
13640 // Set the onscreen size of the symbol
13641 // Current report graphic is scaled by the text size of the label in use
13642 wxScreenDC sdc;
13643 int height;
13644 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13645 height *= g_Platform->GetDisplayDIPMult(this);
13646 float nominal_icon_size_pixels = 15;
13647 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13648
13649 scale_factor *= pix_factor;
13650
13651 float user_scale_factor = g_ChartScaleFactorExp;
13652 if (g_ChartScaleFactorExp > 1.0)
13653 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13654 1.2; // soften the scale factor a bit
13655
13656 scale_factor *= user_scale_factor;
13657
13658 scale_factor *= GetContentScaleFactor();
13659
13660 {
13661 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13662 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13663 double lon = pIDX->IDX_lon;
13664 double lat = pIDX->IDX_lat;
13665
13666 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13667 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13668 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13669 wxPoint r;
13670 GetCanvasPointPix(lat, lon, &r);
13671
13672 wxPoint d[4]; // points of a diamond at the current station location
13673 int dd = (int)(5.0 * scale_factor + 0.5);
13674 d[0].x = r.x;
13675 d[0].y = r.y + dd;
13676 d[1].x = r.x + dd;
13677 d[1].y = r.y;
13678 d[2].x = r.x;
13679 d[2].y = r.y - dd;
13680 d[3].x = r.x - dd;
13681 d[3].y = r.y;
13682
13683 if (1) {
13684 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13685 dc.SetPen(*pblack_pen);
13686 dc.SetBrush(*porange_brush);
13687 dc.DrawPolygon(4, d);
13688
13689 if (type == 'C') {
13690 dc.SetBrush(*pblack_brush);
13691 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13692 }
13693
13694 if (GetVP().chart_scale < 1000000) {
13695 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13696 continue;
13697 } else
13698 continue;
13699
13700 if (1 /*type == 'c'*/) {
13701 {
13702 // Get the display pixel location of the current station
13703 int pixxc, pixyc;
13704 pixxc = r.x;
13705 pixyc = r.y;
13706
13707 // Adjust drawing size using logarithmic scale. tcvalue is
13708 // current in knots
13709 double a1 = fabs(tcvalue) * 10.;
13710 // Current values <= 0.1 knot will have no arrow
13711 a1 = wxMax(1.0, a1);
13712 double a2 = log10(a1);
13713
13714 float cscale = scale_factor * a2 * 0.3;
13715
13716 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13717 dc.SetPen(*porange_pen);
13718 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13719 cscale);
13720 // Draw text, if enabled
13721
13722 if (bDrawCurrentValues) {
13723 dc.SetFont(*pTCFont);
13724 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13725 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13726 }
13727 }
13728 } // scale
13729 }
13730 /* This is useful for debugging the TC database
13731 else
13732 {
13733 dc.SetPen ( *porange_pen );
13734 dc.SetBrush ( *pgray_brush );
13735 dc.DrawPolygon ( 4, d );
13736 }
13737 */
13738 }
13739 lon_last = lon;
13740 lat_last = lat;
13741 }
13742 }
13743 }
13744}
13745
13746void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13747 ShowSingleTideDialog(x, y, pvIDX);
13748}
13749
13750void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13751 if (!pvIDX) return; // Validate input
13752
13753 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13754
13755 // Check if a tide dialog is already open and visible
13756 if (pCwin && pCwin->IsShown()) {
13757 // Same tide station: bring existing dialog to front (preserves user
13758 // context)
13759 if (pCwin->GetCurrentIDX() == pNewIDX) {
13760 pCwin->Raise();
13761 pCwin->SetFocus();
13762
13763 // Provide subtle visual feedback that dialog is already open
13764 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13765 return;
13766 }
13767
13768 // Different tide station: close current dialog before opening new one
13769 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13770 }
13771
13772 if (pCwin) {
13773 // This shouldn't happen but ensures clean state
13774 pCwin->Destroy();
13775 pCwin = NULL;
13776 }
13777
13778 // Create and display new tide dialog
13779 pCwin = new TCWin(this, x, y, pvIDX);
13780
13781 // Ensure the dialog is properly shown and focused
13782 if (pCwin) {
13783 pCwin->Show();
13784 pCwin->Raise();
13785 pCwin->SetFocus();
13786 }
13787}
13788
13789bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13790
13792 if (pCwin) {
13793 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13794 }
13795}
13796
13797#define NUM_CURRENT_ARROW_POINTS 9
13798static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13799 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13800 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13801 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13802
13803void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13804 double scale) {
13805 if (scale > 1e-2) {
13806 float sin_rot = sin(rot_angle * PI / 180.);
13807 float cos_rot = cos(rot_angle * PI / 180.);
13808
13809 // Move to the first point
13810
13811 float xt = CurrentArrowArray[0].x;
13812 float yt = CurrentArrowArray[0].y;
13813
13814 float xp = (xt * cos_rot) - (yt * sin_rot);
13815 float yp = (xt * sin_rot) + (yt * cos_rot);
13816 int x1 = (int)(xp * scale);
13817 int y1 = (int)(yp * scale);
13818
13819 // Walk thru the point list
13820 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13821 xt = CurrentArrowArray[ip].x;
13822 yt = CurrentArrowArray[ip].y;
13823
13824 float xp = (xt * cos_rot) - (yt * sin_rot);
13825 float yp = (xt * sin_rot) + (yt * cos_rot);
13826 int x2 = (int)(xp * scale);
13827 int y2 = (int)(yp * scale);
13828
13829 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13830
13831 x1 = x2;
13832 y1 = y2;
13833 }
13834 }
13835}
13836
13837wxString ChartCanvas::FindValidUploadPort() {
13838 wxString port;
13839 // Try to use the saved persistent upload port first
13840 if (!g_uploadConnection.IsEmpty() &&
13841 g_uploadConnection.StartsWith("Serial")) {
13842 port = g_uploadConnection;
13843 }
13844
13845 else {
13846 // If there is no persistent upload port recorded (yet)
13847 // then use the first available serial connection which has output defined.
13848 for (auto *cp : TheConnectionParams()) {
13849 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13850 port << "Serial:" << cp->Port;
13851 }
13852 }
13853 return port;
13854}
13855
13856void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13857 if (!win) return;
13858
13859 if (NULL == g_pais_query_dialog_active) {
13860 int pos_x = g_ais_query_dialog_x;
13861 int pos_y = g_ais_query_dialog_y;
13862
13863 if (g_pais_query_dialog_active) {
13864 g_pais_query_dialog_active->Destroy();
13865 g_pais_query_dialog_active = new AISTargetQueryDialog();
13866 } else {
13867 g_pais_query_dialog_active = new AISTargetQueryDialog();
13868 }
13869
13870 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13871 wxPoint(pos_x, pos_y));
13872
13873 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13874 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13875 g_pais_query_dialog_active->SetMMSI(mmsi);
13876 g_pais_query_dialog_active->UpdateText();
13877 wxSize sz = g_pais_query_dialog_active->GetSize();
13878
13879 bool b_reset_pos = false;
13880#ifdef __WXMSW__
13881 // Support MultiMonitor setups which an allow negative window positions.
13882 // If the requested window title bar does not intersect any installed
13883 // monitor, then default to simple primary monitor positioning.
13884 RECT frame_title_rect;
13885 frame_title_rect.left = pos_x;
13886 frame_title_rect.top = pos_y;
13887 frame_title_rect.right = pos_x + sz.x;
13888 frame_title_rect.bottom = pos_y + 30;
13889
13890 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13891 b_reset_pos = true;
13892#else
13893
13894 // Make sure drag bar (title bar) of window intersects wxClient Area of
13895 // screen, with a little slop...
13896 wxRect window_title_rect; // conservative estimate
13897 window_title_rect.x = pos_x;
13898 window_title_rect.y = pos_y;
13899 window_title_rect.width = sz.x;
13900 window_title_rect.height = 30;
13901
13902 wxRect ClientRect = wxGetClientDisplayRect();
13903 ClientRect.Deflate(
13904 60, 60); // Prevent the new window from being too close to the edge
13905 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13906
13907#endif
13908
13909 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13910
13911 } else {
13912 g_pais_query_dialog_active->SetMMSI(mmsi);
13913 g_pais_query_dialog_active->UpdateText();
13914 }
13915
13916 g_pais_query_dialog_active->Show();
13917}
13918
13919void ChartCanvas::ToggleCanvasQuiltMode() {
13920 bool cur_mode = GetQuiltMode();
13921
13922 if (!GetQuiltMode())
13923 SetQuiltMode(true);
13924 else if (GetQuiltMode()) {
13925 SetQuiltMode(false);
13926 g_sticky_chart = GetQuiltReferenceChartIndex();
13927 }
13928
13929 if (cur_mode != GetQuiltMode()) {
13930 SetupCanvasQuiltMode();
13931 DoCanvasUpdate();
13932 InvalidateGL();
13933 Refresh();
13934 }
13935 // TODO What to do about this?
13936 // g_bQuiltEnable = GetQuiltMode();
13937
13938 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13939 if (ps52plib) ps52plib->GenerateStateHash();
13940
13941 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13942 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13943}
13944
13945void ChartCanvas::DoCanvasStackDelta(int direction) {
13946 if (!GetQuiltMode()) {
13947 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13948 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13949 if ((current_stack_index + direction) < 0) return;
13950
13951 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13952 int new_dbIndex =
13953 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13954
13955 if (IsChartQuiltableRef(new_dbIndex)) {
13956 ToggleCanvasQuiltMode();
13957 SelectQuiltRefdbChart(new_dbIndex);
13958 m_bpersistent_quilt = false;
13959 }
13960 } else {
13961 SelectChartFromStack(current_stack_index + direction);
13962 }
13963 } else {
13964 std::vector<int> piano_chart_index_array =
13965 GetQuiltExtendedStackdbIndexArray();
13966 int refdb = GetQuiltRefChartdbIndex();
13967
13968 // Find the ref chart in the stack
13969 int current_index = -1;
13970 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13971 if (refdb == piano_chart_index_array[i]) {
13972 current_index = i;
13973 break;
13974 }
13975 }
13976 if (current_index == -1) return;
13977
13978 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13979 int target_family = ctet.GetChartFamily();
13980
13981 int new_index = -1;
13982 int check_index = current_index + direction;
13983 bool found = false;
13984 int check_dbIndex = -1;
13985 int new_dbIndex = -1;
13986
13987 // When quilted. switch within the same chart family
13988 while (!found &&
13989 (unsigned int)check_index < piano_chart_index_array.size() &&
13990 (check_index >= 0)) {
13991 check_dbIndex = piano_chart_index_array[check_index];
13992 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13993 if (target_family == cte.GetChartFamily()) {
13994 found = true;
13995 new_index = check_index;
13996 new_dbIndex = check_dbIndex;
13997 break;
13998 }
13999
14000 check_index += direction;
14001 }
14002
14003 if (!found) return;
14004
14005 if (!IsChartQuiltableRef(new_dbIndex)) {
14006 ToggleCanvasQuiltMode();
14007 SelectdbChart(new_dbIndex);
14008 m_bpersistent_quilt = true;
14009 } else {
14010 SelectQuiltRefChart(new_index);
14011 }
14012 }
14013
14014 // update the state of the menu items (checkmarks etc)
14015 top_frame::Get()->UpdateGlobalMenuItems();
14016 SetQuiltChartHiLiteIndex(-1);
14017
14018 ReloadVP();
14019}
14020
14021//--------------------------------------------------------------------------------------------------------
14022//
14023// Toolbar support
14024//
14025//--------------------------------------------------------------------------------------------------------
14026
14027void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14028 // Handle the per-canvas toolbar clicks here
14029
14030 switch (event.GetId()) {
14031 case ID_ZOOMIN: {
14032 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14033 break;
14034 }
14035
14036 case ID_ZOOMOUT: {
14037 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14038 break;
14039 }
14040
14041 case ID_STKUP:
14042 DoCanvasStackDelta(1);
14043 DoCanvasUpdate();
14044 break;
14045
14046 case ID_STKDN:
14047 DoCanvasStackDelta(-1);
14048 DoCanvasUpdate();
14049 break;
14050
14051 case ID_FOLLOW: {
14052 TogglebFollow();
14053 break;
14054 }
14055
14056 case ID_CURRENT: {
14057 ShowCurrents(!GetbShowCurrent());
14058 ReloadVP();
14059 Refresh(false);
14060 break;
14061 }
14062
14063 case ID_TIDE: {
14064 ShowTides(!GetbShowTide());
14065 ReloadVP();
14066 Refresh(false);
14067 break;
14068 }
14069
14070 case ID_ROUTE: {
14071 if (0 == m_routeState) {
14072 StartRoute();
14073 } else {
14074 FinishRoute();
14075 }
14076
14077#ifdef __ANDROID__
14078 androidSetRouteAnnunciator(m_routeState == 1);
14079#endif
14080 break;
14081 }
14082
14083 case ID_AIS: {
14084 SetAISCanvasDisplayStyle(-1);
14085 break;
14086 }
14087
14088 default:
14089 break;
14090 }
14091
14092 // And then let gFrame handle the rest....
14093 event.Skip();
14094}
14095
14096void ChartCanvas::SetShowAIS(bool show) {
14097 m_bShowAIS = show;
14098 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14099 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14100}
14101
14102void ChartCanvas::SetAttenAIS(bool show) {
14103 m_bShowAISScaled = show;
14104 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14105 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14106}
14107
14108void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14109 // make some arrays to hold the dfferences between cycle steps
14110 // show all, scaled, hide all
14111 bool bShowAIS_Array[3] = {true, true, false};
14112 bool bShowScaled_Array[3] = {false, true, true};
14113 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14114 _("Attenuate less critical AIS targets"),
14115 _("Hide AIS Targets")};
14116 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14117 int ArraySize = 3;
14118 int AIS_Toolbar_Switch = 0;
14119 if (StyleIndx == -1) { // -1 means coming from toolbar button
14120 // find current state of switch
14121 for (int i = 1; i < ArraySize; i++) {
14122 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14123 (bShowScaled_Array[i] == m_bShowAISScaled))
14124 AIS_Toolbar_Switch = i;
14125 }
14126 AIS_Toolbar_Switch++; // we did click so continu with next item
14127 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14128 AIS_Toolbar_Switch++;
14129
14130 } else { // coming from menu bar.
14131 AIS_Toolbar_Switch = StyleIndx;
14132 }
14133 // make sure we are not above array
14134 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14135
14136 int AIS_Toolbar_Switch_Next =
14137 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14138 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14139 AIS_Toolbar_Switch_Next++;
14140 if (AIS_Toolbar_Switch_Next >= ArraySize)
14141 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14142
14143 // Set found values to global and member variables
14144 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14145 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14146}
14147
14148void ChartCanvas::TouchAISToolActive() {}
14149
14150void ChartCanvas::UpdateAISTBTool() {}
14151
14152//---------------------------------------------------------------------------------
14153//
14154// Compass/GPS status icon support
14155//
14156//---------------------------------------------------------------------------------
14157
14158void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14159 // Look for change in overlap or positions
14160 bool b_update = false;
14161 int cc1_edge_comp = 2;
14162 wxRect rect = m_Compass->GetRect();
14163 wxSize parent_size = GetSize();
14164
14165 parent_size *= m_displayScale;
14166
14167 // check to see if it would overlap if it was in its home position (upper
14168 // right)
14169 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14170 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14171 wxRect compass_rect(compass_pt, rect.GetSize());
14172
14173 m_Compass->Move(compass_pt);
14174
14175 if (m_Compass && m_Compass->IsShown())
14176 m_Compass->UpdateStatus(b_force_new | b_update);
14177
14178 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14179 scaler = wxMax(scaler, 1.0);
14180 wxPoint note_point = wxPoint(
14181 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14182 if (m_notification_button) {
14183 m_notification_button->Move(note_point);
14184 m_notification_button->UpdateStatus();
14185 }
14186
14187 if (b_force_new | b_update) Refresh();
14188}
14189
14190void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14191 ChartTypeEnum New_Type,
14192 ChartFamilyEnum New_Family) {
14193 if (!GetpCurrentStack()) return;
14194 if (!ChartData) return;
14195
14196 if (index < GetpCurrentStack()->nEntry) {
14197 // Open the new chart
14198 ChartBase *pTentative_Chart;
14199 pTentative_Chart = ChartData->OpenStackChartConditional(
14200 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14201
14202 if (pTentative_Chart) {
14203 if (m_singleChart) m_singleChart->Deactivate();
14204
14205 m_singleChart = pTentative_Chart;
14206 m_singleChart->Activate();
14207
14208 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14209 GetpCurrentStack(), m_singleChart->GetFullPath());
14210 }
14211
14212 // Setup the view
14213 double zLat, zLon;
14214 if (m_bFollow) {
14215 zLat = gLat;
14216 zLon = gLon;
14217 } else {
14218 zLat = m_vLat;
14219 zLon = m_vLon;
14220 }
14221
14222 double best_scale_ppm = GetBestVPScale(m_singleChart);
14223 double rotation = GetVPRotation();
14224 double oldskew = GetVPSkew();
14225 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14226
14227 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14228 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14229 if (fabs(newskew) > 0.0001) rotation = newskew;
14230 }
14231
14232 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14233
14234 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14235 }
14236
14237 // refresh Piano
14238 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14239 if (idx < 0) return;
14240
14241 std::vector<int> piano_active_chart_index_array;
14242 piano_active_chart_index_array.push_back(
14243 GetpCurrentStack()->GetCurrentEntrydbIndex());
14244 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14245}
14246
14247void ChartCanvas::SelectdbChart(int dbindex) {
14248 if (!GetpCurrentStack()) return;
14249 if (!ChartData) return;
14250
14251 if (dbindex >= 0) {
14252 // Open the new chart
14253 ChartBase *pTentative_Chart;
14254 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14255
14256 if (pTentative_Chart) {
14257 if (m_singleChart) m_singleChart->Deactivate();
14258
14259 m_singleChart = pTentative_Chart;
14260 m_singleChart->Activate();
14261
14262 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14263 GetpCurrentStack(), m_singleChart->GetFullPath());
14264 }
14265
14266 // Setup the view
14267 double zLat, zLon;
14268 if (m_bFollow) {
14269 zLat = gLat;
14270 zLon = gLon;
14271 } else {
14272 zLat = m_vLat;
14273 zLon = m_vLon;
14274 }
14275
14276 double best_scale_ppm = GetBestVPScale(m_singleChart);
14277
14278 if (m_singleChart)
14279 SetViewPoint(zLat, zLon, best_scale_ppm,
14280 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14281
14282 // SetChartUpdatePeriod( );
14283
14284 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14285 }
14286
14287 // TODO refresh_Piano();
14288}
14289
14290void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14291 double target_scale = GetVP().view_scale_ppm;
14292
14293 if (!GetQuiltMode()) {
14294 if (GetpCurrentStack()) {
14295 int stack_index = -1;
14296 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14297 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14298 if (check_dbIndex < 0) continue;
14299 const ChartTableEntry &cte =
14300 ChartData->GetChartTableEntry(check_dbIndex);
14301 if (type == cte.GetChartType()) {
14302 stack_index = i;
14303 break;
14304 } else if (family == cte.GetChartFamily()) {
14305 stack_index = i;
14306 break;
14307 }
14308 }
14309
14310 if (stack_index >= 0) {
14311 SelectChartFromStack(stack_index);
14312 }
14313 }
14314 } else {
14315 int sel_dbIndex = -1;
14316 std::vector<int> piano_chart_index_array =
14317 GetQuiltExtendedStackdbIndexArray();
14318 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14319 int check_dbIndex = piano_chart_index_array[i];
14320 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14321 if (type == cte.GetChartType()) {
14322 if (IsChartQuiltableRef(check_dbIndex)) {
14323 sel_dbIndex = check_dbIndex;
14324 break;
14325 }
14326 } else if (family == cte.GetChartFamily()) {
14327 if (IsChartQuiltableRef(check_dbIndex)) {
14328 sel_dbIndex = check_dbIndex;
14329 break;
14330 }
14331 }
14332 }
14333
14334 if (sel_dbIndex >= 0) {
14335 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14336 // Re-qualify the quilt reference chart selection
14337 AdjustQuiltRefChart();
14338 }
14339
14340 // Now reset the scale to the target...
14341 SetVPScale(target_scale);
14342 }
14343
14344 SetQuiltChartHiLiteIndex(-1);
14345
14346 ReloadVP();
14347}
14348
14349bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14350 return std::find(m_tile_yesshow_index_array.begin(),
14351 m_tile_yesshow_index_array.end(),
14352 index) != m_tile_yesshow_index_array.end();
14353}
14354
14355bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14356 return std::find(m_tile_noshow_index_array.begin(),
14357 m_tile_noshow_index_array.end(),
14358 index) != m_tile_noshow_index_array.end();
14359}
14360
14361void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14362 if (std::find(m_tile_noshow_index_array.begin(),
14363 m_tile_noshow_index_array.end(),
14364 index) == m_tile_noshow_index_array.end()) {
14365 m_tile_noshow_index_array.push_back(index);
14366 }
14367}
14368
14369//-------------------------------------------------------------------------------------------------------
14370//
14371// Piano support
14372//
14373//-------------------------------------------------------------------------------------------------------
14374
14375void ChartCanvas::HandlePianoClick(
14376 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14377 if (g_options && g_options->IsShown())
14378 return; // Piano might be invalid due to chartset updates.
14379 if (!m_pCurrentStack) return;
14380 if (!ChartData) return;
14381
14382 // stop movement or on slow computer we may get something like :
14383 // zoom out with the wheel (timer is set)
14384 // quickly click and display a chart, which may zoom in
14385 // but the delayed timer fires first and it zooms out again!
14386 StopMovement();
14387
14388 // When switching by piano key click, we may appoint the new target chart to
14389 // be any chart in the composite array.
14390 // As an improvement to UX, find the chart that is "closest" to the current
14391 // vp,
14392 // and select that chart. This will cause a jump to the centroid of that
14393 // chart
14394
14395 double distance = 25000; // RTW
14396 int closest_index = -1;
14397 for (int chart_index : selected_dbIndex_array) {
14398 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14399 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14400 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14401
14402 // measure distance as Manhattan style
14403 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14404 if (test_distance < distance) {
14405 distance = test_distance;
14406 closest_index = chart_index;
14407 }
14408 }
14409
14410 int selected_dbIndex = selected_dbIndex_array[0];
14411 if (closest_index >= 0) selected_dbIndex = closest_index;
14412
14413 if (!GetQuiltMode()) {
14414 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14415 if (IsChartQuiltableRef(selected_dbIndex)) {
14416 ToggleCanvasQuiltMode();
14417 SelectQuiltRefdbChart(selected_dbIndex);
14418 m_bpersistent_quilt = false;
14419 } else {
14420 SelectChartFromStack(selected_index);
14421 }
14422 } else {
14423 SelectChartFromStack(selected_index);
14424 g_sticky_chart = selected_dbIndex;
14425 }
14426
14427 if (m_singleChart)
14428 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14429 } else {
14430 // Handle MBTiles overlays first
14431 // Left click simply toggles the noshow array index entry
14432 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14433 bool bfound = false;
14434 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14435 if (m_tile_noshow_index_array[i] ==
14436 selected_dbIndex) { // chart is in the noshow list
14437 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14438 i); // erase it
14439 bfound = true;
14440 break;
14441 }
14442 }
14443 if (!bfound) {
14444 m_tile_noshow_index_array.push_back(selected_dbIndex);
14445 }
14446
14447 // If not already present, add this tileset to the "yes_show" array.
14448 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14449 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14450 }
14451
14452 else {
14453 if (IsChartQuiltableRef(selected_dbIndex)) {
14454 // if( ChartData ) ChartData->PurgeCache();
14455
14456 // If the chart is a vector chart, and of very large scale,
14457 // then we had better set the new scale directly to avoid excessive
14458 // underzoom on, eg, Inland ENCs
14459 bool set_scale = false;
14460 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14461 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14462 set_scale = true;
14463 }
14464 }
14465
14466 if (!set_scale) {
14467 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14468 } else {
14469 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14470
14471 // Adjust scale so that the selected chart is underzoomed/overzoomed
14472 // by a controlled amount
14473 ChartBase *pc =
14474 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14475 if (pc) {
14476 double proposed_scale_onscreen =
14478
14479 if (g_bPreserveScaleOnX) {
14480 proposed_scale_onscreen =
14481 wxMin(proposed_scale_onscreen,
14482 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14483 GetCanvasWidth()));
14484 } else {
14485 proposed_scale_onscreen =
14486 wxMin(proposed_scale_onscreen,
14487 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14488 GetCanvasWidth()));
14489
14490 proposed_scale_onscreen =
14491 wxMax(proposed_scale_onscreen,
14492 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14494 }
14495
14496 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14497 }
14498 }
14499 } else {
14500 ToggleCanvasQuiltMode();
14501 SelectdbChart(selected_dbIndex);
14502 m_bpersistent_quilt = true;
14503 }
14504 }
14505 }
14506
14507 SetQuiltChartHiLiteIndex(-1);
14508 // update the state of the menu items (checkmarks etc)
14509 top_frame::Get()->UpdateGlobalMenuItems();
14510 HideChartInfoWindow();
14511 DoCanvasUpdate();
14512 ReloadVP(); // Pick up the new selections
14513}
14514
14515void ChartCanvas::HandlePianoRClick(
14516 int x, int y, int selected_index,
14517 const std::vector<int> &selected_dbIndex_array) {
14518 if (g_options && g_options->IsShown())
14519 return; // Piano might be invalid due to chartset updates.
14520 if (!GetpCurrentStack()) return;
14521
14522 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14523 UpdateCanvasControlBar();
14524
14525 SetQuiltChartHiLiteIndex(-1);
14526}
14527
14528void ChartCanvas::HandlePianoRollover(
14529 int selected_index, const std::vector<int> &selected_dbIndex_array,
14530 int n_charts, int scale) {
14531 if (g_options && g_options->IsShown())
14532 return; // Piano might be invalid due to chartset updates.
14533 if (!GetpCurrentStack()) return;
14534 if (!ChartData) return;
14535
14536 if (ChartData->IsBusy()) return;
14537
14538 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14539
14540 if (!GetQuiltMode()) {
14541 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14542 } else {
14543 // Select the correct vector
14544 std::vector<int> piano_chart_index_array;
14545 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14546 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14547 if ((GetpCurrentStack()->nEntry > 1) ||
14548 (piano_chart_index_array.size() >= 1)) {
14549 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14550
14551 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14552 ReloadVP(false); // no VP adjustment allowed
14553 } else if (GetpCurrentStack()->nEntry == 1) {
14554 const ChartTableEntry &cte =
14555 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14556 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14557 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14558 ReloadVP(false);
14559 } else if ((-1 == selected_index) &&
14560 (0 == selected_dbIndex_array.size())) {
14561 ShowChartInfoWindow(key_location.x, -1);
14562 }
14563 }
14564 } else {
14565 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14566
14567 if ((GetpCurrentStack()->nEntry > 1) ||
14568 (piano_chart_index_array.size() >= 1)) {
14569 if (n_charts > 1)
14570 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14571 selected_dbIndex_array);
14572 else if (n_charts == 1)
14573 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14574
14575 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14576 ReloadVP(false); // no VP adjustment allowed
14577 }
14578 }
14579 }
14580}
14581
14582void ChartCanvas::ClearPianoRollover() {
14583 ClearQuiltChartHiLiteIndexArray();
14584 ShowChartInfoWindow(0, -1);
14585 std::vector<int> vec;
14586 ShowCompositeInfoWindow(0, 0, 0, vec);
14587 ReloadVP(false);
14588}
14589
14590void ChartCanvas::UpdateCanvasControlBar() {
14591 if (m_pianoFrozen) return;
14592
14593 if (!GetpCurrentStack()) return;
14594 if (!ChartData) return;
14595 if (!g_bShowChartBar) return;
14596
14597 int sel_type = -1;
14598 int sel_family = -1;
14599
14600 std::vector<int> piano_chart_index_array;
14601 std::vector<int> empty_piano_chart_index_array;
14602
14603 wxString old_hash = m_Piano->GetStoredHash();
14604
14605 if (GetQuiltMode()) {
14606 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14607 GetQuiltFullScreendbIndexArray());
14608
14609 std::vector<int> piano_active_chart_index_array =
14610 GetQuiltCandidatedbIndexArray();
14611 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14612
14613 std::vector<int> piano_eclipsed_chart_index_array =
14614 GetQuiltEclipsedStackdbIndexArray();
14615 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14616
14617 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14618 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14619
14620 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14621 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14622 } else {
14623 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14624 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14625 // TODO refresh_Piano();
14626
14627 if (m_singleChart) {
14628 sel_type = m_singleChart->GetChartType();
14629 sel_family = m_singleChart->GetChartFamily();
14630 }
14631 }
14632
14633 // Set up the TMerc and Skew arrays
14634 std::vector<int> piano_skew_chart_index_array;
14635 std::vector<int> piano_tmerc_chart_index_array;
14636 std::vector<int> piano_poly_chart_index_array;
14637
14638 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14639 const ChartTableEntry &ctei =
14640 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14641 double skew_norm = ctei.GetChartSkew();
14642 if (skew_norm > 180.) skew_norm -= 360.;
14643
14644 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14645 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14646
14647 // Polyconic skewed charts should show as skewed
14648 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14649 if (fabs(skew_norm) > 1.)
14650 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14651 else
14652 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14653 } else if (fabs(skew_norm) > 1.)
14654 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14655 }
14656 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14657 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14658 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14659
14660 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14661 if (new_hash != old_hash) {
14662 m_Piano->FormatKeys();
14663 HideChartInfoWindow();
14664 m_Piano->ResetRollover();
14665 SetQuiltChartHiLiteIndex(-1);
14666 m_brepaint_piano = true;
14667 }
14668
14669 // Create a bitmask int that describes what Family/Type of charts are shown in
14670 // the bar, and notify the platform.
14671 int mask = 0;
14672 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14673 const ChartTableEntry &ctei =
14674 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14675 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14676 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14677 if (e == CHART_FAMILY_RASTER) mask |= 1;
14678 if (e == CHART_FAMILY_VECTOR) {
14679 if (t == CHART_TYPE_CM93COMP)
14680 mask |= 4;
14681 else
14682 mask |= 2;
14683 }
14684 }
14685
14686 wxString s_indicated;
14687 if (sel_type == CHART_TYPE_CM93COMP)
14688 s_indicated = "cm93";
14689 else {
14690 if (sel_family == CHART_FAMILY_RASTER)
14691 s_indicated = "raster";
14692 else if (sel_family == CHART_FAMILY_VECTOR)
14693 s_indicated = "vector";
14694 }
14695
14696 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14697}
14698
14699void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14700
14701void ChartCanvas::PianoPopupMenu(
14702 int x, int y, int selected_index,
14703 const std::vector<int> &selected_dbIndex_array) {
14704 if (!GetpCurrentStack()) return;
14705
14706 // No context menu if quilting is disabled
14707 if (!GetQuiltMode()) return;
14708
14709 m_piano_ctx_menu = new wxMenu();
14710
14711 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14712 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14713 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14714 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14715 } else {
14716 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14717 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14718 // wxEVT_COMMAND_MENU_SELECTED,
14719 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14720
14721 menu_selected_dbIndex = selected_dbIndex_array[0];
14722 menu_selected_index = selected_index;
14723
14724 // Search the no-show array
14725 bool b_is_in_noshow = false;
14726 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14727 if (m_quilt_noshow_index_array[i] ==
14728 menu_selected_dbIndex) // chart is in the noshow list
14729 {
14730 b_is_in_noshow = true;
14731 break;
14732 }
14733 }
14734
14735 if (b_is_in_noshow) {
14736 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14737 _("Show This Chart"));
14738 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14739 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14740 } else if (GetpCurrentStack()->nEntry > 1) {
14741 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14742 _("Hide This Chart"));
14743 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14744 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14745 }
14746 }
14747
14748 wxPoint pos = wxPoint(x, y - 30);
14749
14750 // Invoke the drop-down menu
14751 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14752 PopupMenu(m_piano_ctx_menu, pos);
14753
14754 delete m_piano_ctx_menu;
14755 m_piano_ctx_menu = NULL;
14756
14757 HideChartInfoWindow();
14758 m_Piano->ResetRollover();
14759
14760 SetQuiltChartHiLiteIndex(-1);
14761 ClearQuiltChartHiLiteIndexArray();
14762
14763 ReloadVP();
14764}
14765
14766void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14767 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14768 if (m_quilt_noshow_index_array[i] ==
14769 menu_selected_dbIndex) // chart is in the noshow list
14770 {
14771 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14772 break;
14773 }
14774 }
14775}
14776
14777void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14778 if (!GetpCurrentStack()) return;
14779 if (!ChartData) return;
14780
14781 RemoveChartFromQuilt(menu_selected_dbIndex);
14782
14783 // It could happen that the chart being disabled is the reference
14784 // chart....
14785 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14786 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14787
14788 int i = menu_selected_index + 1; // select next smaller scale chart
14789 bool b_success = false;
14790 while (i < GetpCurrentStack()->nEntry - 1) {
14791 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14792 if (type == ChartData->GetDBChartType(dbIndex)) {
14793 SelectQuiltRefChart(i);
14794 b_success = true;
14795 break;
14796 }
14797 i++;
14798 }
14799
14800 // If that did not work, try to select the next larger scale compatible
14801 // chart
14802 if (!b_success) {
14803 i = menu_selected_index - 1;
14804 while (i > 0) {
14805 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14806 if (type == ChartData->GetDBChartType(dbIndex)) {
14807 SelectQuiltRefChart(i);
14808 b_success = true;
14809 break;
14810 }
14811 i--;
14812 }
14813 }
14814 }
14815}
14816
14817void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14818 // Remove the item from the list (if it appears) to avoid multiple addition
14819 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14820 if (m_quilt_noshow_index_array[i] ==
14821 dbIndex) // chart is already in the noshow list
14822 {
14823 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14824 break;
14825 }
14826 }
14827
14828 m_quilt_noshow_index_array.push_back(dbIndex);
14829}
14830
14831bool ChartCanvas::UpdateS52State() {
14832 bool retval = false;
14833
14834 if (ps52plib) {
14835 ps52plib->SetShowS57Text(m_encShowText);
14836 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14837 ps52plib->m_bShowSoundg = m_encShowDepth;
14838 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14839 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14840
14841 // Lights
14842 if (!m_encShowLights) // On, going off
14843 ps52plib->AddObjNoshow("LIGHTS");
14844 else // Off, going on
14845 ps52plib->RemoveObjNoshow("LIGHTS");
14846 ps52plib->SetLightsOff(!m_encShowLights);
14847 ps52plib->m_bExtendLightSectors = true;
14848
14849 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14850 ps52plib->SetAnchorOn(m_encShowAnchor);
14851 ps52plib->SetQualityOfData(m_encShowDataQual);
14852 }
14853
14854 return retval;
14855}
14856
14857void ChartCanvas::SetShowENCDataQual(bool show) {
14858 m_encShowDataQual = show;
14859 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14860 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14861
14862 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14863}
14864
14865void ChartCanvas::SetShowENCText(bool show) {
14866 m_encShowText = show;
14867 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14868 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14869
14870 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14871}
14872
14873void ChartCanvas::SetENCDisplayCategory(int category) {
14874 m_encDisplayCategory = category;
14875 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14876}
14877
14878void ChartCanvas::SetShowENCDepth(bool show) {
14879 m_encShowDepth = show;
14880 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14881 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14882
14883 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14884}
14885
14886void ChartCanvas::SetShowENCLightDesc(bool show) {
14887 m_encShowLightDesc = show;
14888 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14889 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14890
14891 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14892}
14893
14894void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14895 m_encShowBuoyLabels = show;
14896 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14897}
14898
14899void ChartCanvas::SetShowENCLights(bool show) {
14900 m_encShowLights = show;
14901 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14902 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14903
14904 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14905}
14906
14907void ChartCanvas::SetShowENCAnchor(bool show) {
14908 m_encShowAnchor = show;
14909 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14910 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14911
14912 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14913}
14914
14915wxRect ChartCanvas::GetMUIBarRect() {
14916 wxRect rv;
14917 if (m_muiBar) {
14918 rv = m_muiBar->GetRect();
14919 }
14920
14921 return rv;
14922}
14923
14924void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14925 if (!GetAlertString().IsEmpty()) {
14926 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14927 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14928
14929 dc.SetFont(*pfont);
14930 dc.SetPen(*wxTRANSPARENT_PEN);
14931
14932 dc.SetBrush(wxColour(243, 229, 47));
14933 int w, h;
14934 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14935 h += 2;
14936 // int yp = vp.pix_height - 20 - h;
14937
14938 wxRect sbr = GetScaleBarRect();
14939 int xp = sbr.x + sbr.width + 10;
14940 int yp = (sbr.y + sbr.height) - h;
14941
14942 int wdraw = w + 10;
14943 dc.DrawRectangle(xp, yp, wdraw, h);
14944 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14945 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14946 }
14947}
14948
14949//--------------------------------------------------------------------------------------------------------
14950// Screen Brightness Control Support Routines
14951//
14952//--------------------------------------------------------------------------------------------------------
14953
14954#ifdef __UNIX__
14955#define BRIGHT_XCALIB
14956#define __OPCPN_USEICC__
14957#endif
14958
14959#ifdef __OPCPN_USEICC__
14960int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14961 double co_green, double co_blue);
14962
14963wxString temp_file_name;
14964#endif
14965
14966#if 0
14967class ocpnCurtain: public wxDialog
14968{
14969 DECLARE_CLASS( ocpnCurtain )
14970 DECLARE_EVENT_TABLE()
14971
14972public:
14973 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14974 ~ocpnCurtain( );
14975 bool ProcessEvent(wxEvent& event);
14976
14977};
14978
14979IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14980
14981BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14982END_EVENT_TABLE()
14983
14984ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14985{
14986 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14987}
14988
14989ocpnCurtain::~ocpnCurtain()
14990{
14991}
14992
14993bool ocpnCurtain::ProcessEvent(wxEvent& event)
14994{
14995 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14996 return GetParent()->GetEventHandler()->ProcessEvent(event);
14997}
14998#endif
14999
15000#ifdef _WIN32
15001#include <windows.h>
15002
15003HMODULE hGDI32DLL;
15004typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15005typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15006SetDeviceGammaRamp_ptr_type
15007 g_pSetDeviceGammaRamp; // the API entry points in the dll
15008GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15009
15010WORD *g_pSavedGammaMap;
15011
15012#endif
15013
15014int InitScreenBrightness() {
15015#ifdef _WIN32
15016#ifdef ocpnUSE_GL
15017 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15018 HDC hDC;
15019 BOOL bbr;
15020
15021 if (NULL == hGDI32DLL) {
15022 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15023
15024 if (NULL != hGDI32DLL) {
15025 // Get the entry points of the required functions
15026 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15027 hGDI32DLL, "SetDeviceGammaRamp");
15028 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15029 hGDI32DLL, "GetDeviceGammaRamp");
15030
15031 // If the functions are not found, unload the DLL and return false
15032 if ((NULL == g_pSetDeviceGammaRamp) ||
15033 (NULL == g_pGetDeviceGammaRamp)) {
15034 FreeLibrary(hGDI32DLL);
15035 hGDI32DLL = NULL;
15036 return 0;
15037 }
15038 }
15039 }
15040
15041 // Interface is ready, so....
15042 // Get some storage
15043 if (!g_pSavedGammaMap) {
15044 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15045
15046 hDC = GetDC(NULL); // Get the full screen DC
15047 bbr = g_pGetDeviceGammaRamp(
15048 hDC, g_pSavedGammaMap); // Get the existing ramp table
15049 ReleaseDC(NULL, hDC); // Release the DC
15050 }
15051
15052 // On Windows hosts, try to adjust the registry to allow full range
15053 // setting of Gamma table This is an undocumented Windows hack.....
15054 wxRegKey *pRegKey = new wxRegKey(
15055 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15056 "NT\\CurrentVersion\\ICM");
15057 if (!pRegKey->Exists()) pRegKey->Create();
15058 pRegKey->SetValue("GdiIcmGammaRange", 256);
15059
15060 g_brightness_init = true;
15061 return 1;
15062 }
15063#endif
15064
15065 {
15066 if (NULL == g_pcurtain) {
15067 if (top_frame::Get()->CanSetTransparent()) {
15068 // Build the curtain window
15069 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15070 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15071 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15072 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15073
15074 // g_pcurtain = new ocpnCurtain(gFrame,
15075 // wxPoint(0,0),::wxGetDisplaySize(),
15076 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15077 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15078
15079 g_pcurtain->Hide();
15080
15081 HWND hWnd = GetHwndOf(g_pcurtain);
15082 SetWindowLong(hWnd, GWL_EXSTYLE,
15083 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15084 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15085 g_pcurtain->SetTransparent(0);
15086
15087 g_pcurtain->Maximize();
15088 g_pcurtain->Show();
15089
15090 // All of this is obtuse, but necessary for Windows...
15091 g_pcurtain->Enable();
15092 g_pcurtain->Disable();
15093
15094 top_frame::Get()->Disable();
15095 top_frame::Get()->Enable();
15096 // SetFocus();
15097 }
15098 }
15099 g_brightness_init = true;
15100
15101 return 1;
15102 }
15103#else
15104 // Look for "xcalib" application
15105 wxString cmd("xcalib -version");
15106
15107 wxArrayString output;
15108 long r = wxExecute(cmd, output);
15109 if (0 != r)
15110 wxLogMessage(
15111 " External application \"xcalib\" not found. Screen brightness "
15112 "not changed.");
15113
15114 g_brightness_init = true;
15115 return 0;
15116#endif
15117}
15118
15119int RestoreScreenBrightness() {
15120#ifdef _WIN32
15121
15122 if (g_pSavedGammaMap) {
15123 HDC hDC = GetDC(NULL); // Get the full screen DC
15124 g_pSetDeviceGammaRamp(hDC,
15125 g_pSavedGammaMap); // Restore the saved ramp table
15126 ReleaseDC(NULL, hDC); // Release the DC
15127
15128 free(g_pSavedGammaMap);
15129 g_pSavedGammaMap = NULL;
15130 }
15131
15132 if (g_pcurtain) {
15133 g_pcurtain->Close();
15134 g_pcurtain->Destroy();
15135 g_pcurtain = NULL;
15136 }
15137
15138 g_brightness_init = false;
15139 return 1;
15140
15141#endif
15142
15143#ifdef BRIGHT_XCALIB
15144 if (g_brightness_init) {
15145 wxString cmd;
15146 cmd = "xcalib -clear";
15147 wxExecute(cmd, wxEXEC_ASYNC);
15148 g_brightness_init = false;
15149 }
15150
15151 return 1;
15152#endif
15153
15154 return 0;
15155}
15156
15157// Set brightness. [0..100]
15158int SetScreenBrightness(int brightness) {
15159#ifdef _WIN32
15160
15161 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15162 // some (most modern?) versions of gdi32.dll Load the required library dll,
15163 // if not already in place
15164#ifdef ocpnUSE_GL
15165 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15166 if (g_pcurtain) {
15167 g_pcurtain->Close();
15168 g_pcurtain->Destroy();
15169 g_pcurtain = NULL;
15170 }
15171
15172 InitScreenBrightness();
15173
15174 if (NULL == hGDI32DLL) {
15175 // Unicode stuff.....
15176 wchar_t wdll_name[80];
15177 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15178 LPCWSTR cstr = wdll_name;
15179
15180 hGDI32DLL = LoadLibrary(cstr);
15181
15182 if (NULL != hGDI32DLL) {
15183 // Get the entry points of the required functions
15184 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15185 hGDI32DLL, "SetDeviceGammaRamp");
15186 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15187 hGDI32DLL, "GetDeviceGammaRamp");
15188
15189 // If the functions are not found, unload the DLL and return false
15190 if ((NULL == g_pSetDeviceGammaRamp) ||
15191 (NULL == g_pGetDeviceGammaRamp)) {
15192 FreeLibrary(hGDI32DLL);
15193 hGDI32DLL = NULL;
15194 return 0;
15195 }
15196 }
15197 }
15198
15199 HDC hDC = GetDC(NULL); // Get the full screen DC
15200
15201 /*
15202 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15203 if (cmcap != CM_GAMMA_RAMP)
15204 {
15205 wxLogMessage(" Video hardware does not support brightness control by
15206 gamma ramp adjustment."); return false;
15207 }
15208 */
15209
15210 int increment = brightness * 256 / 100;
15211
15212 // Build the Gamma Ramp table
15213 WORD GammaTable[3][256];
15214
15215 int table_val = 0;
15216 for (int i = 0; i < 256; i++) {
15217 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15218 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15219 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15220
15221 table_val += increment;
15222
15223 if (table_val > 65535) table_val = 65535;
15224 }
15225
15226 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15227 ReleaseDC(NULL, hDC); // Release the DC
15228
15229 return 1;
15230 }
15231#endif
15232
15233 {
15234 if (g_pSavedGammaMap) {
15235 HDC hDC = GetDC(NULL); // Get the full screen DC
15236 g_pSetDeviceGammaRamp(hDC,
15237 g_pSavedGammaMap); // Restore the saved ramp table
15238 ReleaseDC(NULL, hDC); // Release the DC
15239 }
15240
15241 if (brightness < 100) {
15242 if (NULL == g_pcurtain) InitScreenBrightness();
15243
15244 if (g_pcurtain) {
15245 int sbrite = wxMax(1, brightness);
15246 sbrite = wxMin(100, sbrite);
15247
15248 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15249 }
15250 } else {
15251 if (g_pcurtain) {
15252 g_pcurtain->Close();
15253 g_pcurtain->Destroy();
15254 g_pcurtain = NULL;
15255 }
15256 }
15257
15258 return 1;
15259 }
15260
15261#endif
15262
15263#ifdef BRIGHT_XCALIB
15264
15265 if (!g_brightness_init) {
15266 last_brightness = 100;
15267 g_brightness_init = true;
15268 temp_file_name = wxFileName::CreateTempFileName("");
15269 InitScreenBrightness();
15270 }
15271
15272#ifdef __OPCPN_USEICC__
15273 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15274 // desired, and then activate this temporary profile using xcalib <filename>
15275 if (!CreateSimpleICCProfileFile(
15276 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15277 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15278 wxString cmd("xcalib ");
15279 cmd += temp_file_name;
15280
15281 wxExecute(cmd, wxEXEC_ASYNC);
15282 }
15283
15284#else
15285 // Or, use "xcalib -co" to set overall contrast value
15286 // This is not as nice, since the -co parameter wants to be a fraction of
15287 // the current contrast, and values greater than 100 are not allowed. As a
15288 // result, increases of contrast must do a "-clear" step first, which
15289 // produces objectionable flashing.
15290 if (brightness > last_brightness) {
15291 wxString cmd;
15292 cmd = "xcalib -clear";
15293 wxExecute(cmd, wxEXEC_ASYNC);
15294
15295 ::wxMilliSleep(10);
15296
15297 int brite_adj = wxMax(1, brightness);
15298 cmd.Printf("xcalib -co %2d -a", brite_adj);
15299 wxExecute(cmd, wxEXEC_ASYNC);
15300 } else {
15301 int brite_adj = wxMax(1, brightness);
15302 int factor = (brite_adj * 100) / last_brightness;
15303 factor = wxMax(1, factor);
15304 wxString cmd;
15305 cmd.Printf("xcalib -co %2d -a", factor);
15306 wxExecute(cmd, wxEXEC_ASYNC);
15307 }
15308
15309#endif
15310
15311 last_brightness = brightness;
15312
15313#endif
15314
15315 return 0;
15316}
15317
15318#ifdef __OPCPN_USEICC__
15319
15320#define MLUT_TAG 0x6d4c5554L
15321#define VCGT_TAG 0x76636774L
15322
15323int GetIntEndian(unsigned char *s) {
15324 int ret;
15325 unsigned char *p;
15326 int i;
15327
15328 p = (unsigned char *)&ret;
15329
15330 if (1)
15331 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15332 else
15333 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15334
15335 return ret;
15336}
15337
15338unsigned short GetShortEndian(unsigned char *s) {
15339 unsigned short ret;
15340 unsigned char *p;
15341 int i;
15342
15343 p = (unsigned char *)&ret;
15344
15345 if (1)
15346 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15347 else
15348 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15349
15350 return ret;
15351}
15352
15353// Create a very simple Gamma correction file readable by xcalib
15354int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15355 double co_green, double co_blue) {
15356 FILE *fp;
15357
15358 if (file_name) {
15359 fp = fopen(file_name, "wb");
15360 if (!fp) return -1; /* file can not be created */
15361 } else
15362 return -1; /* filename char pointer not valid */
15363
15364 // Write header
15365 char header[128];
15366 for (int i = 0; i < 128; i++) header[i] = 0;
15367
15368 fwrite(header, 128, 1, fp);
15369
15370 // Num tags
15371 int numTags0 = 1;
15372 int numTags = GetIntEndian((unsigned char *)&numTags0);
15373 fwrite(&numTags, 1, 4, fp);
15374
15375 int tagName0 = VCGT_TAG;
15376 int tagName = GetIntEndian((unsigned char *)&tagName0);
15377 fwrite(&tagName, 1, 4, fp);
15378
15379 int tagOffset0 = 128 + 4 * sizeof(int);
15380 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15381 fwrite(&tagOffset, 1, 4, fp);
15382
15383 int tagSize0 = 1;
15384 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15385 fwrite(&tagSize, 1, 4, fp);
15386
15387 fwrite(&tagName, 1, 4, fp); // another copy of tag
15388
15389 fwrite(&tagName, 1, 4, fp); // dummy
15390
15391 // Table type
15392
15393 /* VideoCardGammaTable (The simplest type) */
15394 int gammatype0 = 0;
15395 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15396 fwrite(&gammatype, 1, 4, fp);
15397
15398 int numChannels0 = 3;
15399 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15400 fwrite(&numChannels, 1, 2, fp);
15401
15402 int numEntries0 = 256;
15403 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15404 fwrite(&numEntries, 1, 2, fp);
15405
15406 int entrySize0 = 1;
15407 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15408 fwrite(&entrySize, 1, 2, fp);
15409
15410 unsigned char ramp[256];
15411
15412 // Red ramp
15413 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15414 fwrite(ramp, 256, 1, fp);
15415
15416 // Green ramp
15417 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15418 fwrite(ramp, 256, 1, fp);
15419
15420 // Blue ramp
15421 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15422 fwrite(ramp, 256, 1, fp);
15423
15424 fclose(fp);
15425
15426 return 0;
15427}
15428#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:838
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:60
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:13791
bool GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates rounded to nearest integer using specified vie...
Definition chcanv.cpp:4551
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11835
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4547
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3643
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13750
void GetDoubleCanvasPointPixVP(ViewPort &vp, double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision,...
Definition chcanv.cpp:4497
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:805
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2343
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7999
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7808
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5079
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:896
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4628
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5359
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:183
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:789
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4572
bool IsTideDialogOpen() const
Definition chcanv.cpp:13789
void ZoomCanvas(double factor, bool can_zoom_to_cursor=true, bool stoptimer=true)
Perform a smooth zoom operation on the chart canvas by the specified factor.
Definition chcanv.cpp:4634
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13746
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4492
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5378
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10244
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:483
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:1779
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h: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.
Definition routeman.cpp:998
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Display utilities.
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:76
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Miscellaneous utilities, many of which string related.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:183
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:187
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.