OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/own_ship.h"
58#include "model/plugin_comm.h"
59#include "model/route.h"
60#include "model/routeman.h"
61#include "model/select.h"
62#include "model/select_item.h"
63#include "model/track.h"
64
65#include "ais.h"
68#include "canvas_config.h"
69#include "canvas_menu.h"
70#include "canvas_options.h"
71#include "chartdb.h"
72#include "chartimg.h"
73#include "chcanv.h"
74#include "ch_info_win.h"
75#include "cm93.h" // for chart outline draw
76#include "compass.h"
77#include "concanv.h"
78#include "detail_slider.h"
79#include "hotkeys_dlg.h"
80#include "font_mgr.h"
81#include "gl_texture_descr.h"
82#include "go_to_position_dlg.h"
83#include "gshhs.h"
84#include "ienc_toolbar.h"
85#include "kml.h"
86#include "line_clip.h"
87#include "mark_info.h"
88#include "mbtiles.h"
89#include "mui_bar.h"
90#include "navutil.h"
91#include "ocpn_aui_manager.h"
92#include "ocpndc.h"
93#include "ocpn_frame.h"
94#include "ocpn_pixel.h"
95#include "ocpn_region.h"
96#include "options.h"
97#include "piano.h"
98#include "pluginmanager.h"
99#include "quilt.h"
100#include "route_gui.h"
101#include "routemanagerdialog.h"
102#include "route_point_gui.h"
103#include "route_prop_dlg_impl.h"
104#include "s52plib.h"
105#include "s52utils.h"
106#include "s57_query_dlg.h"
107#include "s57chart.h" // for ArrayOfS57Obj
108#include "shapefile_basemap.h"
109#include "styles.h"
110#include "tcmgr.h"
111#include "tc_win.h"
112#include "thumbwin.h"
113#include "tide_time.h"
114#include "timers.h"
115#include "toolbar.h"
116#include "track_gui.h"
117#include "track_prop_dlg.h"
118#include "undo.h"
119
120#include "s57_ocpn_utils.h"
121
122#ifdef __ANDROID__
123#include "androidUTIL.h"
124#endif
125
126#ifdef ocpnUSE_GL
127#include "gl_chart_canvas.h"
130#endif
131
132#ifdef __VISUALC__
133#include <wx/msw/msvcrt.h>
134#endif
135
136#ifndef __WXMSW__
137#include <signal.h>
138#include <setjmp.h>
139#endif
140
141#ifdef __WXMSW__
142#define printf printf2
143
144int __cdecl printf2(const char *format, ...) {
145 char str[1024];
146
147 va_list argptr;
148 va_start(argptr, format);
149 int ret = vsnprintf(str, sizeof(str), format, argptr);
150 va_end(argptr);
151 OutputDebugStringA(str);
152 return ret;
153}
154#endif
155
156#if defined(__MSVC__) && (_MSC_VER < 1700)
157#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
158#endif
159
160// Define to enable the invocation of a temporary menubar by pressing the Alt
161// key. Not implemented for Windows XP, as it interferes with Alt-Tab
162// processing.
163#define OCPN_ALT_MENUBAR 1
164
165// Profiling support
166// #include "/usr/include/valgrind/callgrind.h"
167
168// ----------------------------------------------------------------------------
169// Useful Prototypes
170// ----------------------------------------------------------------------------
171extern ColorScheme global_color_scheme; // library dependence
172extern wxColor GetDimColor(wxColor c); // library dependence
173
174arrayofCanvasPtr g_canvasArray;
176static bool g_bSmoothRecenter = true;
177static bool bDrawCurrentValues;
187static int mouse_x;
197static int mouse_y;
198static bool mouse_leftisdown;
199static bool g_brouteCreating;
200static int r_gamma_mult;
201static int g_gamma_mult;
202static int b_gamma_mult;
203static int gamma_state;
204static bool g_brightness_init;
205static int last_brightness;
206static wxGLContext *g_pGLcontext; // shared common context
207
208// "Curtain" mode parameters
209static wxDialog *g_pcurtain;
210
211static wxString g_lastS52PLIBPluginMessage;
212
213#define MIN_BRIGHT 10
214#define MAX_BRIGHT 100
215
216//------------------------------------------------------------------------------
217// ChartCanvas Implementation
218//------------------------------------------------------------------------------
219BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
220EVT_PAINT(ChartCanvas::OnPaint)
221EVT_ACTIVATE(ChartCanvas::OnActivate)
222EVT_SIZE(ChartCanvas::OnSize)
223#ifndef HAVE_WX_GESTURE_EVENTS
224EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
225#endif
226EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
227EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
228EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
229EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
230EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
231EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
232EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
233EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
234EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
235EVT_KEY_UP(ChartCanvas::OnKeyUp)
236EVT_CHAR(ChartCanvas::OnKeyChar)
237EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
238EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
239EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
240EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
241EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
242EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
243EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
244EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
245EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
246EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
247
248END_EVENT_TABLE()
249
250// Define a constructor for my canvas
251ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
252 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
253 m_nmea_log(nmea_log) {
254 parent_frame = (MyFrame *)frame; // save a pointer to parent
255 m_canvasIndex = canvasIndex;
256
257 pscratch_bm = NULL;
258
259 SetBackgroundColour(wxColour(0, 0, 0));
260 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
261 // color scheme change
262
263 m_groupIndex = 0;
264 m_bDrawingRoute = false;
265 m_bRouteEditing = false;
266 m_bMarkEditing = false;
267 m_bRoutePoinDragging = false;
268 m_bIsInRadius = false;
269 m_bMayToggleMenuBar = true;
270
271 m_bFollow = false;
272 m_bShowNavobjects = true;
273 m_bTCupdate = false;
274 m_bAppendingRoute = false; // was true in MSW, why??
275 pThumbDIBShow = NULL;
276 m_bShowCurrent = false;
277 m_bShowTide = false;
278 bShowingCurrent = false;
279 pCwin = NULL;
280 warp_flag = false;
281 m_bzooming = false;
282 m_b_paint_enable = true;
283 m_routeState = 0;
284
285 pss_overlay_bmp = NULL;
286 pss_overlay_mask = NULL;
287 m_bChartDragging = false;
288 m_bMeasure_Active = false;
289 m_bMeasure_DistCircle = false;
290 m_pMeasureRoute = NULL;
291 m_pTrackRolloverWin = NULL;
292 m_pRouteRolloverWin = NULL;
293 m_pAISRolloverWin = NULL;
294 m_bedge_pan = false;
295 m_disable_edge_pan = false;
296 m_dragoffsetSet = false;
297 m_bautofind = false;
298 m_bFirstAuto = true;
299 m_groupIndex = 0;
300 m_singleChart = NULL;
301 m_upMode = NORTH_UP_MODE;
302 m_bShowAIS = true;
303 m_bShowAISScaled = false;
304 m_timed_move_vp_active = false;
305 m_inPinch = false;
306 m_disable_adjust_on_zoom = false;
307
308 m_vLat = 0.;
309 m_vLon = 0.;
310
311 m_pCIWin = NULL;
312
313 m_pSelectedRoute = NULL;
314 m_pSelectedTrack = NULL;
315 m_pRoutePointEditTarget = NULL;
316 m_pFoundPoint = NULL;
317 m_pMouseRoute = NULL;
318 m_prev_pMousePoint = NULL;
319 m_pEditRouteArray = NULL;
320 m_pFoundRoutePoint = NULL;
321 m_FinishRouteOnKillFocus = true;
322
323 m_pRolloverRouteSeg = NULL;
324 m_pRolloverTrackSeg = NULL;
325 m_bsectors_shown = false;
326
327 m_bbrightdir = false;
328 r_gamma_mult = 1;
329 g_gamma_mult = 1;
330 b_gamma_mult = 1;
331
332 m_pos_image_user_day = NULL;
333 m_pos_image_user_dusk = NULL;
334 m_pos_image_user_night = NULL;
335 m_pos_image_user_grey_day = NULL;
336 m_pos_image_user_grey_dusk = NULL;
337 m_pos_image_user_grey_night = NULL;
338
339 m_zoom_factor = 1;
340 m_rotation_speed = 0;
341 m_mustmove = 0;
342
343 m_OSoffsetx = 0.;
344 m_OSoffsety = 0.;
345
346 m_pos_image_user_yellow_day = NULL;
347 m_pos_image_user_yellow_dusk = NULL;
348 m_pos_image_user_yellow_night = NULL;
349
350 SetOwnShipState(SHIP_INVALID);
351
352 undo = new Undo(this);
353
354 VPoint.Invalidate();
355
356 m_glcc = NULL;
357
358 m_focus_indicator_pix = 1;
359
360 m_pCurrentStack = NULL;
361 m_bpersistent_quilt = false;
362 m_piano_ctx_menu = NULL;
363 m_Compass = NULL;
364 m_NotificationsList = NULL;
365 m_notification_button = NULL;
366
367 g_ChartNotRenderScaleFactor = 2.0;
368 m_bShowScaleInStatusBar = true;
369
370 m_muiBar = NULL;
371 m_bShowScaleInStatusBar = false;
372 m_show_focus_bar = true;
373
374 m_bShowOutlines = false;
375 m_bDisplayGrid = false;
376 m_bShowDepthUnits = true;
377 m_encDisplayCategory = (int)STANDARD;
378
379 m_encShowLights = true;
380 m_encShowAnchor = true;
381 m_encShowDataQual = false;
382 m_bShowGPS = true;
383 m_pQuilt = new Quilt(this);
384 SetQuiltMode(true);
385 SetAlertString("");
386 m_sector_glat = 0;
387 m_sector_glon = 0;
388 g_PrintingInProgress = false;
389
390#ifdef HAVE_WX_GESTURE_EVENTS
391 m_oldVPSScale = -1.0;
392 m_popupWanted = false;
393 m_leftdown = false;
394#endif /* HAVE_WX_GESTURE_EVENTS */
395 m_inLongPress = false;
396 m_sw_down_time = 0;
397 m_sw_up_time = 0;
398 m_sw_left_down.Start();
399 m_sw_left_up.Start();
400
401 SetupGlCanvas();
402
403 singleClickEventIsValid = false;
404
405 // Build the cursors
406
407 pCursorLeft = NULL;
408 pCursorRight = NULL;
409 pCursorUp = NULL;
410 pCursorDown = NULL;
411 pCursorArrow = NULL;
412 pCursorPencil = NULL;
413 pCursorCross = NULL;
414
415 RebuildCursors();
416
417 SetCursor(*pCursorArrow);
418
419 pPanTimer = new wxTimer(this, m_MouseDragging);
420 pPanTimer->Stop();
421
422 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
423 pMovementTimer->Stop();
424
425 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
426 pMovementStopTimer->Stop();
427
428 pRotDefTimer = new wxTimer(this, ROT_TIMER);
429 pRotDefTimer->Stop();
430
431 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
432 m_DoubleClickTimer->Stop();
433
434 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
435 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
436 m_chart_drag_inertia_active = false;
437
438 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
439 m_animationActive = false;
440 m_menuTimer.SetOwner(this, MENU_TIMER);
441 m_tap_timer.SetOwner(this, TAP_TIMER);
442
443 m_panx = m_pany = 0;
444 m_panspeed = 0;
445 m_panx_target_final = m_pany_target_final = 0;
446 m_panx_target_now = m_pany_target_now = 0;
447 m_DragTrigger = -1;
448
449 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
450 pCurTrackTimer->Stop();
451 m_curtrack_timer_msec = 10;
452
453 m_wheelzoom_stop_oneshot = 0;
454 m_last_wheel_dir = 0;
455
456 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
457
458 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
459
460 m_rollover_popup_timer_msec = 20;
461
462 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
463
464 m_b_rot_hidef = true;
465
466 proute_bm = NULL;
467 m_prot_bm = NULL;
468
469 m_upMode = NORTH_UP_MODE;
470 m_bLookAhead = false;
471
472 // Set some benign initial values
473
474 m_cs = GLOBAL_COLOR_SCHEME_DAY;
475 VPoint.clat = 0;
476 VPoint.clon = 0;
477 VPoint.view_scale_ppm = 1;
478 VPoint.Invalidate();
479 m_nMeasureState = 0;
480 m_ignore_next_leftup = false;
481
482 m_canvas_scale_factor = 1.;
483
484 m_canvas_width = 1000;
485
486 m_overzoomTextWidth = 0;
487 m_overzoomTextHeight = 0;
488
489 // Create the default world chart
490 pWorldBackgroundChart = new GSHHSChart;
491 gShapeBasemap.Reset();
492
493 // Create the default depth unit emboss maps
494 m_pEM_Feet = NULL;
495 m_pEM_Meters = NULL;
496 m_pEM_Fathoms = NULL;
497
498 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
499
500 m_pEM_OverZoom = NULL;
501 SetOverzoomFont();
502 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
503
504 // Build icons for tide/current points
505 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
506 m_bmTideDay =
507 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
508
509 // Dusk
510 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
511
512 // Night
513 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
514
515 // Build Dusk/Night ownship icons
516 double factor_dusk = 0.5;
517 double factor_night = 0.25;
518
519 // Red
520 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
521
522 int rimg_width = m_os_image_red_day.GetWidth();
523 int rimg_height = m_os_image_red_day.GetHeight();
524
525 m_os_image_red_dusk = m_os_image_red_day.Copy();
526 m_os_image_red_night = m_os_image_red_day.Copy();
527
528 for (int iy = 0; iy < rimg_height; iy++) {
529 for (int ix = 0; ix < rimg_width; ix++) {
530 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
531 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
532 m_os_image_red_day.GetGreen(ix, iy),
533 m_os_image_red_day.GetBlue(ix, iy));
534 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
535 hsv.value = hsv.value * factor_dusk;
536 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
537 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
538
539 hsv = wxImage::RGBtoHSV(rgb);
540 hsv.value = hsv.value * factor_night;
541 nrgb = wxImage::HSVtoRGB(hsv);
542 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
543 }
544 }
545 }
546
547 // Grey
548 m_os_image_grey_day =
549 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
550
551 int gimg_width = m_os_image_grey_day.GetWidth();
552 int gimg_height = m_os_image_grey_day.GetHeight();
553
554 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
555 m_os_image_grey_night = m_os_image_grey_day.Copy();
556
557 for (int iy = 0; iy < gimg_height; iy++) {
558 for (int ix = 0; ix < gimg_width; ix++) {
559 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
560 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
561 m_os_image_grey_day.GetGreen(ix, iy),
562 m_os_image_grey_day.GetBlue(ix, iy));
563 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
564 hsv.value = hsv.value * factor_dusk;
565 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
566 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
567
568 hsv = wxImage::RGBtoHSV(rgb);
569 hsv.value = hsv.value * factor_night;
570 nrgb = wxImage::HSVtoRGB(hsv);
571 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
572 }
573 }
574 }
575
576 // Yellow
577 m_os_image_yellow_day = m_os_image_red_day.Copy();
578
579 gimg_width = m_os_image_yellow_day.GetWidth();
580 gimg_height = m_os_image_yellow_day.GetHeight();
581
582 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
583 m_os_image_yellow_night = m_os_image_red_day.Copy();
584
585 for (int iy = 0; iy < gimg_height; iy++) {
586 for (int ix = 0; ix < gimg_width; ix++) {
587 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
588 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
589 m_os_image_yellow_day.GetGreen(ix, iy),
590 m_os_image_yellow_day.GetBlue(ix, iy));
591 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
592 hsv.hue += 60. / 360.; // shift to yellow
593 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
594 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
595
596 hsv = wxImage::RGBtoHSV(rgb);
597 hsv.value = hsv.value * factor_dusk;
598 hsv.hue += 60. / 360.; // shift to yellow
599 nrgb = wxImage::HSVtoRGB(hsv);
600 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
601
602 hsv = wxImage::RGBtoHSV(rgb);
603 hsv.hue += 60. / 360.; // shift to yellow
604 hsv.value = hsv.value * factor_night;
605 nrgb = wxImage::HSVtoRGB(hsv);
606 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
607 }
608 }
609 }
610
611 // Set initial pointers to ownship images
612 m_pos_image_red = &m_os_image_red_day;
613 m_pos_image_yellow = &m_os_image_yellow_day;
614 m_pos_image_grey = &m_os_image_grey_day;
615
616 SetUserOwnship();
617
618 m_pBrightPopup = NULL;
619
620#ifdef ocpnUSE_GL
621 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
622#endif
623
624 SetupGridFont();
625
626 m_Piano = new Piano(this);
627
628 m_bShowCompassWin = true;
629 m_Compass = new ocpnCompass(this);
630 m_Compass->SetScaleFactor(g_compass_scalefactor);
631 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
632
633 if (IsPrimaryCanvas()) {
634 m_notification_button = new NotificationButton(this);
635 m_notification_button->SetScaleFactor(g_compass_scalefactor);
636 m_notification_button->Show(true);
637 }
638
639 m_pianoFrozen = false;
640
641 SetMinSize(wxSize(200, 200));
642
643 m_displayScale = 1.0;
644#if defined(__WXOSX__) || defined(__WXGTK3__)
645 // Support scaled HDPI displays.
646 m_displayScale = GetContentScaleFactor();
647#endif
648 VPoint.SetPixelScale(m_displayScale);
649
650#ifdef HAVE_WX_GESTURE_EVENTS
651 // if (!m_glcc)
652 {
653 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
654 wxLogError("Failed to enable touch events");
655 }
656
657 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
658
659 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
660 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
661
662 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
663 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
664
665 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
666 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
667
668 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
669 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
670 }
671#endif
672
673 // Listen for notification events
674 auto &noteman = NotificationManager::GetInstance();
675
676 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
677 evt_notificationlist_change_listener.Listen(
678 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
679 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
680 if (m_NotificationsList && m_NotificationsList->IsShown()) {
681 m_NotificationsList->ReloadNotificationList();
682 }
683 Refresh();
684 });
685}
686
687ChartCanvas::~ChartCanvas() {
688 delete pThumbDIBShow;
689
690 // Delete Cursors
691 delete pCursorLeft;
692 delete pCursorRight;
693 delete pCursorUp;
694 delete pCursorDown;
695 delete pCursorArrow;
696 delete pCursorPencil;
697 delete pCursorCross;
698
699 delete pPanTimer;
700 delete pMovementTimer;
701 delete pMovementStopTimer;
702 delete pCurTrackTimer;
703 delete pRotDefTimer;
704 delete m_DoubleClickTimer;
705
706 delete m_pTrackRolloverWin;
707 delete m_pRouteRolloverWin;
708 delete m_pAISRolloverWin;
709 delete m_pBrightPopup;
710
711 delete m_pCIWin;
712
713 delete pscratch_bm;
714
715 m_dc_route.SelectObject(wxNullBitmap);
716 delete proute_bm;
717
718 delete pWorldBackgroundChart;
719 delete pss_overlay_bmp;
720
721 delete m_pEM_Feet;
722 delete m_pEM_Meters;
723 delete m_pEM_Fathoms;
724
725 delete m_pEM_OverZoom;
726 // delete m_pEM_CM93Offset;
727
728 delete m_prot_bm;
729
730 delete m_pos_image_user_day;
731 delete m_pos_image_user_dusk;
732 delete m_pos_image_user_night;
733 delete m_pos_image_user_grey_day;
734 delete m_pos_image_user_grey_dusk;
735 delete m_pos_image_user_grey_night;
736 delete m_pos_image_user_yellow_day;
737 delete m_pos_image_user_yellow_dusk;
738 delete m_pos_image_user_yellow_night;
739
740 delete undo;
741#ifdef ocpnUSE_GL
742 if (!g_bdisable_opengl) {
743 delete m_glcc;
744
745#if wxCHECK_VERSION(2, 9, 0)
746 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
747#endif
748 }
749#endif
750
751 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
752 // wx tries to deliver events to this canvas during destroy.
753 MUIBar *muiBar = m_muiBar;
754 m_muiBar = 0;
755 delete muiBar;
756 delete m_pQuilt;
757 delete m_pCurrentStack;
758 delete m_Compass;
759 delete m_Piano;
760 delete m_notification_button;
761}
762
763void ChartCanvas::SetupGridFont() {
764 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
765 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
766 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
767 m_pgridFont = FontMgr::Get().FindOrCreateFont(
768 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
769 FALSE, wxString("Arial"));
770}
771
772void ChartCanvas::RebuildCursors() {
773 delete pCursorLeft;
774 delete pCursorRight;
775 delete pCursorUp;
776 delete pCursorDown;
777 delete pCursorArrow;
778 delete pCursorPencil;
779 delete pCursorCross;
780
781 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
782 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
783
784 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
785
786 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
787 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
788 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
789 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
790 wxImage ICursorPencil =
791 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
792 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
793
794#if !defined(__WXMSW__) && !defined(__WXQT__)
795 ICursorLeft.ConvertAlphaToMask(128);
796 ICursorRight.ConvertAlphaToMask(128);
797 ICursorUp.ConvertAlphaToMask(128);
798 ICursorDown.ConvertAlphaToMask(128);
799 ICursorPencil.ConvertAlphaToMask(10);
800 ICursorCross.ConvertAlphaToMask(10);
801#endif
802
803 if (ICursorLeft.Ok()) {
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
805 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
806 pCursorLeft = new wxCursor(ICursorLeft);
807 } else
808 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
809
810 if (ICursorRight.Ok()) {
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
812 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
813 pCursorRight = new wxCursor(ICursorRight);
814 } else
815 pCursorRight = new wxCursor(wxCURSOR_ARROW);
816
817 if (ICursorUp.Ok()) {
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
819 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
820 pCursorUp = new wxCursor(ICursorUp);
821 } else
822 pCursorUp = new wxCursor(wxCURSOR_ARROW);
823
824 if (ICursorDown.Ok()) {
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
826 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
827 pCursorDown = new wxCursor(ICursorDown);
828 } else
829 pCursorDown = new wxCursor(wxCURSOR_ARROW);
830
831 if (ICursorPencil.Ok()) {
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
833 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
834 pCursorPencil = new wxCursor(ICursorPencil);
835 } else
836 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
837
838 if (ICursorCross.Ok()) {
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
840 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
841 pCursorCross = new wxCursor(ICursorCross);
842 } else
843 pCursorCross = new wxCursor(wxCURSOR_ARROW);
844
845 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
846 pPlugIn_Cursor = NULL;
847}
848
849void ChartCanvas::CanvasApplyLocale() {
850 CreateDepthUnitEmbossMaps(m_cs);
851 CreateOZEmbossMapData(m_cs);
852}
853
854void ChartCanvas::SetupGlCanvas() {
855#ifndef __ANDROID__
856#ifdef ocpnUSE_GL
857 if (!g_bdisable_opengl) {
858 if (g_bopengl) {
859 wxLogMessage("Creating glChartCanvas");
860 m_glcc = new glChartCanvas(this);
861
862 // We use one context for all GL windows, so that textures etc will be
863 // automatically shared
864 if (IsPrimaryCanvas()) {
865 // qDebug() << "Creating Primary Context";
866
867 // wxGLContextAttrs ctxAttr;
868 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
869 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
870 // NULL, &ctxAttr);
871 wxGLContext *pctx = new wxGLContext(m_glcc);
872 m_glcc->SetContext(pctx);
873 g_pGLcontext = pctx; // Save a copy of the common context
874 } else {
875#ifdef __WXOSX__
876 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
877#else
878 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
879 // saved common context
880#endif
881 }
882 }
883 }
884#endif
885#endif
886
887#ifdef __ANDROID__ // ocpnUSE_GL
888 if (!g_bdisable_opengl) {
889 if (g_bopengl) {
890 // qDebug() << "SetupGlCanvas";
891 wxLogMessage("Creating glChartCanvas");
892
893 // We use one context for all GL windows, so that textures etc will be
894 // automatically shared
895 if (IsPrimaryCanvas()) {
896 qDebug() << "Creating Primary glChartCanvas";
897
898 // wxGLContextAttrs ctxAttr;
899 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
900 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
901 // NULL, &ctxAttr);
902 m_glcc = new glChartCanvas(this);
903
904 wxGLContext *pctx = new wxGLContext(m_glcc);
905 m_glcc->SetContext(pctx);
906 g_pGLcontext = pctx; // Save a copy of the common context
907 m_glcc->m_pParentCanvas = this;
908 // m_glcc->Reparent(this);
909 } else {
910 qDebug() << "Creating Secondary glChartCanvas";
911 // QGLContext *pctx =
912 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
913 // << "pctx: " << pctx;
914
915 m_glcc = new glChartCanvas(
916 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
917 // m_glcc = new glChartCanvas(this, pctx); //Shared
918 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
919 wxGLContext *pwxctx = new wxGLContext(m_glcc);
920 m_glcc->SetContext(pwxctx);
921 m_glcc->m_pParentCanvas = this;
922 // m_glcc->Reparent(this);
923 }
924 }
925 }
926#endif
927}
928
929void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
930 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
931
932 // On Android, we get a KillFocus on just about every keystroke.
933 // Why?
934#ifdef __ANDROID__
935 return;
936#endif
937
938 // Special logic:
939 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
940 // canvas focus. Why??? Who knows... So, we provide for this case by
941 // starting a timer if required to actually Finish() a route on a legitimate
942 // focus change, but not if the focus is quickly regained ( <20 msec.) on
943 // this canvas.
944#ifdef __WXOSX__
945 if (m_routeState && m_FinishRouteOnKillFocus)
946 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
947#else
948 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
949#endif
950}
951
952void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
953 m_routeFinishTimer.Stop();
954
955 // Try to keep the global top-line menubar selections up to date with the
956 // current "focus" canvas
957 gFrame->UpdateGlobalMenuItems(this);
958
959 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
960}
961
962void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
963 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
964}
965
966#ifdef HAVE_WX_GESTURE_EVENTS
967void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
968#ifdef __ANDROID__
969 /* we defer the popup menu call upon the leftup event
970 else the menu disappears immediately,
971 (see
972 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
973 */
974 m_popupWanted = true;
975#else
976 m_inLongPress = !g_bhide_context_menus;
977
978 // Send a synthetic mouse left-up event to sync the mouse pan logic.
979 m_menuPos = event.GetPosition();
980 wxMouseEvent ev(wxEVT_LEFT_UP);
981 ev.m_x = m_menuPos.x;
982 ev.m_y = m_menuPos.y;
983 wxPostEvent(this, ev);
984
985 // In touch mode, send a "RIGHT CLICK" event, for plugins
986 if (g_btouch) {
987 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
988 ev_right_click.m_x = m_menuPos.x;
989 ev_right_click.m_y = m_menuPos.y;
990 MouseEvent(ev_right_click);
991 }
992#endif
993}
994
995void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
996 // not implemented yet
997}
998
999void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1000
1001void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1002
1003void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1004#ifdef __WXGTK__
1005 long dt = m_sw_left_up.Time() - m_sw_up_time;
1006 m_sw_up_time = m_sw_left_up.Time();
1007
1008 // printf(" dt %ld\n",dt);
1009 if (dt < 5) {
1010 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1011 // ignore it.
1012 return;
1013 }
1014#endif
1015 // printf("Left_UP\n");
1016
1017 wxPoint pos = event.GetPosition();
1018
1019 m_leftdown = false;
1020
1021 if (!m_popupWanted) {
1022 wxMouseEvent ev(wxEVT_LEFT_UP);
1023 ev.m_x = pos.x;
1024 ev.m_y = pos.y;
1025 MouseEvent(ev);
1026 return;
1027 }
1028
1029 m_popupWanted = false;
1030
1031 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1032 ev.m_x = pos.x;
1033 ev.m_y = pos.y;
1034
1035 MouseEvent(ev);
1036}
1037
1038void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1039 m_leftdown = true;
1040
1041 // Detect and manage multiple left-downs coming from GTK mouse emulation
1042#ifdef __WXGTK__
1043 long dt = m_sw_left_down.Time() - m_sw_down_time;
1044 m_sw_down_time = m_sw_left_down.Time();
1045
1046 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1047
1048 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1049 // The timing between the two events is dependent upon the wxWidgets
1050 // message queue status, and the processing time required for intervening
1051 // events.
1052 // We detect and remove the duplicate events by measuring the elapsed time
1053 // between arrival of events.
1054 // Choose a duplicate detection time long enough to catch worst case time lag
1055 // between duplicating events, but considerably shorter than the nominal
1056 // "intentional double-click" time interval defined generally as 350 msec.
1057 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1058 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1059 // ignore it.
1060 return;
1061 }
1062#endif
1063
1064 // printf("Left_DOWN\n");
1065
1066 // detect and manage double-tap
1067#ifdef __WXGTK__
1068 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1069 2; // Use system setting for distance
1070 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1071 m_lastTapPos.y - max_double_click_distance,
1072 max_double_click_distance * 2, max_double_click_distance * 2);
1073
1074 // A new tap has started, check if it's close enough and in time
1075 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1076 // printf(" TapBump 1\n");
1077 m_tap_count += 1;
1078 } else {
1079 // printf(" TapSet 1\n");
1080 m_tap_count = 1;
1081 m_lastTapPos = event.GetPosition();
1082 m_tap_timer.StartOnce(
1083 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1084 }
1085
1086 if (m_tap_count == 2) {
1087 // printf(" Doubletap detected\n");
1088 m_tap_count = 0; // Reset after a double-tap
1089
1090 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1091 ev.m_x = event.m_x;
1092 ev.m_y = event.m_y;
1093 // wxPostEvent(this, ev);
1094 MouseEvent(ev);
1095 return;
1096 }
1097
1098#endif
1099
1100 MouseEvent(event);
1101}
1102
1103void ChartCanvas::OnMotion(wxMouseEvent &event) {
1104 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1105 dragging, upon simple click, and without the OnLeftDown event before Thus,
1106 this consists in skiping it, and setting the leftdown bit according to a
1107 status that we trust */
1108 event.m_leftDown = m_leftdown;
1109 MouseEvent(event);
1110}
1111
1112void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1113 /* there are spurious end zoom events upon right-click */
1114 if (event.IsGestureEnd()) return;
1115
1116 double factor = event.GetZoomFactor();
1117
1118 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1119 m_oldVPSScale = GetVPScale();
1120 }
1121
1122 double current_vps = GetVPScale();
1123 double wanted_factor = m_oldVPSScale / current_vps * factor;
1124
1125 ZoomCanvas(wanted_factor, true, false);
1126
1127 // Allow combined zoom/pan operation
1128 if (event.IsGestureStart()) {
1129 m_zoomStartPoint = event.GetPosition();
1130 } else {
1131 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1132 PanCanvas(-delta.x, -delta.y);
1133 m_zoomStartPoint = event.GetPosition();
1134 }
1135}
1136
1137void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1138
1139void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1140 DoRotateCanvas(0.0);
1141}
1142#endif /* HAVE_WX_GESTURE_EVENTS */
1143
1144void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1145 // printf("tap timer %d\n", m_tap_count);
1146 m_tap_count = 0;
1147}
1148
1149void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1150 m_FinishRouteOnKillFocus = false;
1151 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1152 m_FinishRouteOnKillFocus = true;
1153}
1154
1155void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1156 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1157 m_vLat = pcc->iLat;
1158 m_vLon = pcc->iLon;
1159
1160 m_restore_dbindex = pcc->DBindex;
1161 m_bFollow = pcc->bFollow;
1162 if (pcc->GroupID < 0) pcc->GroupID = 0;
1163
1164 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1165 m_groupIndex = 0;
1166 else
1167 m_groupIndex = pcc->GroupID;
1168
1169 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1170
1171 ShowTides(pcc->bShowTides);
1172 ShowCurrents(pcc->bShowCurrents);
1173
1174 SetShowDepthUnits(pcc->bShowDepthUnits);
1175 SetShowGrid(pcc->bShowGrid);
1176 SetShowOutlines(pcc->bShowOutlines);
1177
1178 SetShowAIS(pcc->bShowAIS);
1179 SetAttenAIS(pcc->bAttenAIS);
1180
1181 // ENC options
1182 SetShowENCText(pcc->bShowENCText);
1183 m_encDisplayCategory = pcc->nENCDisplayCategory;
1184 m_encShowDepth = pcc->bShowENCDepths;
1185 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1186 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1187 m_encShowLights = pcc->bShowENCLights;
1188 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1189 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1190 m_encShowDataQual = pcc->bShowENCDataQuality;
1191
1192 bool courseUp = pcc->bCourseUp;
1193 bool headUp = pcc->bHeadUp;
1194 m_upMode = NORTH_UP_MODE;
1195 if (courseUp)
1196 m_upMode = COURSE_UP_MODE;
1197 else if (headUp)
1198 m_upMode = HEAD_UP_MODE;
1199
1200 m_bLookAhead = pcc->bLookahead;
1201
1202 m_singleChart = NULL;
1203}
1204
1205void ChartCanvas::ApplyGlobalSettings() {
1206 // GPS compas window
1207 if (m_Compass) {
1208 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1209 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1210 }
1211 if (m_notification_button) m_notification_button->UpdateStatus();
1212}
1213
1214void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1215 bool groupOK = CheckGroup(m_groupIndex);
1216
1217 if (!groupOK) {
1218 SetGroupIndex(m_groupIndex, true);
1219 }
1220}
1221
1222void ChartCanvas::SetShowGPS(bool bshow) {
1223 if (m_bShowGPS != bshow) {
1224 delete m_Compass;
1225 m_Compass = new ocpnCompass(this, bshow);
1226 m_Compass->SetScaleFactor(g_compass_scalefactor);
1227 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1228 }
1229 m_bShowGPS = bshow;
1230}
1231
1232void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1233 m_bShowCompassWin = bshow;
1234 if (m_Compass) {
1235 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1236 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1237 }
1238}
1239
1240int ChartCanvas::GetPianoHeight() {
1241 int height = 0;
1242 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1243
1244 return height;
1245}
1246
1247void ChartCanvas::ConfigureChartBar() {
1248 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1249
1250 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1251 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1252
1253 if (GetQuiltMode()) {
1254 m_Piano->SetRoundedRectangles(true);
1255 }
1256 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1257 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1258 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1259}
1260
1261void ChartCanvas::ShowTides(bool bShow) {
1262 gFrame->LoadHarmonics();
1263
1264 if (ptcmgr->IsReady()) {
1265 SetbShowTide(bShow);
1266
1267 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1268 } else {
1269 wxLogMessage("Chart1::Event...TCMgr Not Available");
1270 SetbShowTide(false);
1271 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1272 }
1273
1274 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1275 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1276
1277 // TODO
1278 // if( GetbShowTide() ) {
1279 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1280 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1281 // update
1282 // } else
1283 // FrameTCTimer.Stop();
1284}
1285
1286void ChartCanvas::ShowCurrents(bool bShow) {
1287 gFrame->LoadHarmonics();
1288
1289 if (ptcmgr->IsReady()) {
1290 SetbShowCurrent(bShow);
1291 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1292 } else {
1293 wxLogMessage("Chart1::Event...TCMgr Not Available");
1294 SetbShowCurrent(false);
1295 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1296 }
1297
1298 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1299 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1300
1301 // TODO
1302 // if( GetbShowCurrent() ) {
1303 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1304 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1305 // update
1306 // } else
1307 // FrameTCTimer.Stop();
1308}
1309
1310// TODO
1311static ChartDummy *pDummyChart;
1312
1315
1316void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1317
1318void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1319 SetAlertString("");
1320
1321 int new_index = index;
1322 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1323
1324 bool bgroup_override = false;
1325 int old_group_index = new_index;
1326
1327 if (!CheckGroup(new_index)) {
1328 new_index = 0;
1329 bgroup_override = true;
1330 }
1331
1332 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1333 new_index = index;
1334
1335 // Get the currently displayed chart native scale, and the current ViewPort
1336 int current_chart_native_scale = GetCanvasChartNativeScale();
1337 ViewPort vp = GetVP();
1338
1339 m_groupIndex = new_index;
1340
1341 // Are there ENCs in this group
1342 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1343
1344 // Update the MUIBar for ENC availability
1345 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1346
1347 // Allow the chart database to pre-calculate the MBTile inclusion test
1348 // boolean...
1349 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1350
1351 // Invalidate the "sticky" chart on group change, since it might not be in
1352 // the new group
1353 g_sticky_chart = -1;
1354
1355 // We need a chartstack and quilt to figure out which chart to open in the
1356 // new group
1357 UpdateCanvasOnGroupChange();
1358
1359 int dbi_now = -1;
1360 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1361
1362 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1363
1364 // If a new reference chart is indicated, set a good scale for it.
1365 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1366 double best_scale = GetBestStartScale(dbi_hint, vp);
1367 SetVPScale(best_scale);
1368 }
1369
1370 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1371
1372 // Refresh the canvas, selecting the "best" chart,
1373 // applying the prior ViewPort exactly
1374 canvasChartsRefresh(dbi_hint);
1375
1376 UpdateCanvasControlBar();
1377
1378 if (!autoSwitch && bgroup_override) {
1379 // show a short timed message box
1380 wxString msg(_("Group \""));
1381
1382 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1383 msg += pGroup->m_group_name;
1384
1385 msg += _("\" is empty.");
1386
1387 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1388
1389 return;
1390 }
1391
1392 // Message box is deferred so that canvas refresh occurs properly before
1393 // dialog
1394 if (bgroup_override) {
1395 wxString msg(_("Group \""));
1396
1397 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1398 msg += pGroup->m_group_name;
1399
1400 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1401
1402 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1403 }
1404}
1405
1406bool ChartCanvas::CheckGroup(int igroup) {
1407 if (!ChartData) return true; // Not known yet...
1408
1409 if (igroup == 0) return true; // "all charts" is always OK
1410
1411 if (igroup < 0) // negative group is an error
1412 return false;
1413
1414 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1415
1416 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1417 // and auto-shift to group 0
1418 return false;
1419
1420 for (const auto &elem : pGroup->m_element_array) {
1421 for (unsigned int ic = 0;
1422 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1423 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1424 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1425
1426 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1427 }
1428 }
1429
1430 // If necessary, check for GSHHS
1431 for (const auto &elem : pGroup->m_element_array) {
1432 const wxString &element_root = elem.m_element_name;
1433 wxString test_string = "GSHH";
1434 if (element_root.Upper().Contains(test_string)) return true;
1435 }
1436
1437 return false;
1438}
1439
1440void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1441 if (!ChartData) return;
1442
1443 AbstractPlatform::ShowBusySpinner();
1444
1445 double old_scale = GetVPScale();
1446 InvalidateQuilt();
1447 SetQuiltRefChart(-1);
1448
1449 m_singleChart = NULL;
1450
1451 // delete m_pCurrentStack;
1452 // m_pCurrentStack = NULL;
1453
1454 // Build a new ChartStack
1455 if (!m_pCurrentStack) {
1456 m_pCurrentStack = new ChartStack;
1457 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1458 }
1459
1460 if (-1 != dbi_hint) {
1461 if (GetQuiltMode()) {
1462 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1463 SetQuiltRefChart(dbi_hint);
1464 } else {
1465 // Open the saved chart
1466 ChartBase *pTentative_Chart;
1467 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1468
1469 if (pTentative_Chart) {
1470 /* m_singleChart is always NULL here, (set above) should this go before
1471 * that? */
1472 if (m_singleChart) m_singleChart->Deactivate();
1473
1474 m_singleChart = pTentative_Chart;
1475 m_singleChart->Activate();
1476
1477 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1478 GetpCurrentStack(), m_singleChart->GetFullPath());
1479 }
1480 }
1481
1482 // refresh_Piano();
1483 } else {
1484 // Select reference chart from the stack, as though clicked by user
1485 // Make it the smallest scale chart on the stack
1486 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1487 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1488 SetQuiltRefChart(selected_index);
1489 }
1490
1491 // Validate the correct single chart, or set the quilt mode as appropriate
1492 SetupCanvasQuiltMode();
1493 if (!GetQuiltMode() && m_singleChart == 0) {
1494 // use a dummy like in DoChartUpdate
1495 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1496 m_singleChart = pDummyChart;
1497 SetVPScale(old_scale);
1498 }
1499
1500 ReloadVP();
1501
1502 UpdateCanvasControlBar();
1503 UpdateGPSCompassStatusBox(true);
1504
1505 SetCursor(wxCURSOR_ARROW);
1506
1507 AbstractPlatform::HideBusySpinner();
1508}
1509
1510bool ChartCanvas::DoCanvasUpdate() {
1511 double tLat, tLon; // Chart Stack location
1512 double vpLat, vpLon; // ViewPort location
1513 bool blong_jump = false;
1514 meters_to_shift = 0;
1515 dir_to_shift = 0;
1516
1517 bool bNewChart = false;
1518 bool bNewView = false;
1519 bool bCanvasChartAutoOpen = true; // debugging
1520
1521 bool bNewPiano = false;
1522 bool bOpenSpecified;
1523 ChartStack LastStack;
1524 ChartBase *pLast_Ch;
1525
1526 ChartStack WorkStack;
1527
1528 if (bDBUpdateInProgress) return false;
1529 if (!ChartData) return false;
1530
1531 if (ChartData->IsBusy()) return false;
1532 if (m_chart_drag_inertia_active) return false;
1533
1534 // Startup case:
1535 // Quilting is enabled, but the last chart seen was not quiltable
1536 // In this case, drop to single chart mode, set persistence flag,
1537 // And open the specified chart
1538 // TODO implement this
1539 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1540 // if( GetQuiltMode() ) {
1541 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1542 // gFrame->ToggleQuiltMode();
1543 // m_bpersistent_quilt = true;
1544 // m_singleChart = NULL;
1545 // }
1546 // }
1547 // }
1548
1549 // If in auto-follow mode, use the current glat,glon to build chart
1550 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1551 // other means
1552
1553 if (m_bFollow) {
1554 tLat = gLat;
1555 tLon = gLon;
1556
1557 // Set the ViewPort center based on the OWNSHIP offset
1558 double dx = m_OSoffsetx;
1559 double dy = m_OSoffsety;
1560 double d_east = dx / GetVP().view_scale_ppm;
1561 double d_north = dy / GetVP().view_scale_ppm;
1562
1563 if (GetUpMode() == NORTH_UP_MODE) {
1564 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1565 } else {
1566 double offset_angle = atan2(d_north, d_east);
1567 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1568 double chart_angle = GetVPRotation();
1569 double target_angle = chart_angle + offset_angle;
1570 double d_east_mod = offset_distance * cos(target_angle);
1571 double d_north_mod = offset_distance * sin(target_angle);
1572 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1573 }
1574
1575 // on lookahead mode, adjust the vp center point
1576 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1577 double cog_to_use = gCog;
1578 if (g_btenhertz &&
1579 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1580 cog_to_use = gCog_gt;
1581 blong_jump = true;
1582 }
1583 if (!g_btenhertz) cog_to_use = g_COGAvg;
1584
1585 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1586
1587 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1588 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1589
1590 double pixel_delta_tent =
1591 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1592
1593 double pixel_delta = 0;
1594
1595 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1596 // avoid jumping of the vp center point during slow maneuvering, or at
1597 // anchor....
1598 if (!std::isnan(gSog)) {
1599 if (gSog < 2.0)
1600 pixel_delta = 0.;
1601 else
1602 pixel_delta = pixel_delta_tent;
1603 }
1604
1605 meters_to_shift = 0;
1606 dir_to_shift = 0;
1607 if (!std::isnan(gCog)) {
1608 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1609 dir_to_shift = cog_to_use;
1610 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1611 &vpLon);
1612 } else {
1613 vpLat = gLat;
1614 vpLon = gLon;
1615 }
1616 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1617 m_OSoffsetx = 0; // center ownship on loss of GPS
1618 m_OSoffsety = 0;
1619 vpLat = gLat;
1620 vpLon = gLon;
1621 }
1622
1623 } else {
1624 tLat = m_vLat;
1625 tLon = m_vLon;
1626 vpLat = m_vLat;
1627 vpLon = m_vLon;
1628 }
1629
1630 if (GetQuiltMode()) {
1631 int current_db_index = -1;
1632 if (m_pCurrentStack)
1633 current_db_index =
1634 m_pCurrentStack
1635 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1636 // chart dbIndex
1637 else
1638 m_pCurrentStack = new ChartStack;
1639
1640 // This logic added to enable opening a chart when there is no
1641 // previous chart indication, either from inital startup, or from adding
1642 // new chart directory
1643 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1644 m_pCurrentStack) {
1645 if (m_pCurrentStack->nEntry) {
1646 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1647 1); // smallest scale
1648 SelectQuiltRefdbChart(new_dbIndex, true);
1649 m_bautofind = false;
1650 }
1651 }
1652
1653 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1654 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1655
1656 if (m_bFirstAuto) {
1657 // Allow the chart database to pre-calculate the MBTile inclusion test
1658 // boolean...
1659 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1660
1661 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1662 // physical pixels. On standard DPI displays where logical = physical
1663 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1664 // logical pixels, this ratio would be 0.5.
1665 double proposed_scale_onscreen =
1666 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1667
1668 int initial_db_index = m_restore_dbindex;
1669 if (initial_db_index < 0) {
1670 if (m_pCurrentStack->nEntry) {
1671 initial_db_index =
1672 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1673 } else
1674 m_bautofind = true; // initial_db_index = 0;
1675 }
1676
1677 if (m_pCurrentStack->nEntry) {
1678 int initial_type = ChartData->GetDBChartType(initial_db_index);
1679
1680 // Check to see if the target new chart is quiltable as a reference
1681 // chart
1682
1683 if (!IsChartQuiltableRef(initial_db_index)) {
1684 // If it is not quiltable, then walk the stack up looking for a
1685 // satisfactory chart i.e. one that is quiltable and of the same type
1686 // XXX if there's none?
1687 int stack_index = 0;
1688
1689 if (stack_index >= 0) {
1690 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1691 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1692 if (IsChartQuiltableRef(test_db_index) &&
1693 (initial_type ==
1694 ChartData->GetDBChartType(initial_db_index))) {
1695 initial_db_index = test_db_index;
1696 break;
1697 }
1698 stack_index++;
1699 }
1700 }
1701 }
1702
1703 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1704 if (pc) {
1705 SetQuiltRefChart(initial_db_index);
1706 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1707 }
1708 }
1709 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1710 // just GetVPScale(), so I'm not sure why it's necessary to define the
1711 // proposed_scale_onscreen variable.
1712 bNewView |= SetViewPoint(vpLat, vpLon,
1713 GetCanvasScaleFactor() / proposed_scale_onscreen,
1714 0, GetVPRotation());
1715 }
1716 // Measure rough jump distance if in bfollow mode
1717 // No good reason to do smooth pan for
1718 // jump distance more than one screen width at scale.
1719 bool super_jump = false;
1720 if (m_bFollow) {
1721 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1722 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1723 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1724 }
1725#if 0
1726 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1727 int nstep = 5;
1728 if (blong_jump) nstep = 20;
1729 StartTimedMovementVP(vpLat, vpLon, nstep);
1730 } else
1731#endif
1732 {
1733 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1734 }
1735
1736 goto update_finish;
1737 }
1738
1739 // Single Chart Mode from here....
1740 pLast_Ch = m_singleChart;
1741 ChartTypeEnum new_open_type;
1742 ChartFamilyEnum new_open_family;
1743 if (pLast_Ch) {
1744 new_open_type = pLast_Ch->GetChartType();
1745 new_open_family = pLast_Ch->GetChartFamily();
1746 } else {
1747 new_open_type = CHART_TYPE_KAP;
1748 new_open_family = CHART_FAMILY_RASTER;
1749 }
1750
1751 bOpenSpecified = m_bFirstAuto;
1752
1753 // Make sure the target stack is valid
1754 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1755
1756 // Build a chart stack based on tLat, tLon
1757 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1758 m_groupIndex)) { // Bogus Lat, Lon?
1759 if (NULL == pDummyChart) {
1760 pDummyChart = new ChartDummy;
1761 bNewChart = true;
1762 }
1763
1764 if (m_singleChart)
1765 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1766
1767 m_singleChart = pDummyChart;
1768
1769 // If the current viewpoint is invalid, set the default scale to
1770 // something reasonable.
1771 double set_scale = GetVPScale();
1772 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1773
1774 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1775
1776 // If the chart stack has just changed, there is new status
1777 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1778 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1779 bNewPiano = true;
1780 bNewChart = true;
1781 }
1782 }
1783
1784 // Copy the new (by definition empty) stack into the target stack
1785 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1786
1787 goto update_finish;
1788 }
1789
1790 // Check to see if Chart Stack has changed
1791 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1792 // New chart stack, so...
1793 bNewPiano = true;
1794
1795 // Save a copy of the current stack
1796 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1797
1798 // Copy the new stack into the target stack
1799 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1800
1801 // Is Current Chart in new stack?
1802
1803 int tEntry = -1;
1804 if (NULL != m_singleChart) // this handles startup case
1805 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1806 m_singleChart->GetFullPath());
1807
1808 if (tEntry != -1) { // m_singleChart is in the new stack
1809 m_pCurrentStack->CurrentStackEntry = tEntry;
1810 bNewChart = false;
1811 }
1812
1813 else // m_singleChart is NOT in new stack
1814 { // So, need to open a new chart
1815 // Find the largest scale raster chart that opens OK
1816
1817 ChartBase *pProposed = NULL;
1818
1819 if (bCanvasChartAutoOpen) {
1820 bool search_direction =
1821 false; // default is to search from lowest to highest
1822 int start_index = 0;
1823
1824 // A special case: If panning at high scale, open largest scale
1825 // chart first
1826 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1827 (LastStack.nEntry == 0)) {
1828 search_direction = true;
1829 start_index = m_pCurrentStack->nEntry - 1;
1830 }
1831
1832 // Another special case, open specified index on program start
1833 if (bOpenSpecified) {
1834 search_direction = false;
1835 start_index = 0;
1836 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1837 start_index = 0;
1838
1839 new_open_type = CHART_TYPE_DONTCARE;
1840 }
1841
1842 pProposed = ChartData->OpenStackChartConditional(
1843 m_pCurrentStack, start_index, search_direction, new_open_type,
1844 new_open_family);
1845
1846 // Try to open other types/families of chart in some priority
1847 if (NULL == pProposed)
1848 pProposed = ChartData->OpenStackChartConditional(
1849 m_pCurrentStack, start_index, search_direction,
1850 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1851
1852 if (NULL == pProposed)
1853 pProposed = ChartData->OpenStackChartConditional(
1854 m_pCurrentStack, start_index, search_direction,
1855 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1856
1857 bNewChart = true;
1858
1859 } // bCanvasChartAutoOpen
1860
1861 else
1862 pProposed = NULL;
1863
1864 // If no go, then
1865 // Open a Dummy Chart
1866 if (NULL == pProposed) {
1867 if (NULL == pDummyChart) {
1868 pDummyChart = new ChartDummy;
1869 bNewChart = true;
1870 }
1871
1872 if (pLast_Ch)
1873 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1874
1875 pProposed = pDummyChart;
1876 }
1877
1878 // Arriving here, pProposed points to an opened chart, or NULL.
1879 if (m_singleChart) m_singleChart->Deactivate();
1880 m_singleChart = pProposed;
1881
1882 if (m_singleChart) {
1883 m_singleChart->Activate();
1884 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1885 m_pCurrentStack, m_singleChart->GetFullPath());
1886 }
1887 } // need new chart
1888
1889 // Arriving here, m_singleChart is opened and OK, or NULL
1890 if (NULL != m_singleChart) {
1891 // Setup the view using the current scale
1892 double set_scale = GetVPScale();
1893
1894 // If the current viewpoint is invalid, set the default scale to
1895 // something reasonable.
1896 if (!GetVP().IsValid())
1897 set_scale = 1. / 20000.;
1898 else { // otherwise, match scale if elected.
1899 double proposed_scale_onscreen;
1900
1901 if (m_bFollow) { // autoset the scale only if in autofollow
1902 double new_scale_ppm =
1903 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1904 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1905 } else
1906 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1907
1908 // This logic will bring a new chart onscreen at roughly twice the true
1909 // paper scale equivalent. Note that first chart opened on application
1910 // startup (bOpenSpecified = true) will open at the config saved scale
1911 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1912 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1913 double equivalent_vp_scale =
1914 GetCanvasScaleFactor() / proposed_scale_onscreen;
1915 double new_scale_ppm =
1916 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1917 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1918 }
1919
1920 if (m_bFollow) { // bounds-check the scale only if in autofollow
1921 proposed_scale_onscreen =
1922 wxMin(proposed_scale_onscreen,
1923 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1924 GetCanvasWidth()));
1925 proposed_scale_onscreen =
1926 wxMax(proposed_scale_onscreen,
1927 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1929 }
1930
1931 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1932 }
1933
1934 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1935 m_singleChart->GetChartSkew() * PI / 180.,
1936 GetVPRotation());
1937 }
1938 } // new stack
1939
1940 else // No change in Chart Stack
1941 {
1942 if ((m_bFollow) && m_singleChart)
1943 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1944 m_singleChart->GetChartSkew() * PI / 180.,
1945 GetVPRotation());
1946 }
1947
1948update_finish:
1949
1950 // TODO
1951 // if( bNewPiano ) UpdateControlBar();
1952
1953 m_bFirstAuto = false; // Auto open on program start
1954
1955 // If we need a Refresh(), do it here...
1956 // But don't duplicate a Refresh() done by SetViewPoint()
1957 if (bNewChart && !bNewView) Refresh(false);
1958
1959#ifdef ocpnUSE_GL
1960 // If a new chart, need to invalidate gl viewport for refresh
1961 // so the fbo gets flushed
1962 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1963#endif
1964
1965 return bNewChart | bNewView;
1966}
1967
1968void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1969 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1970
1971 SetQuiltRefChart(db_index);
1972 if (ChartData) {
1973 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1974 if (pc) {
1975 if (b_autoscale) {
1976 double best_scale_ppm = GetBestVPScale(pc);
1977 SetVPScale(best_scale_ppm);
1978 }
1979 } else
1980 SetQuiltRefChart(-1);
1981 } else
1982 SetQuiltRefChart(-1);
1983}
1984
1985void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1986 std::vector<int> piano_chart_index_array =
1987 GetQuiltExtendedStackdbIndexArray();
1988 int current_db_index = piano_chart_index_array[selected_index];
1989
1990 SelectQuiltRefdbChart(current_db_index);
1991}
1992
1993double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1994 if (pchart) {
1995 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1996
1997 if ((g_bPreserveScaleOnX) ||
1998 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1999 double new_scale_ppm = GetVPScale();
2000 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2001 } else {
2002 // This logic will bring the new chart onscreen at roughly twice the true
2003 // paper scale equivalent.
2004 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2005 double equivalent_vp_scale =
2006 GetCanvasScaleFactor() / proposed_scale_onscreen;
2007 double new_scale_ppm =
2008 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2009 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2010 }
2011
2012 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2013 // set. Otherwise, we get severe performance problems on all platforms
2014
2015 double max_underzoom_multiplier = 2.0;
2016 if (GetVP().b_quilt) {
2017 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2018 pchart->GetChartType(),
2019 pchart->GetChartFamily());
2020 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2021 }
2022
2023 proposed_scale_onscreen = wxMin(
2024 proposed_scale_onscreen,
2025 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2026 max_underzoom_multiplier);
2027
2028 // And, do not allow excessive overzoom either
2029 proposed_scale_onscreen =
2030 wxMax(proposed_scale_onscreen,
2031 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2032
2033 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2034 } else
2035 return 1.0;
2036}
2037
2038void ChartCanvas::SetupCanvasQuiltMode() {
2039 if (GetQuiltMode()) // going to quilt mode
2040 {
2041 ChartData->LockCache();
2042
2043 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2044
2045 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2046
2047 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2048 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2049 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2050 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2051
2052 m_Piano->SetRoundedRectangles(true);
2053
2054 // Select the proper Ref chart
2055 int target_new_dbindex = -1;
2056 if (m_pCurrentStack) {
2057 target_new_dbindex =
2058 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2059
2060 if (-1 != target_new_dbindex) {
2061 if (!IsChartQuiltableRef(target_new_dbindex)) {
2062 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2063 int type = ChartData->GetDBChartType(target_new_dbindex);
2064
2065 // walk the stack up looking for a satisfactory chart
2066 int stack_index = m_pCurrentStack->CurrentStackEntry;
2067
2068 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2069 (stack_index >= 0)) {
2070 int proj_tent = ChartData->GetDBChartProj(
2071 m_pCurrentStack->GetDBIndex(stack_index));
2072 int type_tent = ChartData->GetDBChartType(
2073 m_pCurrentStack->GetDBIndex(stack_index));
2074
2075 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2076 if ((proj == proj_tent) && (type_tent == type)) {
2077 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2078 break;
2079 }
2080 }
2081 stack_index++;
2082 }
2083 }
2084 }
2085 }
2086
2087 if (IsChartQuiltableRef(target_new_dbindex))
2088 SelectQuiltRefdbChart(target_new_dbindex,
2089 false); // Try not to allow a scale change
2090 else
2091 SelectQuiltRefdbChart(-1, false);
2092
2093 m_singleChart = NULL; // Bye....
2094
2095 // Re-qualify the quilt reference chart selection
2096 AdjustQuiltRefChart();
2097
2098 // Restore projection type saved on last quilt mode toggle
2099 // TODO
2100 // if(g_sticky_projection != -1)
2101 // GetVP().SetProjectionType(g_sticky_projection);
2102 // else
2103 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2104 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2105
2106 } else // going to SC Mode
2107 {
2108 std::vector<int> empty_array;
2109 m_Piano->SetActiveKeyArray(empty_array);
2110 m_Piano->SetNoshowIndexArray(empty_array);
2111 m_Piano->SetEclipsedIndexArray(empty_array);
2112
2113 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2114 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2115 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2116 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2117 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2118
2119 m_Piano->SetRoundedRectangles(false);
2120 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2121 }
2122
2123 // When shifting from quilt to single chart mode, select the "best" single
2124 // chart to show
2125 if (!GetQuiltMode()) {
2126 if (ChartData && ChartData->IsValid()) {
2127 UnlockQuilt();
2128
2129 double tLat, tLon;
2130 if (m_bFollow == true) {
2131 tLat = gLat;
2132 tLon = gLon;
2133 } else {
2134 tLat = m_vLat;
2135 tLon = m_vLon;
2136 }
2137
2138 if (!m_singleChart) {
2139 // Build a temporary chart stack based on tLat, tLon
2140 ChartStack TempStack;
2141 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2142 m_groupIndex);
2143
2144 // Iterate over the quilt charts actually shown, looking for the
2145 // largest scale chart that will be in the new chartstack.... This
2146 // will (almost?) always be the reference chart....
2147
2148 ChartBase *Candidate_Chart = NULL;
2149 int cur_max_scale = (int)1e8;
2150
2151 ChartBase *pChart = GetFirstQuiltChart();
2152 while (pChart) {
2153 // Is this pChart in new stack?
2154 int tEntry =
2155 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2156 if (tEntry != -1) {
2157 if (pChart->GetNativeScale() < cur_max_scale) {
2158 Candidate_Chart = pChart;
2159 cur_max_scale = pChart->GetNativeScale();
2160 }
2161 }
2162 pChart = GetNextQuiltChart();
2163 }
2164
2165 m_singleChart = Candidate_Chart;
2166
2167 // If the quilt is empty, there is no "best" chart.
2168 // So, open the smallest scale chart in the current stack
2169 if (NULL == m_singleChart) {
2170 m_singleChart = ChartData->OpenStackChartConditional(
2171 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2172 CHART_FAMILY_DONTCARE);
2173 }
2174 }
2175
2176 // Invalidate all the charts in the quilt,
2177 // as any cached data may be region based and not have fullscreen coverage
2178 InvalidateAllQuiltPatchs();
2179
2180 if (m_singleChart) {
2181 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2182 std::vector<int> one_array;
2183 one_array.push_back(dbi);
2184 m_Piano->SetActiveKeyArray(one_array);
2185 }
2186
2187 if (m_singleChart) {
2188 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2189 }
2190 }
2191 // Invalidate the current stack so that it will be rebuilt on next tick
2192 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2193 }
2194}
2195
2196bool ChartCanvas::IsTempMenuBarEnabled() {
2197#ifdef __WXMSW__
2198 int major;
2199 wxGetOsVersion(&major);
2200 return (major >
2201 5); // For Windows, function is only available on Vista and above
2202#else
2203 return true;
2204#endif
2205}
2206
2207double ChartCanvas::GetCanvasRangeMeters() {
2208 int width, height;
2209 GetSize(&width, &height);
2210 int minDimension = wxMin(width, height);
2211
2212 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2213 range *= cos(GetVP().clat * PI / 180.);
2214 return range;
2215}
2216
2217void ChartCanvas::SetCanvasRangeMeters(double range) {
2218 int width, height;
2219 GetSize(&width, &height);
2220 int minDimension = wxMin(width, height);
2221
2222 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2223 SetVPScale(scale_ppm / 2);
2224}
2225
2226bool ChartCanvas::SetUserOwnship() {
2227 // Look for user defined ownship image
2228 // This may be found in the shared data location along with other user
2229 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2230 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2231 double factor_dusk = 0.5;
2232 double factor_night = 0.25;
2233
2234 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2235 m_pos_image_user_day = new wxImage;
2236 *m_pos_image_user_day = pbmp->ConvertToImage();
2237 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2238
2239 int gimg_width = m_pos_image_user_day->GetWidth();
2240 int gimg_height = m_pos_image_user_day->GetHeight();
2241
2242 // Make dusk and night images
2243 m_pos_image_user_dusk = new wxImage;
2244 m_pos_image_user_night = new wxImage;
2245
2246 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2247 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2248
2249 for (int iy = 0; iy < gimg_height; iy++) {
2250 for (int ix = 0; ix < gimg_width; ix++) {
2251 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2252 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2253 m_pos_image_user_day->GetGreen(ix, iy),
2254 m_pos_image_user_day->GetBlue(ix, iy));
2255 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2256 hsv.value = hsv.value * factor_dusk;
2257 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2258 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2259 nrgb.blue);
2260
2261 hsv = wxImage::RGBtoHSV(rgb);
2262 hsv.value = hsv.value * factor_night;
2263 nrgb = wxImage::HSVtoRGB(hsv);
2264 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2265 nrgb.blue);
2266 }
2267 }
2268 }
2269
2270 // Make some alternate greyed out day/dusk/night images
2271 m_pos_image_user_grey_day = new wxImage;
2272 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2273
2274 m_pos_image_user_grey_dusk = new wxImage;
2275 m_pos_image_user_grey_night = new wxImage;
2276
2277 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2278 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2279
2280 for (int iy = 0; iy < gimg_height; iy++) {
2281 for (int ix = 0; ix < gimg_width; ix++) {
2282 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2283 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2284 m_pos_image_user_grey_day->GetGreen(ix, iy),
2285 m_pos_image_user_grey_day->GetBlue(ix, iy));
2286 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2287 hsv.value = hsv.value * factor_dusk;
2288 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2289 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2290 nrgb.blue);
2291
2292 hsv = wxImage::RGBtoHSV(rgb);
2293 hsv.value = hsv.value * factor_night;
2294 nrgb = wxImage::HSVtoRGB(hsv);
2295 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2296 nrgb.blue);
2297 }
2298 }
2299 }
2300
2301 // Make a yellow image for rendering under low accuracy chart conditions
2302 m_pos_image_user_yellow_day = new wxImage;
2303 m_pos_image_user_yellow_dusk = new wxImage;
2304 m_pos_image_user_yellow_night = new wxImage;
2305
2306 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2307 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2308 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2309
2310 for (int iy = 0; iy < gimg_height; iy++) {
2311 for (int ix = 0; ix < gimg_width; ix++) {
2312 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2313 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2314 m_pos_image_user_grey_day->GetGreen(ix, iy),
2315 m_pos_image_user_grey_day->GetBlue(ix, iy));
2316
2317 // Simply remove all "blue" from the greyscaled image...
2318 // so, what is not black becomes yellow.
2319 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2320 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2321 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2322
2323 hsv = wxImage::RGBtoHSV(rgb);
2324 hsv.value = hsv.value * factor_dusk;
2325 nrgb = wxImage::HSVtoRGB(hsv);
2326 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2327
2328 hsv = wxImage::RGBtoHSV(rgb);
2329 hsv.value = hsv.value * factor_night;
2330 nrgb = wxImage::HSVtoRGB(hsv);
2331 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2332 0);
2333 }
2334 }
2335 }
2336
2337 return true;
2338 } else
2339 return false;
2340}
2341
2343 m_display_size_mm = size;
2344
2345 // int sx, sy;
2346 // wxDisplaySize( &sx, &sy );
2347
2348 // Calculate logical pixels per mm for later reference.
2349 wxSize sd = g_Platform->getDisplaySize();
2350 double horizontal = sd.x;
2351 // Set DPI (Win) scale factor
2352 g_scaler = g_Platform->GetDisplayDIPMult(this);
2353
2354 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2355 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2356
2357 if (ps52plib) {
2358 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2359 ps52plib->SetPPMM(m_pix_per_mm);
2360 }
2361
2362 wxString msg;
2363 msg.Printf(
2364 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2365 "%d:%d ",
2366 m_display_size_mm, sd.x, sd.y);
2367 wxLogDebug(msg);
2368
2369 int ssx, ssy;
2370 ssx = g_monitor_info[g_current_monitor].width;
2371 ssy = g_monitor_info[g_current_monitor].height;
2372 msg.Printf("monitor size: %d %d", ssx, ssy);
2373 wxLogDebug(msg);
2374
2375 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2376}
2377#if 0
2378void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2379{
2380 wxString msg(event.m_string.c_str(), wxConvUTF8);
2381 // if cpus are removed between runs
2382 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2383 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2384 }
2385
2386 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2387 {
2388 compress_msg_array.RemoveAt(event.thread);
2389 compress_msg_array.Insert( msg, event.thread);
2390 }
2391 else
2392 compress_msg_array.Add(msg);
2393
2394
2395 wxString combined_msg;
2396 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2397 combined_msg += compress_msg_array[i];
2398 combined_msg += "\n";
2399 }
2400
2401 bool skip = false;
2402 pprog->Update(pprog_count, combined_msg, &skip );
2403 pprog->SetSize(pprog_size);
2404 if(skip)
2405 b_skipout = skip;
2406}
2407#endif
2408void ChartCanvas::InvalidateGL() {
2409 if (!m_glcc) return;
2410#ifdef ocpnUSE_GL
2411 if (g_bopengl) m_glcc->Invalidate();
2412#endif
2413 if (m_Compass) m_Compass->UpdateStatus(true);
2414}
2415
2416int ChartCanvas::GetCanvasChartNativeScale() {
2417 int ret = 1;
2418 if (!VPoint.b_quilt) {
2419 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2420 } else
2421 ret = (int)m_pQuilt->GetRefNativeScale();
2422
2423 return ret;
2424}
2425
2426ChartBase *ChartCanvas::GetChartAtCursor() {
2427 ChartBase *target_chart;
2428 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2429 target_chart = m_singleChart;
2430 else if (VPoint.b_quilt)
2431 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2432 else
2433 target_chart = NULL;
2434 return target_chart;
2435}
2436
2437ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2438 ChartBase *target_chart;
2439 if (VPoint.b_quilt)
2440 target_chart =
2441 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2442 else
2443 target_chart = NULL;
2444 return target_chart;
2445}
2446
2447int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2448 int new_dbIndex = -1;
2449 if (!VPoint.b_quilt) {
2450 if (m_pCurrentStack) {
2451 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2452 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2453 if (sc >= scale) {
2454 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2455 break;
2456 }
2457 }
2458 }
2459 } else {
2460 // Using the current quilt, select a useable reference chart
2461 // Said chart will be in the extended (possibly full-screen) stack,
2462 // And will have a scale equal to or just greater than the stipulated
2463 // value
2464 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2465 if (im > 0) {
2466 for (unsigned int is = 0; is < im; is++) {
2467 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2468 m_pQuilt->GetExtendedStackIndexArray()[is]);
2469 if ((m.Scale_ge(
2470 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2471 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2472 break;
2473 }
2474 }
2475 }
2476 }
2477
2478 return new_dbIndex;
2479}
2480
2481void ChartCanvas::EnablePaint(bool b_enable) {
2482 m_b_paint_enable = b_enable;
2483#ifdef ocpnUSE_GL
2484 if (m_glcc) m_glcc->EnablePaint(b_enable);
2485#endif
2486}
2487
2488bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2489
2490void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2491
2492std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2493 return m_pQuilt->GetQuiltIndexArray();
2494 ;
2495}
2496
2497void ChartCanvas::SetQuiltMode(bool b_quilt) {
2498 VPoint.b_quilt = b_quilt;
2499 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2500}
2501
2502bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2503
2504int ChartCanvas::GetQuiltReferenceChartIndex() {
2505 return m_pQuilt->GetRefChartdbIndex();
2506}
2507
2508void ChartCanvas::InvalidateAllQuiltPatchs() {
2509 m_pQuilt->InvalidateAllQuiltPatchs();
2510}
2511
2512ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2513 return m_pQuilt->GetLargestScaleChart();
2514}
2515
2516ChartBase *ChartCanvas::GetFirstQuiltChart() {
2517 return m_pQuilt->GetFirstChart();
2518}
2519
2520ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2521
2522int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2523
2524void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2525 m_pQuilt->SetHiliteIndex(dbIndex);
2526}
2527
2528void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2529 m_pQuilt->SetHiliteIndexArray(hilite_array);
2530}
2531
2532void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2533 m_pQuilt->ClearHiliteIndexArray();
2534}
2535
2536std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2537 bool flag2) {
2538 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2539}
2540
2541int ChartCanvas::GetQuiltRefChartdbIndex() {
2542 return m_pQuilt->GetRefChartdbIndex();
2543}
2544
2545std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2546 return m_pQuilt->GetExtendedStackIndexArray();
2547}
2548
2549std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2550 return m_pQuilt->GetFullscreenIndexArray();
2551}
2552
2553std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2554 return m_pQuilt->GetEclipsedStackIndexArray();
2555}
2556
2557void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2558
2559double ChartCanvas::GetQuiltMaxErrorFactor() {
2560 return m_pQuilt->GetMaxErrorFactor();
2561}
2562
2563bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2564 return m_pQuilt->IsChartQuiltableRef(db_index);
2565}
2566
2567bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2568 double chartMaxScale =
2569 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2570 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2571}
2572
2573void ChartCanvas::StartMeasureRoute() {
2574 if (!m_routeState) { // no measure tool if currently creating route
2575 if (m_bMeasure_Active) {
2576 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2577 m_pMeasureRoute = NULL;
2578 }
2579
2580 m_bMeasure_Active = true;
2581 m_nMeasureState = 1;
2582 m_bDrawingRoute = false;
2583
2584 SetCursor(*pCursorPencil);
2585 Refresh();
2586 }
2587}
2588
2589void ChartCanvas::CancelMeasureRoute() {
2590 m_bMeasure_Active = false;
2591 m_nMeasureState = 0;
2592 m_bDrawingRoute = false;
2593
2594 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2595 m_pMeasureRoute = NULL;
2596
2597 SetCursor(*pCursorArrow);
2598}
2599
2600ViewPort &ChartCanvas::GetVP() { return VPoint; }
2601
2602void ChartCanvas::SetVP(ViewPort &vp) {
2603 VPoint = vp;
2604 VPoint.SetPixelScale(m_displayScale);
2605}
2606
2607// void ChartCanvas::SetFocus()
2608// {
2609// printf("set %d\n", m_canvasIndex);
2610// //wxWindow:SetFocus();
2611// }
2612
2613void ChartCanvas::TriggerDeferredFocus() {
2614 // #if defined(__WXGTK__) || defined(__WXOSX__)
2615
2616 m_deferredFocusTimer.Start(20, true);
2617
2618#if defined(__WXGTK__) || defined(__WXOSX__)
2619 gFrame->Raise();
2620#endif
2621
2622 // gFrame->Raise();
2623 // #else
2624 // SetFocus();
2625 // Refresh(true);
2626 // #endif
2627}
2628
2629void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2630 SetFocus();
2631 Refresh(true);
2632}
2633
2634void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2635 if (SendKeyEventToPlugins(event))
2636 return; // PlugIn did something, and does not want the canvas to do
2637 // anything else
2638
2639 int key_char = event.GetKeyCode();
2640 switch (key_char) {
2641 case '?':
2642 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2643 break;
2644 case '+':
2645 ZoomCanvas(g_plus_minus_zoom_factor, false);
2646 break;
2647 case '-':
2648 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2649 break;
2650 default:
2651 break;
2652 }
2653 if (g_benable_rotate) {
2654 switch (key_char) {
2655 case ']':
2656 RotateCanvas(1);
2657 Refresh();
2658 break;
2659
2660 case '[':
2661 RotateCanvas(-1);
2662 Refresh();
2663 break;
2664
2665 case '\\':
2666 DoRotateCanvas(0);
2667 break;
2668 }
2669 }
2670
2671 event.Skip();
2672}
2673
2674void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2675 if (SendKeyEventToPlugins(event))
2676 return; // PlugIn did something, and does not want the canvas to do
2677 // anything else
2678
2679 bool b_handled = false;
2680
2681 m_modkeys = event.GetModifiers();
2682
2683 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2684
2685#ifdef OCPN_ALT_MENUBAR
2686#ifndef __WXOSX__
2687 // If the permanent menubar is disabled, we show it temporarily when Alt is
2688 // pressed or when Alt + a letter is presssed (for the top-menu-level
2689 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2690 // some special cases.
2691 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2692 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2693 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2694 if (!g_bTempShowMenuBar) {
2695 g_bTempShowMenuBar = true;
2696 parent_frame->ApplyGlobalSettings(false);
2697 }
2698 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2699 event.Skip();
2700 return;
2701 }
2702 // If another key is pressed while Alt is down, do NOT toggle the menus when
2703 // Alt is released
2704 if (event.GetKeyCode() != WXK_ALT) {
2705 m_bMayToggleMenuBar = false;
2706 }
2707 }
2708#endif
2709#endif
2710
2711 // HOTKEYS
2712 switch (event.GetKeyCode()) {
2713 case WXK_TAB:
2714 // parent_frame->SwitchKBFocus( this );
2715 break;
2716
2717 case WXK_MENU:
2718 int x, y;
2719 event.GetPosition(&x, &y);
2720 m_FinishRouteOnKillFocus = false;
2721 CallPopupMenu(x, y);
2722 m_FinishRouteOnKillFocus = true;
2723 break;
2724
2725 case WXK_ALT:
2726 m_modkeys |= wxMOD_ALT;
2727 break;
2728
2729 case WXK_CONTROL:
2730 m_modkeys |= wxMOD_CONTROL;
2731 break;
2732
2733#ifdef __WXOSX__
2734 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2735 case WXK_RAW_CONTROL:
2736 m_modkeys |= wxMOD_RAW_CONTROL;
2737 break;
2738#endif
2739
2740 case WXK_LEFT:
2741 if (m_modkeys == wxMOD_CONTROL)
2742 parent_frame->DoStackDown(this);
2743 else if (g_bsmoothpanzoom) {
2744 StartTimedMovement();
2745 m_panx = -1;
2746 } else {
2747 PanCanvas(-panspeed, 0);
2748 }
2749 b_handled = true;
2750 break;
2751
2752 case WXK_UP:
2753 if (g_bsmoothpanzoom) {
2754 StartTimedMovement();
2755 m_pany = -1;
2756 } else
2757 PanCanvas(0, -panspeed);
2758 b_handled = true;
2759 break;
2760
2761 case WXK_RIGHT:
2762 if (m_modkeys == wxMOD_CONTROL)
2763 parent_frame->DoStackUp(this);
2764 else if (g_bsmoothpanzoom) {
2765 StartTimedMovement();
2766 m_panx = 1;
2767 } else
2768 PanCanvas(panspeed, 0);
2769 b_handled = true;
2770
2771 break;
2772
2773 case WXK_DOWN:
2774 if (g_bsmoothpanzoom) {
2775 StartTimedMovement();
2776 m_pany = 1;
2777 } else
2778 PanCanvas(0, panspeed);
2779 b_handled = true;
2780 break;
2781
2782 case WXK_F2: {
2783 // TogglebFollow();
2784 if (event.ShiftDown()) {
2785 double scale = GetVP().view_scale_ppm;
2786 auto current_family = m_pQuilt->GetRefFamily();
2787 auto target_family = CHART_FAMILY_UNKNOWN;
2788 if (current_family == CHART_FAMILY_RASTER)
2789 target_family = CHART_FAMILY_VECTOR;
2790 else
2791 target_family = CHART_FAMILY_RASTER;
2792
2793 std::shared_ptr<HostApi> host_api;
2794 host_api = GetHostApi();
2795 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2796
2797 if (api_121)
2798 api_121->SelectChartFamily(m_canvasIndex,
2799 (ChartFamilyEnumPI)target_family);
2800
2801 } else
2802 TogglebFollow();
2803 break;
2804 }
2805 case WXK_F3: {
2806 SetShowENCText(!GetShowENCText());
2807 Refresh(true);
2808 InvalidateGL();
2809 break;
2810 }
2811 case WXK_F4:
2812 if (!m_bMeasure_Active) {
2813 if (event.ShiftDown())
2814 m_bMeasure_DistCircle = true;
2815 else
2816 m_bMeasure_DistCircle = false;
2817
2818 StartMeasureRoute();
2819 } else {
2820 CancelMeasureRoute();
2821
2822 SetCursor(*pCursorArrow);
2823
2824 // SurfaceToolbar();
2825 InvalidateGL();
2826 Refresh(false);
2827 }
2828
2829 break;
2830
2831 case WXK_F5:
2832 parent_frame->ToggleColorScheme();
2833 gFrame->Raise();
2834 TriggerDeferredFocus();
2835 break;
2836
2837 case WXK_F6: {
2838 int mod = m_modkeys & wxMOD_SHIFT;
2839 if (mod != m_brightmod) {
2840 m_brightmod = mod;
2841 m_bbrightdir = !m_bbrightdir;
2842 }
2843
2844 if (!m_bbrightdir) {
2845 g_nbrightness -= 10;
2846 if (g_nbrightness <= MIN_BRIGHT) {
2847 g_nbrightness = MIN_BRIGHT;
2848 m_bbrightdir = true;
2849 }
2850 } else {
2851 g_nbrightness += 10;
2852 if (g_nbrightness >= MAX_BRIGHT) {
2853 g_nbrightness = MAX_BRIGHT;
2854 m_bbrightdir = false;
2855 }
2856 }
2857
2858 SetScreenBrightness(g_nbrightness);
2859 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2860
2861 SetFocus(); // just in case the external program steals it....
2862 gFrame->Raise(); // And reactivate the application main
2863
2864 break;
2865 }
2866
2867 case WXK_F7:
2868 parent_frame->DoStackDown(this);
2869 break;
2870
2871 case WXK_F8:
2872 parent_frame->DoStackUp(this);
2873 break;
2874
2875#ifndef __WXOSX__
2876 case WXK_F9: {
2877 ToggleCanvasQuiltMode();
2878 break;
2879 }
2880#endif
2881
2882 case WXK_F11:
2883 parent_frame->ToggleFullScreen();
2884 b_handled = true;
2885 break;
2886
2887 case WXK_F12: {
2888 if (m_modkeys == wxMOD_ALT) {
2889 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2890 } else {
2891 ToggleChartOutlines();
2892 }
2893 break;
2894 }
2895
2896 case WXK_PAUSE: // Drop MOB
2897 parent_frame->ActivateMOB();
2898 break;
2899
2900 // NUMERIC PAD
2901 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2902 case WXK_PAGEUP: {
2903 ZoomCanvas(g_plus_minus_zoom_factor, false);
2904 break;
2905 }
2906 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2907 case WXK_PAGEDOWN: {
2908 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2909 break;
2910 }
2911 case WXK_DELETE:
2912 case WXK_BACK:
2913 if (m_bMeasure_Active) {
2914 if (m_nMeasureState > 2) {
2915 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2916 m_pMeasureRoute->m_lastMousePointIndex =
2917 m_pMeasureRoute->GetnPoints();
2918 m_nMeasureState--;
2919 gFrame->RefreshAllCanvas();
2920 } else {
2921 CancelMeasureRoute();
2922 StartMeasureRoute();
2923 }
2924 }
2925 break;
2926 default:
2927 break;
2928 }
2929
2930 if (event.GetKeyCode() < 128) // ascii
2931 {
2932 int key_char = event.GetKeyCode();
2933
2934 // Handle both QWERTY and AZERTY keyboard separately for a few control
2935 // codes
2936 if (!g_b_assume_azerty) {
2937#ifdef __WXMAC__
2938 if (g_benable_rotate) {
2939 switch (key_char) {
2940 // On other platforms these are handled in OnKeyChar, which
2941 // (apparently) works better in some locales. On OS X it is better
2942 // to handle them here, since pressing Alt (which should change the
2943 // rotation speed) changes the key char and so prevents the keys
2944 // from working.
2945 case ']':
2946 RotateCanvas(1);
2947 b_handled = true;
2948 break;
2949
2950 case '[':
2951 RotateCanvas(-1);
2952 b_handled = true;
2953 break;
2954
2955 case '\\':
2956 DoRotateCanvas(0);
2957 b_handled = true;
2958 break;
2959 }
2960 }
2961#endif
2962 } else { // AZERTY
2963 switch (key_char) {
2964 case 43:
2965 ZoomCanvas(g_plus_minus_zoom_factor, false);
2966 break;
2967
2968 case 54: // '-' alpha/num pad
2969 // case 56: // '_' alpha/num pad
2970 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2971 break;
2972 }
2973 }
2974
2975#ifdef __WXOSX__
2976 // Ctrl+Cmd+F toggles fullscreen on macOS
2977 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2978 m_modkeys & wxMOD_RAW_CONTROL) {
2979 parent_frame->ToggleFullScreen();
2980 return;
2981 }
2982#endif
2983
2984 if (event.ControlDown()) key_char -= 64;
2985
2986 if (key_char >= '0' && key_char <= '9')
2987 SetGroupIndex(key_char - '0');
2988 else
2989
2990 switch (key_char) {
2991 case 'A':
2992 SetShowENCAnchor(!GetShowENCAnchor());
2993 ReloadVP();
2994
2995 break;
2996
2997 case 'C':
2998 parent_frame->ToggleColorScheme();
2999 break;
3000
3001 case 'D': {
3002 int x, y;
3003 event.GetPosition(&x, &y);
3004 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3005 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3006 // First find out what kind of chart is being used
3007 if (!pPopupDetailSlider) {
3008 if (VPoint.b_quilt) {
3009 if (m_pQuilt) {
3010 if (m_pQuilt->GetChartAtPix(
3011 VPoint,
3012 wxPoint(
3013 x, y))) // = null if no chart loaded for this point
3014 {
3015 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3016 ->GetChartType();
3017 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3018 ->GetChartFamily();
3019 }
3020 }
3021 } else {
3022 if (m_singleChart) {
3023 ChartType = m_singleChart->GetChartType();
3024 ChartFam = m_singleChart->GetChartFamily();
3025 }
3026 }
3027 // If a charttype is found show the popupslider
3028 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3029 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3031 this, -1, ChartType, ChartFam,
3032 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3033 wxDefaultSize, wxSIMPLE_BORDER, "");
3035 }
3036 } else //( !pPopupDetailSlider ) close popupslider
3037 {
3039 pPopupDetailSlider = NULL;
3040 }
3041 break;
3042 }
3043
3044 case 'E':
3045 m_nmea_log->Show();
3046 m_nmea_log->Raise();
3047 break;
3048
3049 case 'L':
3050 SetShowENCLights(!GetShowENCLights());
3051 ReloadVP();
3052
3053 break;
3054
3055 case 'M':
3056 if (event.ShiftDown())
3057 m_bMeasure_DistCircle = true;
3058 else
3059 m_bMeasure_DistCircle = false;
3060
3061 StartMeasureRoute();
3062 break;
3063
3064 case 'N':
3065 if (g_bInlandEcdis && ps52plib) {
3066 SetENCDisplayCategory((_DisCat)STANDARD);
3067 }
3068 break;
3069
3070 case 'O':
3071 ToggleChartOutlines();
3072 break;
3073
3074 case 'Q':
3075 ToggleCanvasQuiltMode();
3076 break;
3077
3078 case 'P':
3079 parent_frame->ToggleTestPause();
3080 break;
3081 case 'R':
3082 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3083 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3084 g_iNavAidRadarRingsNumberVisible = 1;
3085 else if (!g_bNavAidRadarRingsShown &&
3086 g_iNavAidRadarRingsNumberVisible == 1)
3087 g_iNavAidRadarRingsNumberVisible = 0;
3088 break;
3089 case 'S':
3090 SetShowENCDepth(!m_encShowDepth);
3091 ReloadVP();
3092 break;
3093
3094 case 'T':
3095 SetShowENCText(!GetShowENCText());
3096 ReloadVP();
3097 break;
3098
3099 case 'U':
3100 SetShowENCDataQual(!GetShowENCDataQual());
3101 ReloadVP();
3102 break;
3103
3104 case 'V':
3105 m_bShowNavobjects = !m_bShowNavobjects;
3106 Refresh(true);
3107 break;
3108
3109 case 'W': // W Toggle CPA alarm
3110 ToggleCPAWarn();
3111
3112 break;
3113
3114 case 1: // Ctrl A
3115 TogglebFollow();
3116
3117 break;
3118
3119 case 2: // Ctrl B
3120 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3121 break;
3122
3123 case 13: // Ctrl M // Drop Marker at cursor
3124 {
3125 if (event.ControlDown()) gFrame->DropMarker(false);
3126 break;
3127 }
3128
3129 case 14: // Ctrl N - Activate next waypoint in a route
3130 {
3131 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3132 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3133 if ((indexActive + 1) <= r->GetnPoints()) {
3135 InvalidateGL();
3136 Refresh(false);
3137 }
3138 }
3139 break;
3140 }
3141
3142 case 15: // Ctrl O - Drop Marker at boat's position
3143 {
3144 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3145 break;
3146 }
3147
3148 case 32: // Special needs use space bar
3149 {
3150 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3151 break;
3152 }
3153
3154 case -32: // Ctrl Space // Drop MOB
3155 {
3156 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3157
3158 break;
3159 }
3160
3161 case -20: // Ctrl ,
3162 {
3163 parent_frame->DoSettings();
3164 break;
3165 }
3166 case 17: // Ctrl Q
3167 parent_frame->Close();
3168 return;
3169
3170 case 18: // Ctrl R
3171 StartRoute();
3172 return;
3173
3174 case 20: // Ctrl T
3175 if (NULL == pGoToPositionDialog) // There is one global instance of
3176 // the Go To Position Dialog
3178 pGoToPositionDialog->SetCanvas(this);
3179 pGoToPositionDialog->Show();
3180 break;
3181
3182 case 25: // Ctrl Y
3183 if (undo->AnythingToRedo()) {
3184 undo->RedoNextAction();
3185 InvalidateGL();
3186 Refresh(false);
3187 }
3188 break;
3189
3190 case 26:
3191 if (event.ShiftDown()) { // Shift-Ctrl-Z
3192 if (undo->AnythingToRedo()) {
3193 undo->RedoNextAction();
3194 InvalidateGL();
3195 Refresh(false);
3196 }
3197 } else { // Ctrl Z
3198 if (undo->AnythingToUndo()) {
3199 undo->UndoLastAction();
3200 InvalidateGL();
3201 Refresh(false);
3202 }
3203 }
3204 break;
3205
3206 case 27:
3207 // Generic break
3208 if (m_bMeasure_Active) {
3209 CancelMeasureRoute();
3210
3211 SetCursor(*pCursorArrow);
3212
3213 // SurfaceToolbar();
3214 gFrame->RefreshAllCanvas();
3215 }
3216
3217 if (m_routeState) // creating route?
3218 {
3219 FinishRoute();
3220 // SurfaceToolbar();
3221 InvalidateGL();
3222 Refresh(false);
3223 }
3224
3225 break;
3226
3227 case 7: // Ctrl G
3228 switch (gamma_state) {
3229 case (0):
3230 r_gamma_mult = 0;
3231 g_gamma_mult = 1;
3232 b_gamma_mult = 0;
3233 gamma_state = 1;
3234 break;
3235 case (1):
3236 r_gamma_mult = 1;
3237 g_gamma_mult = 0;
3238 b_gamma_mult = 0;
3239 gamma_state = 2;
3240 break;
3241 case (2):
3242 r_gamma_mult = 1;
3243 g_gamma_mult = 1;
3244 b_gamma_mult = 1;
3245 gamma_state = 0;
3246 break;
3247 }
3248 SetScreenBrightness(g_nbrightness);
3249
3250 break;
3251
3252 case 9: // Ctrl I
3253 if (event.ControlDown()) {
3254 m_bShowCompassWin = !m_bShowCompassWin;
3255 SetShowGPSCompassWindow(m_bShowCompassWin);
3256 Refresh(false);
3257 }
3258 break;
3259
3260 default:
3261 break;
3262
3263 } // switch
3264 }
3265
3266 // Allow OnKeyChar to catch the key events too.
3267 if (!b_handled) {
3268 event.Skip();
3269 }
3270}
3271
3272void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3273 if (SendKeyEventToPlugins(event))
3274 return; // PlugIn did something, and does not want the canvas to do
3275 // anything else
3276
3277 switch (event.GetKeyCode()) {
3278 case WXK_TAB:
3279 parent_frame->SwitchKBFocus(this);
3280 break;
3281
3282 case WXK_LEFT:
3283 case WXK_RIGHT:
3284 m_panx = 0;
3285 if (!m_pany) m_panspeed = 0;
3286 break;
3287
3288 case WXK_UP:
3289 case WXK_DOWN:
3290 m_pany = 0;
3291 if (!m_panx) m_panspeed = 0;
3292 break;
3293
3294 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3295 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3296 case WXK_PAGEUP:
3297 case WXK_PAGEDOWN:
3298 if (m_mustmove) DoMovement(m_mustmove);
3299
3300 m_zoom_factor = 1;
3301 break;
3302
3303 case WXK_ALT:
3304 m_modkeys &= ~wxMOD_ALT;
3305#ifdef OCPN_ALT_MENUBAR
3306#ifndef __WXOSX__
3307 // If the permanent menu bar is disabled, and we are not in the middle of
3308 // another key combo, then show the menu bar temporarily when Alt is
3309 // released (or hide it if already visible).
3310 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3311 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3312 parent_frame->ApplyGlobalSettings(false);
3313 }
3314 m_bMayToggleMenuBar = true;
3315#endif
3316#endif
3317 break;
3318
3319 case WXK_CONTROL:
3320 m_modkeys &= ~wxMOD_CONTROL;
3321 break;
3322 }
3323
3324 if (event.GetKeyCode() < 128) // ascii
3325 {
3326 int key_char = event.GetKeyCode();
3327
3328 // Handle both QWERTY and AZERTY keyboard separately for a few control
3329 // codes
3330 if (!g_b_assume_azerty) {
3331 switch (key_char) {
3332 case '+':
3333 case '=':
3334 case '-':
3335 case '_':
3336 case 54:
3337 case 56: // '_' alpha/num pad
3338 DoMovement(m_mustmove);
3339
3340 // m_zoom_factor = 1;
3341 break;
3342 case '[':
3343 case ']':
3344 DoMovement(m_mustmove);
3345 m_rotation_speed = 0;
3346 break;
3347 }
3348 } else {
3349 switch (key_char) {
3350 case 43:
3351 case 54: // '-' alpha/num pad
3352 case 56: // '_' alpha/num pad
3353 DoMovement(m_mustmove);
3354
3355 m_zoom_factor = 1;
3356 break;
3357 }
3358 }
3359 }
3360 event.Skip();
3361}
3362
3363void ChartCanvas::ToggleChartOutlines() {
3364 m_bShowOutlines = !m_bShowOutlines;
3365
3366 Refresh(false);
3367
3368#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3369 // needs a full refresh
3370 if (g_bopengl) InvalidateGL();
3371#endif
3372}
3373
3374void ChartCanvas::ToggleLookahead() {
3375 m_bLookAhead = !m_bLookAhead;
3376 m_OSoffsetx = 0; // center ownship
3377 m_OSoffsety = 0;
3378}
3379
3380void ChartCanvas::SetUpMode(int mode) {
3381 m_upMode = mode;
3382
3383 if (mode != NORTH_UP_MODE) {
3384 // Stuff the COGAvg table in case COGUp is selected
3385 double stuff = 0;
3386 if (!std::isnan(gCog)) stuff = gCog;
3387
3388 if (g_COGAvgSec > 0) {
3389 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3390 }
3391 g_COGAvg = stuff;
3392 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3393 } else {
3394 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3395 SetVPRotation(GetVPSkew());
3396 else
3397 SetVPRotation(0); /* reset to north up */
3398 }
3399
3400 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3401 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3402
3403 UpdateGPSCompassStatusBox(true);
3404 gFrame->DoChartUpdate();
3405}
3406
3407bool ChartCanvas::DoCanvasCOGSet() {
3408 if (GetUpMode() == NORTH_UP_MODE) return false;
3409 double cog_use = g_COGAvg;
3410 if (g_btenhertz) cog_use = gCog;
3411
3412 double rotation = 0;
3413 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3414 rotation = -gHdt * PI / 180.;
3415 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3416 rotation = -cog_use * PI / 180.;
3417
3418 SetVPRotation(rotation);
3419 return true;
3420}
3421
3422double easeOutCubic(double t) {
3423 // Starts quickly and slows down toward the end
3424 return 1.0 - pow(1.0 - t, 3.0);
3425}
3426
3427void ChartCanvas::StartChartDragInertia() {
3428 m_bChartDragging = false;
3429
3430 // Set some parameters
3431 m_chart_drag_inertia_time = 750; // msec
3432 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3433 m_last_elapsed = 0;
3434
3435 // Calculate ending drag velocity
3436 size_t n_vel = 10;
3437 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3438 int xacc = 0;
3439 int yacc = 0;
3440 double tacc = 0;
3441 size_t length = m_drag_vec_t.size();
3442 for (size_t i = 0; i < n_vel; i++) {
3443 xacc += m_drag_vec_x.at(length - 1 - i);
3444 yacc += m_drag_vec_y.at(length - 1 - i);
3445 tacc += m_drag_vec_t.at(length - 1 - i);
3446 }
3447
3448 if (tacc == 0) return;
3449
3450 double drag_velocity_x = xacc / tacc;
3451 double drag_velocity_y = yacc / tacc;
3452 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3453 // drag_velocity_y);
3454
3455 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3456 // touch tap.
3457 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3458
3459 m_chart_drag_velocity_x = drag_velocity_x;
3460 m_chart_drag_velocity_y = drag_velocity_y;
3461
3462 m_chart_drag_inertia_active = true;
3463 // First callback as fast as possible.
3464 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3465}
3466
3467void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3468 if (!m_chart_drag_inertia_active) return;
3469 // Calculate time fraction from 0..1
3470 wxLongLong now = wxGetLocalTimeMillis();
3471 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3472 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3473 if (t > 1.0) t = 1.0;
3474 double e = 1.0 - easeOutCubic(t); // 0..1
3475
3476 double dx =
3477 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3478 double dy =
3479 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3480
3481 m_last_elapsed = elapsed;
3482
3483 // Ensure that target destination lies on whole-pixel boundary
3484 // This allows the render engine to use a faster FBO copy method for drawing
3485 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3486 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3487 double inertia_lat, inertia_lon;
3488 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3489 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3490 // Check if ownship has moved off-screen
3491 if (!IsOwnshipOnScreen()) {
3492 m_bFollow = false; // update the follow flag
3493 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3494 UpdateFollowButtonState();
3495 m_OSoffsetx = 0;
3496 m_OSoffsety = 0;
3497 } else {
3498 m_OSoffsetx += dx;
3499 m_OSoffsety -= dy;
3500 }
3501
3502 Refresh(false);
3503
3504 // Stop condition
3505 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3506 m_chart_drag_inertia_timer.Stop();
3507
3508 // Disable chart pan movement logic
3509 m_target_lat = GetVP().clat;
3510 m_target_lon = GetVP().clon;
3511 m_pan_drag.x = m_pan_drag.y = 0;
3512 m_panx = m_pany = 0;
3513 m_chart_drag_inertia_active = false;
3514 DoCanvasUpdate();
3515
3516 } else {
3517 int target_redraw_interval = 40; // msec
3518 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3519 }
3520}
3521
3522void ChartCanvas::StopMovement() {
3523 m_panx = m_pany = 0;
3524 m_panspeed = 0;
3525 m_zoom_factor = 1;
3526 m_rotation_speed = 0;
3527 m_mustmove = 0;
3528#if 0
3529#if !defined(__WXGTK__) && !defined(__WXQT__)
3530 SetFocus();
3531 gFrame->Raise();
3532#endif
3533#endif
3534}
3535
3536/* instead of integrating in timer callbacks
3537 (which do not always get called fast enough)
3538 we can perform the integration of movement
3539 at each render frame based on the time change */
3540bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3541 // Start/restart the stop movement timer
3542 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3543
3544 if (!pMovementTimer->IsRunning()) {
3545 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3546 }
3547
3548 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3549 // already moving, gets called again because of key-repeat event
3550 return false;
3551 }
3552
3553 m_last_movement_time = wxDateTime::UNow();
3554
3555 return true;
3556}
3557void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3558 int nstep) {
3559 // Save the target
3560 m_target_lat = target_lat;
3561 m_target_lon = target_lon;
3562
3563 // Save the start point
3564 m_start_lat = GetVP().clat;
3565 m_start_lon = GetVP().clon;
3566
3567 m_VPMovementTimer.Start(1, true); // oneshot
3568 m_timed_move_vp_active = true;
3569 m_stvpc = 0;
3570 m_timedVP_step = nstep;
3571}
3572
3573void ChartCanvas::DoTimedMovementVP() {
3574 if (!m_timed_move_vp_active) return; // not active
3575 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3576 StopMovement();
3577 return;
3578 }
3579 // Stop condition
3580 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3581 double d2 =
3582 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3583 d2 = pow(d2, 0.5);
3584
3585 if (d2 < one_pix) {
3586 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3587 StopMovementVP();
3588 return;
3589 }
3590
3591 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3592 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3593 // StopMovementVP();
3594 // return;
3595 // }
3596
3597 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3598 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3599
3600 m_run_lat = new_lat;
3601 m_run_lon = new_lon;
3602
3603 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3604}
3605
3606void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3607
3608void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3609
3610void ChartCanvas::StartTimedMovementTarget() {}
3611
3612void ChartCanvas::DoTimedMovementTarget() {}
3613
3614void ChartCanvas::StopMovementTarget() {}
3615int ntm;
3616
3617void ChartCanvas::DoTimedMovement() {
3618 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3619 !m_rotation_speed)
3620 return; /* not moving */
3621
3622 wxDateTime now = wxDateTime::UNow();
3623 long dt = 0;
3624 if (m_last_movement_time.IsValid())
3625 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3626
3627 m_last_movement_time = now;
3628
3629 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3630 dt = 500;
3631
3632 DoMovement(dt);
3633}
3634
3636 /* if we get here quickly assume 1ms so that some movement occurs */
3637 if (dt == 0) dt = 1;
3638
3639 m_mustmove -= dt;
3640 if (m_mustmove < 0) m_mustmove = 0;
3641
3642 if (!m_inPinch) { // this stops compound zoom/pan
3643 if (m_pan_drag.x || m_pan_drag.y) {
3644 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3645 m_pan_drag.x = m_pan_drag.y = 0;
3646 }
3647
3648 if (m_panx || m_pany) {
3649 const double slowpan = .1, maxpan = 2;
3650 if (m_modkeys == wxMOD_ALT)
3651 m_panspeed = slowpan;
3652 else {
3653 m_panspeed += (double)dt / 500; /* apply acceleration */
3654 m_panspeed = wxMin(maxpan, m_panspeed);
3655 }
3656 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3657 }
3658 }
3659 if (m_zoom_factor != 1) {
3660 double alpha = 400, beta = 1.5;
3661 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3662
3663 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3664
3665 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3666
3667 // Try to hit the zoom target exactly.
3668 // if(m_wheelzoom_stop_oneshot > 0)
3669 {
3670 if (zoom_factor > 1) {
3671 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3672 zoom_factor = VPoint.chart_scale / m_zoom_target;
3673 }
3674
3675 else if (zoom_factor < 1) {
3676 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3677 zoom_factor = VPoint.chart_scale / m_zoom_target;
3678 }
3679 }
3680
3681 if (fabs(zoom_factor - 1) > 1e-4) {
3682 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3683 } else {
3684 StopMovement();
3685 }
3686
3687 if (m_wheelzoom_stop_oneshot > 0) {
3688 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3689 m_wheelzoom_stop_oneshot = 0;
3690 StopMovement();
3691 }
3692
3693 // Don't overshoot the zoom target.
3694 if (zoom_factor > 1) {
3695 if (VPoint.chart_scale <= m_zoom_target) {
3696 m_wheelzoom_stop_oneshot = 0;
3697 StopMovement();
3698 }
3699 } else if (zoom_factor < 1) {
3700 if (VPoint.chart_scale >= m_zoom_target) {
3701 m_wheelzoom_stop_oneshot = 0;
3702 StopMovement();
3703 }
3704 }
3705 }
3706 }
3707
3708 if (m_rotation_speed) { /* in degrees per second */
3709 double speed = m_rotation_speed;
3710 if (m_modkeys == wxMOD_ALT) speed /= 10;
3711 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3712 }
3713}
3714
3715void ChartCanvas::SetColorScheme(ColorScheme cs) {
3716 SetAlertString("");
3717
3718 // Setup ownship image pointers
3719 switch (cs) {
3720 case GLOBAL_COLOR_SCHEME_DAY:
3721 m_pos_image_red = &m_os_image_red_day;
3722 m_pos_image_grey = &m_os_image_grey_day;
3723 m_pos_image_yellow = &m_os_image_yellow_day;
3724 m_pos_image_user = m_pos_image_user_day;
3725 m_pos_image_user_grey = m_pos_image_user_grey_day;
3726 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3727 m_cTideBitmap = m_bmTideDay;
3728 m_cCurrentBitmap = m_bmCurrentDay;
3729
3730 break;
3731 case GLOBAL_COLOR_SCHEME_DUSK:
3732 m_pos_image_red = &m_os_image_red_dusk;
3733 m_pos_image_grey = &m_os_image_grey_dusk;
3734 m_pos_image_yellow = &m_os_image_yellow_dusk;
3735 m_pos_image_user = m_pos_image_user_dusk;
3736 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3737 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3738 m_cTideBitmap = m_bmTideDusk;
3739 m_cCurrentBitmap = m_bmCurrentDusk;
3740 break;
3741 case GLOBAL_COLOR_SCHEME_NIGHT:
3742 m_pos_image_red = &m_os_image_red_night;
3743 m_pos_image_grey = &m_os_image_grey_night;
3744 m_pos_image_yellow = &m_os_image_yellow_night;
3745 m_pos_image_user = m_pos_image_user_night;
3746 m_pos_image_user_grey = m_pos_image_user_grey_night;
3747 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3748 m_cTideBitmap = m_bmTideNight;
3749 m_cCurrentBitmap = m_bmCurrentNight;
3750 break;
3751 default:
3752 m_pos_image_red = &m_os_image_red_day;
3753 m_pos_image_grey = &m_os_image_grey_day;
3754 m_pos_image_yellow = &m_os_image_yellow_day;
3755 m_pos_image_user = m_pos_image_user_day;
3756 m_pos_image_user_grey = m_pos_image_user_grey_day;
3757 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3758 m_cTideBitmap = m_bmTideDay;
3759 m_cCurrentBitmap = m_bmCurrentDay;
3760 break;
3761 }
3762
3763 CreateDepthUnitEmbossMaps(cs);
3764 CreateOZEmbossMapData(cs);
3765
3766 // Set up fog effect base color
3767 m_fog_color = wxColor(
3768 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3769 float dim = 1.0;
3770 switch (cs) {
3771 case GLOBAL_COLOR_SCHEME_DUSK:
3772 dim = 0.5;
3773 break;
3774 case GLOBAL_COLOR_SCHEME_NIGHT:
3775 dim = 0.25;
3776 break;
3777 default:
3778 break;
3779 }
3780 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3781 m_fog_color.Blue() * dim);
3782
3783 // Really dark
3784#if 0
3785 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3786 SetBackgroundColour( wxColour(0,0,0) );
3787
3788 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3789 }
3790 else{
3791 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3792#ifndef __WXMAC__
3793 SetBackgroundColour( wxNullColour );
3794#endif
3795 }
3796#endif
3797
3798 // UpdateToolbarColorScheme(cs);
3799
3800 m_Piano->SetColorScheme(cs);
3801
3802 m_Compass->SetColorScheme(cs);
3803
3804 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3805
3806 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3807
3808 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3809 if (m_notification_button) {
3810 m_notification_button->SetColorScheme(cs);
3811 }
3812
3813#ifdef ocpnUSE_GL
3814 if (g_bopengl && m_glcc) {
3815 m_glcc->SetColorScheme(cs);
3816 g_glTextureManager->ClearAllRasterTextures();
3817 // m_glcc->FlushFBO();
3818 }
3819#endif
3820 SetbTCUpdate(true); // force re-render of tide/current locators
3821 m_brepaint_piano = true;
3822
3823 ReloadVP();
3824
3825 m_cs = cs;
3826}
3827
3828wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3829 wxImage img = Bitmap.ConvertToImage();
3830 int sx = img.GetWidth();
3831 int sy = img.GetHeight();
3832
3833 wxImage new_img(img);
3834
3835 for (int i = 0; i < sx; i++) {
3836 for (int j = 0; j < sy; j++) {
3837 if (!img.IsTransparent(i, j)) {
3838 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3839 (unsigned char)(img.GetGreen(i, j) * factor),
3840 (unsigned char)(img.GetBlue(i, j) * factor));
3841 }
3842 }
3843 }
3844
3845 wxBitmap ret = wxBitmap(new_img);
3846
3847 return ret;
3848}
3849
3850void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3851 int max) {
3852 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3853 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3854
3855 if (!m_pBrightPopup) {
3856 // Calculate size
3857 int x, y;
3858 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3859
3860 m_pBrightPopup = new TimedPopupWin(this, 3);
3861
3862 m_pBrightPopup->SetSize(x, y);
3863 m_pBrightPopup->Move(120, 120);
3864 }
3865
3866 int bmpsx = m_pBrightPopup->GetSize().x;
3867 int bmpsy = m_pBrightPopup->GetSize().y;
3868
3869 wxBitmap bmp(bmpsx, bmpsx);
3870 wxMemoryDC mdc(bmp);
3871
3872 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3873 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3874 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3875 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3876 mdc.Clear();
3877
3878 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3879
3880 mdc.SetFont(*pfont);
3881 wxString val;
3882
3883 if (brightness == max)
3884 val = "MAX";
3885 else if (brightness == min)
3886 val = "MIN";
3887 else
3888 val.Printf("%3d", brightness);
3889
3890 mdc.DrawText(val, 0, 0);
3891
3892 mdc.SelectObject(wxNullBitmap);
3893
3894 m_pBrightPopup->SetBitmap(bmp);
3895 m_pBrightPopup->Show();
3896 m_pBrightPopup->Refresh();
3897}
3898
3899void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3900 m_b_rot_hidef = true;
3901 ReloadVP();
3902}
3903
3904void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3905 if (!g_bRollover) return;
3906
3907 bool b_need_refresh = false;
3908
3909 wxSize win_size = GetSize() * m_displayScale;
3910 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3911
3912 // Handle the AIS Rollover Window first
3913 bool showAISRollover = false;
3914 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3915 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3916 SelectItem *pFind = pSelectAIS->FindSelection(
3917 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3918 if (pFind) {
3919 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3920 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3921
3922 if (ptarget) {
3923 showAISRollover = true;
3924
3925 if (NULL == m_pAISRolloverWin) {
3926 m_pAISRolloverWin = new RolloverWin(this);
3927 m_pAISRolloverWin->IsActive(false);
3928 b_need_refresh = true;
3929 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3930 m_AISRollover_MMSI != FoundAIS_MMSI) {
3931 // Sometimes the mouse moves fast enough to get over a new AIS
3932 // target before the one-shot has fired to remove the old target.
3933 // Result: wrong target data is shown.
3934 // Detect this case,close the existing rollover ASAP, and restart
3935 // the timer.
3936 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3937 m_pAISRolloverWin->IsActive(false);
3938 m_AISRollover_MMSI = 0;
3939 Refresh();
3940 return;
3941 }
3942
3943 m_AISRollover_MMSI = FoundAIS_MMSI;
3944
3945 if (!m_pAISRolloverWin->IsActive()) {
3946 wxString s = ptarget->GetRolloverString();
3947 m_pAISRolloverWin->SetString(s);
3948
3949 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3950 AIS_ROLLOVER, win_size);
3951 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3952 m_pAISRolloverWin->IsActive(true);
3953 b_need_refresh = true;
3954 }
3955 }
3956 } else {
3957 m_AISRollover_MMSI = 0;
3958 showAISRollover = false;
3959 }
3960 }
3961
3962 // Maybe turn the rollover off
3963 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3964 m_pAISRolloverWin->IsActive(false);
3965 m_AISRollover_MMSI = 0;
3966 b_need_refresh = true;
3967 }
3968
3969 // Now the Route info rollover
3970 // Show the route segment info
3971 bool showRouteRollover = false;
3972
3973 if (NULL == m_pRolloverRouteSeg) {
3974 // Get a list of all selectable sgements, and search for the first
3975 // visible segment as the rollover target.
3976
3977 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3978 SelectableItemList SelList = pSelect->FindSelectionList(
3979 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3980 auto node = SelList.begin();
3981 while (node != SelList.end()) {
3982 SelectItem *pFindSel = *node;
3983
3984 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3985
3986 if (pr && pr->IsVisible()) {
3987 m_pRolloverRouteSeg = pFindSel;
3988 showRouteRollover = true;
3989
3990 if (NULL == m_pRouteRolloverWin) {
3991 m_pRouteRolloverWin = new RolloverWin(this, 10);
3992 m_pRouteRolloverWin->IsActive(false);
3993 }
3994
3995 if (!m_pRouteRolloverWin->IsActive()) {
3996 wxString s;
3997 RoutePoint *segShow_point_a =
3998 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3999 RoutePoint *segShow_point_b =
4000 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4001
4002 double brg, dist;
4003 DistanceBearingMercator(
4004 segShow_point_b->m_lat, segShow_point_b->m_lon,
4005 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4006
4007 if (!pr->m_bIsInLayer)
4008 s.Append(_("Route") + ": ");
4009 else
4010 s.Append(_("Layer Route: "));
4011
4012 if (pr->m_RouteNameString.IsEmpty())
4013 s.Append(_("(unnamed)"));
4014 else
4015 s.Append(pr->m_RouteNameString);
4016
4017 s << "\n"
4018 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4019 << "\n"
4020 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4021 << segShow_point_b->GetName() << "\n";
4022
4023 if (g_bShowTrue)
4024 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4025 (int)floor(brg + 0.5), 0x00B0);
4026 if (g_bShowMag) {
4027 double latAverage =
4028 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4029 double lonAverage =
4030 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4031 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4032
4033 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4034 (int)floor(varBrg + 0.5), 0x00B0);
4035 }
4036
4037 s << FormatDistanceAdaptive(dist);
4038
4039 // Compute and display cumulative distance from route start point to
4040 // current leg end point and RNG,TTG,ETA from ship to current leg end
4041 // point for active route
4042 double shiptoEndLeg = 0.;
4043 bool validActive = false;
4044 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4045 validActive = true;
4046
4047 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4048 auto node = pr->pRoutePointList->begin();
4049 RoutePoint *prp;
4050 float dist_to_endleg = 0;
4051 wxString t;
4052
4053 for (++node; node != pr->pRoutePointList->end(); ++node) {
4054 prp = *node;
4055 if (validActive)
4056 shiptoEndLeg += prp->m_seg_len;
4057 else if (prp->m_bIsActive)
4058 validActive = true;
4059 dist_to_endleg += prp->m_seg_len;
4060 if (prp->IsSame(segShow_point_a)) break;
4061 }
4062 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4063 }
4064 // write from ship to end selected leg point data if the route is
4065 // active
4066 if (validActive) {
4067 s << "\n"
4068 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4069 shiptoEndLeg +=
4071 ->GetCurrentRngToActivePoint(); // add distance from ship
4072 // to active point
4073 shiptoEndLeg +=
4074 segShow_point_b
4075 ->m_seg_len; // add the lenght of the selected leg
4076 s << FormatDistanceAdaptive(shiptoEndLeg);
4077 // ensure sog/cog are valid and vmg is positive to keep data
4078 // coherent
4079 double vmg = 0.;
4080 if (!std::isnan(gCog) && !std::isnan(gSog))
4081 vmg = gSog *
4082 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4083 PI / 180.);
4084 if (vmg > 0.) {
4085 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4086 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4087 s << " - "
4088 << wxString(ttg_sec > SECONDS_PER_DAY
4089 ? ttg_span.Format(_("%Dd %H:%M"))
4090 : ttg_span.Format(_("%H:%M")));
4091 wxDateTime dtnow, eta;
4092 eta = dtnow.SetToCurrent().Add(ttg_span);
4093 s << " - " << eta.Format("%b").Mid(0, 4)
4094 << eta.Format(" %d %H:%M");
4095 } else
4096 s << " ---- ----";
4097 }
4098 m_pRouteRolloverWin->SetString(s);
4099
4100 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4101 LEG_ROLLOVER, win_size);
4102 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4103 m_pRouteRolloverWin->IsActive(true);
4104 b_need_refresh = true;
4105 showRouteRollover = true;
4106 break;
4107 }
4108 } else {
4109 ++node;
4110 }
4111 }
4112 } else {
4113 // Is the cursor still in select radius, and not timed out?
4114 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4115 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4116 m_pRolloverRouteSeg))
4117 showRouteRollover = false;
4118 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4119 showRouteRollover = false;
4120 else
4121 showRouteRollover = true;
4122 }
4123
4124 // If currently creating a route, do not show this rollover window
4125 if (m_routeState) showRouteRollover = false;
4126
4127 // Similar for AIS target rollover window
4128 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4129 showRouteRollover = false;
4130
4131 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4132 !showRouteRollover) {
4133 m_pRouteRolloverWin->IsActive(false);
4134 m_pRolloverRouteSeg = NULL;
4135 m_pRouteRolloverWin->Destroy();
4136 m_pRouteRolloverWin = NULL;
4137 b_need_refresh = true;
4138 } else if (m_pRouteRolloverWin && showRouteRollover) {
4139 m_pRouteRolloverWin->IsActive(true);
4140 b_need_refresh = true;
4141 }
4142
4143 // Now the Track info rollover
4144 // Show the track segment info
4145 bool showTrackRollover = false;
4146
4147 if (NULL == m_pRolloverTrackSeg) {
4148 // Get a list of all selectable sgements, and search for the first
4149 // visible segment as the rollover target.
4150
4151 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4152 SelectableItemList SelList = pSelect->FindSelectionList(
4153 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4154
4155 auto node = SelList.begin();
4156 while (node != SelList.end()) {
4157 SelectItem *pFindSel = *node;
4158
4159 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4160
4161 if (pt && pt->IsVisible()) {
4162 m_pRolloverTrackSeg = pFindSel;
4163 showTrackRollover = true;
4164
4165 if (NULL == m_pTrackRolloverWin) {
4166 m_pTrackRolloverWin = new RolloverWin(this, 10);
4167 m_pTrackRolloverWin->IsActive(false);
4168 }
4169
4170 if (!m_pTrackRolloverWin->IsActive()) {
4171 wxString s;
4172 TrackPoint *segShow_point_a =
4173 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4174 TrackPoint *segShow_point_b =
4175 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4176
4177 double brg, dist;
4178 DistanceBearingMercator(
4179 segShow_point_b->m_lat, segShow_point_b->m_lon,
4180 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4181
4182 if (!pt->m_bIsInLayer)
4183 s.Append(_("Track") + ": ");
4184 else
4185 s.Append(_("Layer Track: "));
4186
4187 if (pt->GetName().IsEmpty())
4188 s.Append(_("(unnamed)"));
4189 else
4190 s.Append(pt->GetName());
4191 double tlenght = pt->Length();
4192 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4193 if (pt->GetLastPoint()->GetTimeString() &&
4194 pt->GetPoint(0)->GetTimeString()) {
4195 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4196 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4197 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4198 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4199 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4200 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4201 << getUsrSpeedUnit();
4202 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4203 : ttime.Format(" %H:%M"));
4204 }
4205 }
4206
4207 if (g_bShowTrackPointTime &&
4208 strlen(segShow_point_b->GetTimeString())) {
4209 wxString stamp = segShow_point_b->GetTimeString();
4210 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4211 if (timestamp.IsValid()) {
4212 // Format track rollover timestamp to OCPN global TZ setting
4215 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4216 }
4217 s << "\n" << _("Segment Created: ") << stamp;
4218 }
4219
4220 s << "\n";
4221 if (g_bShowTrue)
4222 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4223 0x00B0);
4224
4225 if (g_bShowMag) {
4226 double latAverage =
4227 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4228 double lonAverage =
4229 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4230 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4231
4232 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4233 0x00B0);
4234 }
4235
4236 s << FormatDistanceAdaptive(dist);
4237
4238 if (segShow_point_a->GetTimeString() &&
4239 segShow_point_b->GetTimeString()) {
4240 wxDateTime apoint = segShow_point_a->GetCreateTime();
4241 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4242 if (apoint.IsValid() && bpoint.IsValid()) {
4243 double segmentSpeed = toUsrSpeed(
4244 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4245 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4246 << getUsrSpeedUnit();
4247 }
4248 }
4249
4250 m_pTrackRolloverWin->SetString(s);
4251
4252 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4253 LEG_ROLLOVER, win_size);
4254 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4255 m_pTrackRolloverWin->IsActive(true);
4256 b_need_refresh = true;
4257 showTrackRollover = true;
4258 break;
4259 }
4260 } else {
4261 ++node;
4262 }
4263 }
4264 } else {
4265 // Is the cursor still in select radius, and not timed out?
4266 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4267 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4268 m_pRolloverTrackSeg))
4269 showTrackRollover = false;
4270 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4271 showTrackRollover = false;
4272 else
4273 showTrackRollover = true;
4274 }
4275
4276 // Similar for AIS target rollover window
4277 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4278 showTrackRollover = false;
4279
4280 // Similar for route rollover window
4281 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4282 showTrackRollover = false;
4283
4284 // TODO We onlt show tracks on primary canvas....
4285 // if(!IsPrimaryCanvas())
4286 // showTrackRollover = false;
4287
4288 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4289 !showTrackRollover) {
4290 m_pTrackRolloverWin->IsActive(false);
4291 m_pRolloverTrackSeg = NULL;
4292 m_pTrackRolloverWin->Destroy();
4293 m_pTrackRolloverWin = NULL;
4294 b_need_refresh = true;
4295 } else if (m_pTrackRolloverWin && showTrackRollover) {
4296 m_pTrackRolloverWin->IsActive(true);
4297 b_need_refresh = true;
4298 }
4299
4300 if (b_need_refresh) Refresh();
4301}
4302
4303void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4304 if ((GetShowENCLights() || m_bsectors_shown) &&
4305 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4306 extendedSectorLegs)) {
4307 if (!m_bsectors_shown) {
4308 ReloadVP(false);
4309 m_bsectors_shown = true;
4310 }
4311 } else {
4312 if (m_bsectors_shown) {
4313 ReloadVP(false);
4314 m_bsectors_shown = false;
4315 }
4316 }
4317
4318// This is here because GTK status window update is expensive..
4319// cairo using pango rebuilds the font every time so is very
4320// inefficient
4321// Anyway, only update the status bar when this timer expires
4322#if defined(__WXGTK__) || defined(__WXQT__)
4323 {
4324 // Check the absolute range of the cursor position
4325 // There could be a window wherein the chart geoereferencing is not
4326 // valid....
4327 double cursor_lat, cursor_lon;
4328 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4329
4330 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4331 while (cursor_lon < -180.) cursor_lon += 360.;
4332
4333 while (cursor_lon > 180.) cursor_lon -= 360.;
4334
4335 SetCursorStatus(cursor_lat, cursor_lon);
4336 }
4337 }
4338#endif
4339}
4340
4341void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4342 if (!parent_frame->m_pStatusBar) return;
4343
4344 wxString s1;
4345 s1 += " ";
4346 s1 += toSDMM(1, cursor_lat);
4347 s1 += " ";
4348 s1 += toSDMM(2, cursor_lon);
4349
4350 if (STAT_FIELD_CURSOR_LL >= 0)
4351 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4352
4353 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4354
4355 double brg, dist;
4356 wxString sm;
4357 wxString st;
4358 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4359 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4360 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4361
4362 wxString s = st + sm;
4363 s << FormatDistanceAdaptive(dist);
4364
4365 // CUSTOMIZATION - LIVE ETA OPTION
4366 // -------------------------------------------------------
4367 // Calculate an "live" ETA based on route starting from the current
4368 // position of the boat and goes to the cursor of the mouse.
4369 // In any case, an standard ETA will be calculated with a default speed
4370 // of the boat to give an estimation of the route (in particular if GPS
4371 // is off).
4372
4373 // Display only if option "live ETA" is selected in Settings > Display >
4374 // General.
4375 if (g_bShowLiveETA) {
4376 float realTimeETA;
4377 float boatSpeed;
4378 float boatSpeedDefault = g_defaultBoatSpeed;
4379
4380 // Calculate Estimate Time to Arrival (ETA) in minutes
4381 // Check before is value not closed to zero (it will make an very big
4382 // number...)
4383 if (!std::isnan(gSog)) {
4384 boatSpeed = gSog;
4385 if (boatSpeed < 0.5) {
4386 realTimeETA = 0;
4387 } else {
4388 realTimeETA = dist / boatSpeed * 60;
4389 }
4390 } else {
4391 realTimeETA = 0;
4392 }
4393
4394 // Add space after distance display
4395 s << " ";
4396 // Display ETA
4397 s << minutesToHoursDays(realTimeETA);
4398
4399 // In any case, display also an ETA with default speed at 6knts
4400
4401 s << " [@";
4402 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4403 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4404 s << " ";
4405 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4406 s << "]";
4407 }
4408 // END OF - LIVE ETA OPTION
4409
4410 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4411}
4412
4413// CUSTOMIZATION - FORMAT MINUTES
4414// -------------------------------------------------------
4415// New function to format minutes into a more readable format:
4416// * Hours + minutes, or
4417// * Days + hours.
4418wxString minutesToHoursDays(float timeInMinutes) {
4419 wxString s;
4420
4421 if (timeInMinutes == 0) {
4422 s << "--min";
4423 }
4424
4425 // Less than 60min, keep time in minutes
4426 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4427 s << wxString::Format("%d", (int)timeInMinutes);
4428 s << "min";
4429 }
4430
4431 // Between 1h and less than 24h, display time in hours, minutes
4432 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4433 int hours;
4434 int min;
4435 hours = (int)timeInMinutes / 60;
4436 min = (int)timeInMinutes % 60;
4437
4438 if (min == 0) {
4439 s << wxString::Format("%d", hours);
4440 s << "h";
4441 } else {
4442 s << wxString::Format("%d", hours);
4443 s << "h";
4444 s << wxString::Format("%d", min);
4445 s << "min";
4446 }
4447
4448 }
4449
4450 // More than 24h, display time in days, hours
4451 else if (timeInMinutes > 24 * 60) {
4452 int days;
4453 int hours;
4454 days = (int)(timeInMinutes / 60) / 24;
4455 hours = (int)(timeInMinutes / 60) % 24;
4456
4457 if (hours == 0) {
4458 s << wxString::Format("%d", days);
4459 s << "d";
4460 } else {
4461 s << wxString::Format("%d", days);
4462 s << "d";
4463 s << wxString::Format("%d", hours);
4464 s << "h";
4465 }
4466 }
4467
4468 return s;
4469}
4470
4471// END OF CUSTOMIZATION - FORMAT MINUTES
4472// Thanks open source code ;-)
4473// -------------------------------------------------------
4474
4475void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4476 double clat, clon;
4477 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4478 *lat = clat;
4479 *lon = clon;
4480}
4481
4482void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4483 wxPoint2DDouble *r) {
4484 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4485}
4486
4488 double rlon, wxPoint2DDouble *r) {
4489 // If the Current Chart is a raster chart, and the
4490 // requested lat/long is within the boundaries of the chart,
4491 // and the VP is not rotated,
4492 // then use the embedded BSB chart georeferencing algorithm
4493 // for greater accuracy
4494 // Additionally, use chart embedded georef if the projection is TMERC
4495 // i.e. NOT MERCATOR and NOT POLYCONIC
4496
4497 // If for some reason the chart rejects the request by returning an error,
4498 // then fall back to Viewport Projection estimate from canvas parameters
4499 if (!g_bopengl && m_singleChart &&
4500 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4501 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4502 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4503 (m_singleChart->GetChartProjectionType() !=
4504 PROJECTION_TRANSVERSE_MERCATOR) &&
4505 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4506 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4507 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4508 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4509 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4510 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4511 // Cur_BSB_Ch->GetCOVRTablenPoints
4512 // ( 0 ), rlon,
4513 // rlat );
4514 // bInside = true;
4515 // if ( bInside )
4516 if (Cur_BSB_Ch) {
4517 // This is a Raster chart....
4518 // If the VP is changing, the raster chart parameters may not yet be
4519 // setup So do that before accessing the chart's embedded
4520 // georeferencing
4521 Cur_BSB_Ch->SetVPRasterParms(vp);
4522 double rpixxd, rpixyd;
4523 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4524 r->m_x = rpixxd;
4525 r->m_y = rpixyd;
4526 return;
4527 }
4528 }
4529 }
4530
4531 // if needed, use the VPoint scaling estimator,
4532 *r = vp.GetDoublePixFromLL(rlat, rlon);
4533}
4534
4535// This routine might be deleted and all of the rendering improved
4536// to have floating point accuracy
4537bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4538 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4539}
4540
4541bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4542 wxPoint *r) {
4543 wxPoint2DDouble p;
4544 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4545
4546 // some projections give nan values when invisible values (other side of
4547 // world) are requested we should stop using integer coordinates or return
4548 // false here (and test it everywhere)
4549 if (std::isnan(p.m_x)) {
4550 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4551 return false;
4552 }
4553
4554 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4555 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4556 else
4557 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4558
4559 return true;
4560}
4561
4562void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4563 double &lon) {
4564 // If the Current Chart is a raster chart, and the
4565 // requested x,y is within the boundaries of the chart,
4566 // and the VP is not rotated,
4567 // then use the embedded BSB chart georeferencing algorithm
4568 // for greater accuracy
4569 // Additionally, use chart embedded georef if the projection is TMERC
4570 // i.e. NOT MERCATOR and NOT POLYCONIC
4571
4572 // If for some reason the chart rejects the request by returning an error,
4573 // then fall back to Viewport Projection estimate from canvas parameters
4574 bool bUseVP = true;
4575
4576 if (!g_bopengl && m_singleChart &&
4577 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4578 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4579 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4580 (m_singleChart->GetChartProjectionType() !=
4581 PROJECTION_TRANSVERSE_MERCATOR) &&
4582 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4583 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4584 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4585 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4586
4587 // TODO maybe need iterative process to validate bInside
4588 // first pass is mercator, then check chart boundaries
4589
4590 if (Cur_BSB_Ch) {
4591 // This is a Raster chart....
4592 // If the VP is changing, the raster chart parameters may not yet be
4593 // setup So do that before accessing the chart's embedded
4594 // georeferencing
4595 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4596
4597 double slat, slon;
4598 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4599 lat = slat;
4600
4601 if (slon < -180.)
4602 slon += 360.;
4603 else if (slon > 180.)
4604 slon -= 360.;
4605
4606 lon = slon;
4607 bUseVP = false;
4608 }
4609 }
4610 }
4611
4612 // if needed, use the VPoint scaling estimator
4613 if (bUseVP) {
4614 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4615 }
4616}
4617
4619 StopMovement();
4620 DoZoomCanvas(factor, false);
4621 extendedSectorLegs.clear();
4622}
4623
4624void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4625 bool stoptimer) {
4626 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4627
4628 if (g_bsmoothpanzoom) {
4629 if (StartTimedMovement(stoptimer)) {
4630 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4631 m_zoom_factor = factor;
4632 }
4633
4634 m_zoom_target = VPoint.chart_scale / factor;
4635 } else {
4636 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4637
4638 DoZoomCanvas(factor, can_zoom_to_cursor);
4639 }
4640
4641 extendedSectorLegs.clear();
4642}
4643
4644void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4645 // possible on startup
4646 if (!ChartData) return;
4647 if (!m_pCurrentStack) return;
4648
4649 /* TODO: queue the quilted loading code to a background thread
4650 so yield is never called from here, and also rendering is not delayed */
4651
4652 // Cannot allow Yield() re-entrancy here
4653 if (m_bzooming) return;
4654 m_bzooming = true;
4655
4656 double old_ppm = GetVP().view_scale_ppm;
4657
4658 // Capture current cursor position for zoom to cursor
4659 double zlat = m_cursor_lat;
4660 double zlon = m_cursor_lon;
4661
4662 double proposed_scale_onscreen =
4663 GetVP().chart_scale /
4664 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4665 bool b_do_zoom = false;
4666
4667 if (factor > 1) {
4668 b_do_zoom = true;
4669
4670 // double zoom_factor = factor;
4671
4672 ChartBase *pc = NULL;
4673
4674 if (!VPoint.b_quilt) {
4675 pc = m_singleChart;
4676 } else {
4677 if (!m_disable_adjust_on_zoom) {
4678 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4679 if (new_db_index >= 0)
4680 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4681 else { // for whatever reason, no reference chart is known
4682 // Choose the smallest scale chart on the current stack
4683 // and then adjust for scale range
4684 int current_ref_stack_index = -1;
4685 if (m_pCurrentStack->nEntry) {
4686 int trial_index =
4687 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4688 m_pQuilt->SetReferenceChart(trial_index);
4689 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4690 if (new_db_index >= 0)
4691 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4692 }
4693 }
4694
4695 if (m_pCurrentStack)
4696 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4697 new_db_index); // highlite the correct bar entry
4698 }
4699 }
4700
4701 if (pc) {
4702 // double target_scale_ppm = GetVPScale() * zoom_factor;
4703 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4704 // target_scale_ppm;
4705
4706 // Query the chart to determine the appropriate zoom range
4707 double min_allowed_scale =
4708 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4709
4710 if (proposed_scale_onscreen < min_allowed_scale) {
4711 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4712 m_zoom_factor = 1; /* stop zooming */
4713 b_do_zoom = false;
4714 } else
4715 proposed_scale_onscreen = min_allowed_scale;
4716 }
4717
4718 } else {
4719 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4720 }
4721
4722 } else if (factor < 1) {
4723 b_do_zoom = true;
4724
4725 ChartBase *pc = NULL;
4726
4727 bool b_smallest = false;
4728
4729 if (!VPoint.b_quilt) { // not quilted
4730 pc = m_singleChart;
4731
4732 if (pc) {
4733 // If m_singleChart is not on the screen, unbound the zoomout
4734 LLBBox viewbox = VPoint.GetBBox();
4735 // BoundingBox chart_box;
4736 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4737 double max_allowed_scale;
4738
4739 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4740
4741 // We can allow essentially unbounded zoomout in single chart mode
4742 // if( ChartData->GetDBBoundingBox( current_index,
4743 // &chart_box ) &&
4744 // !viewbox.IntersectOut( chart_box ) )
4745 // // Clamp the minimum scale zoom-out to the value
4746 // specified by the chart max_allowed_scale =
4747 // wxMin(max_allowed_scale, 4.0 *
4748 // pc->GetNormalScaleMax(
4749 // GetCanvasScaleFactor(),
4750 // GetCanvasWidth() ) );
4751 if (proposed_scale_onscreen > max_allowed_scale) {
4752 m_zoom_factor = 1; /* stop zooming */
4753 proposed_scale_onscreen = max_allowed_scale;
4754 }
4755 }
4756
4757 } else {
4758 if (!m_disable_adjust_on_zoom) {
4759 int new_db_index =
4760 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4761 if (new_db_index >= 0)
4762 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4763
4764 if (m_pCurrentStack)
4765 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4766 new_db_index); // highlite the correct bar entry
4767
4768 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4769
4770 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4771 proposed_scale_onscreen =
4772 wxMin(proposed_scale_onscreen,
4773 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4774 }
4775
4776 // set a minimum scale
4777 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4778 m_absolute_min_scale_ppm)
4779 proposed_scale_onscreen =
4780 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4781 }
4782 }
4783 double new_scale =
4784 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4785
4786 if (b_do_zoom) {
4787 // Disable ZTC if lookahead is ON, and currently b_follow is active
4788 bool b_allow_ztc = true;
4789 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4790 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4791 if (m_bLookAhead) {
4792 double brg, distance;
4793 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4794 &distance);
4795 dir_to_shift = brg;
4796 meters_to_shift = distance * 1852;
4797 }
4798 // Arrange to combine the zoom and pan into one operation for smoother
4799 // appearance
4800 SetVPScale(new_scale, false); // adjust, but deferred refresh
4801 wxPoint r;
4802 GetCanvasPointPix(zlat, zlon, &r);
4803 // this will emit the Refresh()
4804 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4805 } else {
4806 SetVPScale(new_scale);
4807 if (m_bFollow) DoCanvasUpdate();
4808 }
4809 }
4810
4811 m_bzooming = false;
4812}
4813
4814void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4815 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4816 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4817}
4818
4819int rot;
4820void ChartCanvas::RotateCanvas(double dir) {
4821 // SetUpMode(NORTH_UP_MODE);
4822
4823 if (g_bsmoothpanzoom) {
4824 if (StartTimedMovement()) {
4825 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4826 m_rotation_speed = dir * 60;
4827 }
4828 } else {
4829 double speed = dir * 10;
4830 if (m_modkeys == wxMOD_ALT) speed /= 20;
4831 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4832 }
4833}
4834
4835void ChartCanvas::DoRotateCanvas(double rotation) {
4836 while (rotation < 0) rotation += 2 * PI;
4837 while (rotation > 2 * PI) rotation -= 2 * PI;
4838
4839 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4840
4841 SetVPRotation(rotation);
4842 parent_frame->UpdateRotationState(VPoint.rotation);
4843}
4844
4845void ChartCanvas::DoTiltCanvas(double tilt) {
4846 while (tilt < 0) tilt = 0;
4847 while (tilt > .95) tilt = .95;
4848
4849 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4850
4851 VPoint.tilt = tilt;
4852 Refresh(false);
4853}
4854
4855void ChartCanvas::TogglebFollow() {
4856 if (!m_bFollow)
4857 SetbFollow();
4858 else
4859 ClearbFollow();
4860}
4861
4862void ChartCanvas::ClearbFollow() {
4863 m_bFollow = false; // update the follow flag
4864
4865 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4866
4867 UpdateFollowButtonState();
4868
4869 DoCanvasUpdate();
4870 ReloadVP();
4871 parent_frame->SetChartUpdatePeriod();
4872}
4873
4874void ChartCanvas::SetbFollow() {
4875 // Is the OWNSHIP on-screen?
4876 // If not, then reset the OWNSHIP offset to 0 (center screen)
4877 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4878 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4879 m_OSoffsetx = 0;
4880 m_OSoffsety = 0;
4881 }
4882
4883 // Apply the present b_follow offset values to ship position
4884 wxPoint2DDouble p;
4886 p.m_x += m_OSoffsetx;
4887 p.m_y -= m_OSoffsety;
4888
4889 // compute the target center screen lat/lon
4890 double dlat, dlon;
4891 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4892
4893 JumpToPosition(dlat, dlon, GetVPScale());
4894 m_bFollow = true;
4895
4896 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4897 UpdateFollowButtonState();
4898
4899 if (!g_bSmoothRecenter) {
4900 DoCanvasUpdate();
4901 ReloadVP();
4902 }
4903 parent_frame->SetChartUpdatePeriod();
4904}
4905
4906void ChartCanvas::UpdateFollowButtonState() {
4907 if (m_muiBar) {
4908 if (!m_bFollow)
4909 m_muiBar->SetFollowButtonState(0);
4910 else {
4911 if (m_bLookAhead)
4912 m_muiBar->SetFollowButtonState(2);
4913 else
4914 m_muiBar->SetFollowButtonState(1);
4915 }
4916 }
4917
4918#ifdef __ANDROID__
4919 if (!m_bFollow)
4920 androidSetFollowTool(0);
4921 else {
4922 if (m_bLookAhead)
4923 androidSetFollowTool(2);
4924 else
4925 androidSetFollowTool(1);
4926 }
4927#endif
4928
4929 // Look for plugin using API-121 or later
4930 // If found, make the follow state callback.
4931 if (g_pi_manager) {
4932 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4933 if (pic->m_enabled && pic->m_init_state) {
4934 switch (pic->m_api_version) {
4935 case 121: {
4936 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4937 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4938 break;
4939 }
4940 default:
4941 break;
4942 }
4943 }
4944 }
4945 }
4946}
4947
4948void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4949 if (g_bSmoothRecenter && !m_routeState) {
4950 if (StartSmoothJump(lat, lon, scale_ppm))
4951 return;
4952 else {
4953 // move closer to the target destination, and try again
4954 double gcDist, gcBearingEnd;
4955 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4956 &gcBearingEnd);
4957 gcBearingEnd += 180;
4958 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4959 GetCanvasWidth() / GetVPScale(); // meters
4960 double lon_offset =
4961 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4962 double new_lat = lat + (lat_offset / (1852 * 60));
4963 double new_lon = lon + (lon_offset / (1852 * 60));
4964 SetViewPoint(new_lat, new_lon);
4965 ReloadVP();
4966 StartSmoothJump(lat, lon, scale_ppm);
4967 return;
4968 }
4969 }
4970
4971 if (lon > 180.0) lon -= 360.0;
4972 m_vLat = lat;
4973 m_vLon = lon;
4974 StopMovement();
4975 m_bFollow = false;
4976
4977 if (!GetQuiltMode()) {
4978 double skew = 0;
4979 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4980 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4981 } else {
4982 if (scale_ppm != GetVPScale()) {
4983 // XXX should be done in SetViewPoint
4984 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4985 AdjustQuiltRefChart();
4986 }
4987 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4988 }
4989
4990 ReloadVP();
4991
4992 UpdateFollowButtonState();
4993
4994 // TODO
4995 // if( g_pi_manager ) {
4996 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4997 // }
4998}
4999
5000bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5001 // Check distance to jump, in pixels at current chart scale
5002 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5003 // width.
5004 double gcDist;
5005 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5006 double distance_pixels = gcDist * GetVPScale();
5007 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5008 // Jump is too far, try again
5009 return false;
5010 }
5011
5012 // Save where we're coming from
5013 m_startLat = m_vLat;
5014 m_startLon = m_vLon;
5015 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5016
5017 // Save where we want to end up
5018 m_endLat = lat;
5019 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5020 m_endScale = scale_ppm;
5021
5022 // Setup timing
5023 m_animationDuration = 600; // ms
5024 m_animationStart = wxGetLocalTimeMillis();
5025
5026 // Stop any previous movement, ensure no conflicts
5027 StopMovement();
5028 m_bFollow = false;
5029
5030 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5031 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5032 m_animationActive = true;
5033
5034 return true;
5035}
5036
5037void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5038 // Calculate time fraction from 0..1
5039 wxLongLong now = wxGetLocalTimeMillis();
5040 double elapsed = (now - m_animationStart).ToDouble();
5041 double t = elapsed / m_animationDuration.ToDouble();
5042 if (t > 1.0) t = 1.0;
5043
5044 // Ease function for smoother movement
5045 double e = easeOutCubic(t);
5046
5047 // Interpolate lat/lon/scale
5048 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5049 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5050 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5051
5052 // Update viewpoint
5053 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5054 // portion)
5055 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5056 ReloadVP();
5057
5058 // If we reached the end, stop the timer and finalize
5059 if (t >= 1.0) {
5060 m_easeTimer.Stop();
5061 m_animationActive = false;
5062 UpdateFollowButtonState();
5063 ZoomCanvasSimple(1.0001);
5064 DoCanvasUpdate();
5065 ReloadVP();
5066 }
5067}
5068
5069bool ChartCanvas::PanCanvas(double dx, double dy) {
5070 if (!ChartData) return false;
5071 extendedSectorLegs.clear();
5072
5073 double dlat, dlon;
5074 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5075
5076 int iters = 0;
5077 for (;;) {
5078 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5079
5080 if (iters++ > 5) return false;
5081 if (!std::isnan(dlat)) break;
5082
5083 dx *= .5, dy *= .5;
5084 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5085 }
5086
5087 // avoid overshooting the poles
5088 if (dlat > 90)
5089 dlat = 90;
5090 else if (dlat < -90)
5091 dlat = -90;
5092
5093 if (dlon > 360.) dlon -= 360.;
5094 if (dlon < -360.) dlon += 360.;
5095
5096 // This should not really be necessary, but round-trip georef on some
5097 // charts is not perfect, So we can get creep on repeated unidimensional
5098 // pans, and corrupt chart cacheing.......
5099
5100 // But this only works on north-up projections
5101 // TODO: can we remove this now?
5102 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5103 // .001 ) ) {
5104 //
5105 // if( dx == 0 ) dlon = clon;
5106 // if( dy == 0 ) dlat = clat;
5107 // }
5108
5109 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5110
5111 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5112
5113 if (VPoint.b_quilt) {
5114 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5115 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5116 // Tweak the scale slightly for a new ref chart
5117 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5118 if (pc) {
5119 double tweak_scale_ppm =
5120 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5121 SetVPScale(tweak_scale_ppm);
5122 }
5123 }
5124
5125 if (new_ref_dbIndex == -1) {
5126#pragma GCC diagnostic push
5127#pragma GCC diagnostic ignored "-Warray-bounds"
5128 // The compiler sees a -1 index being used. Does not happen, though.
5129
5130 // for whatever reason, no reference chart is known
5131 // Probably panned out of the coverage region
5132 // If any charts are anywhere on-screen, choose the smallest
5133 // scale chart on the screen to be a new reference chart.
5134 int trial_index = -1;
5135 if (m_pCurrentStack->nEntry) {
5136 int trial_index =
5137 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5138 }
5139
5140 if (trial_index < 0) {
5141 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5142 if (full_screen_array.size())
5143 trial_index = full_screen_array[full_screen_array.size() - 1];
5144 }
5145
5146 if (trial_index >= 0) {
5147 m_pQuilt->SetReferenceChart(trial_index);
5148 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5149 VPoint.rotation);
5150 ReloadVP();
5151 }
5152#pragma GCC diagnostic pop
5153 }
5154 }
5155
5156 // Turn off bFollow only if the ownship has left the screen
5157 if (m_bFollow) {
5158 double offx, offy;
5159 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5160
5161 double offset_angle = atan2(offy, offx);
5162 double offset_distance = sqrt((offy * offy) + (offx * offx));
5163 double chart_angle = GetVPRotation();
5164 double target_angle = chart_angle - offset_angle;
5165 double d_east_mod = offset_distance * cos(target_angle);
5166 double d_north_mod = offset_distance * sin(target_angle);
5167
5168 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5169 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5170
5171 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5172 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5173 m_bFollow = false; // update the follow flag
5174 UpdateFollowButtonState();
5175 }
5176 }
5177
5178 Refresh(false);
5179
5180 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5181
5182 return true;
5183}
5184
5185bool ChartCanvas::IsOwnshipOnScreen() {
5186 wxPoint r;
5188 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5189 ((r.y > 0) && r.y < GetCanvasHeight()))
5190 return true;
5191 else
5192 return false;
5193}
5194
5195void ChartCanvas::ReloadVP(bool b_adjust) {
5196 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5197
5198 LoadVP(VPoint, b_adjust);
5199}
5200
5201void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5202#ifdef ocpnUSE_GL
5203 if (g_bopengl && m_glcc) {
5204 m_glcc->Invalidate();
5205 if (m_glcc->GetSize() != GetSize()) {
5206 m_glcc->SetSize(GetSize());
5207 }
5208 } else
5209#endif
5210 {
5211 m_cache_vp.Invalidate();
5212 m_bm_cache_vp.Invalidate();
5213 }
5214
5215 VPoint.Invalidate();
5216
5217 if (m_pQuilt) m_pQuilt->Invalidate();
5218
5219 // Make sure that the Selected Group is sensible...
5220 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5221 // m_groupIndex = 0;
5222 // if( !CheckGroup( m_groupIndex ) )
5223 // m_groupIndex = 0;
5224
5225 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5226 vp.m_projection_type, b_adjust);
5227}
5228
5229void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5230 m_pQuilt->SetReferenceChart(dbIndex);
5231 VPoint.Invalidate();
5232 m_pQuilt->Invalidate();
5233}
5234
5235double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5236 if (m_pQuilt)
5237 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5238 else
5239 return vp.view_scale_ppm;
5240}
5241
5242// Verify and adjust the current reference chart,
5243// so that it will not lead to excessive overzoom or underzoom onscreen
5244int ChartCanvas::AdjustQuiltRefChart() {
5245 int ret = -1;
5246 if (m_pQuilt) {
5247 wxASSERT(ChartData);
5248 ChartBase *pc =
5249 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5250 if (pc) {
5251 double min_ref_scale =
5252 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5253 double max_ref_scale =
5254 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5255
5256 if (VPoint.chart_scale < min_ref_scale) {
5257 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5258 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5259 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5260 } else {
5261 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5262
5263 if (!brender_ok) {
5264 int target_stack_index = wxNOT_FOUND;
5265 int il = 0;
5266 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5267 if (index == m_pQuilt->GetRefChartdbIndex()) {
5268 target_stack_index = il;
5269 break;
5270 }
5271 il++;
5272 }
5273 if (wxNOT_FOUND == target_stack_index) // should never happen...
5274 target_stack_index = 0;
5275
5276 int ref_family = pc->GetChartFamily();
5277 int extended_array_count =
5278 m_pQuilt->GetExtendedStackIndexArray().size();
5279 while ((!brender_ok) &&
5280 ((int)target_stack_index < (extended_array_count - 1))) {
5281 target_stack_index++;
5282 int test_db_index =
5283 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5284
5285 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5286 IsChartQuiltableRef(test_db_index)) {
5287 // open the target, and check the min_scale
5288 ChartBase *ptest_chart =
5289 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5290 if (ptest_chart) {
5291 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5292 }
5293 }
5294 }
5295
5296 if (brender_ok) { // found a better reference chart
5297 int new_db_index =
5298 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5299 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5300 IsChartQuiltableRef(new_db_index)) {
5301 m_pQuilt->SetReferenceChart(new_db_index);
5302 ret = new_db_index;
5303 } else
5304 ret = m_pQuilt->GetRefChartdbIndex();
5305 } else
5306 ret = m_pQuilt->GetRefChartdbIndex();
5307
5308 } else
5309 ret = m_pQuilt->GetRefChartdbIndex();
5310 }
5311 } else
5312 ret = -1;
5313 }
5314
5315 return ret;
5316}
5317
5318void ChartCanvas::UpdateCanvasOnGroupChange() {
5319 delete m_pCurrentStack;
5320 m_pCurrentStack = new ChartStack;
5321 wxASSERT(ChartData);
5322 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5323 m_groupIndex);
5324
5325 if (m_pQuilt) {
5326 m_pQuilt->Compose(VPoint);
5327 SetFocus();
5328 }
5329}
5330
5331bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5332 double latNE, double lonNE) {
5333 // Center Point
5334 double latc = (latSW + latNE) / 2.0;
5335 double lonc = (lonSW + lonNE) / 2.0;
5336
5337 // Get scale in ppm (latitude)
5338 double ne_easting, ne_northing;
5339 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5340
5341 double sw_easting, sw_northing;
5342 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5343
5344 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5345
5346 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5347}
5348
5349bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5350 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5351 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5352}
5353
5354bool ChartCanvas::SetVPProjection(int projection) {
5355 if (!g_bopengl) // alternative projections require opengl
5356 return false;
5357
5358 // the view scale varies depending on geographic location and projection
5359 // rescale to keep the relative scale on the screen the same
5360 double prev_true_scale_ppm = m_true_scale_ppm;
5361 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5362 VPoint.skew, VPoint.rotation, projection) &&
5363 SetVPScale(wxMax(
5364 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5365 m_absolute_min_scale_ppm));
5366}
5367
5368bool ChartCanvas::SetViewPoint(double lat, double lon) {
5369 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5370 VPoint.rotation);
5371}
5372
5373bool ChartCanvas::SetVPRotation(double angle) {
5374 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5375 VPoint.skew, angle);
5376}
5377bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5378 double skew, double rotation, int projection,
5379 bool b_adjust, bool b_refresh) {
5380 bool b_ret = false;
5381 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5382 skew -= 2 * PI;
5383 // Any sensible change?
5384 if (VPoint.IsValid()) {
5385 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5386 (fabs(VPoint.skew - skew) < 1e-9) &&
5387 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5388 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5389 (VPoint.m_projection_type == projection ||
5390 projection == PROJECTION_UNKNOWN))
5391 return false;
5392 }
5393 if (VPoint.m_projection_type != projection)
5394 VPoint.InvalidateTransformCache(); // invalidate
5395
5396 // Take a local copy of the last viewport
5397 ViewPort last_vp = VPoint;
5398
5399 VPoint.skew = skew;
5400 VPoint.clat = lat;
5401 VPoint.clon = lon;
5402 VPoint.rotation = rotation;
5403 VPoint.view_scale_ppm = scale_ppm;
5404 if (projection != PROJECTION_UNKNOWN)
5405 VPoint.SetProjectionType(projection);
5406 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5407 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5408
5409 // don't allow latitude above 88 for mercator (90 is infinity)
5410 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5411 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5412 if (VPoint.clat > 89.5)
5413 VPoint.clat = 89.5;
5414 else if (VPoint.clat < -89.5)
5415 VPoint.clat = -89.5;
5416 }
5417
5418 // don't zoom out too far for transverse mercator polyconic until we resolve
5419 // issues
5420 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5421 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5422 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5423
5424 // SetVPRotation(rotation);
5425
5426 if (!g_bopengl) // tilt is not possible without opengl
5427 VPoint.tilt = 0;
5428
5429 if ((VPoint.pix_width <= 0) ||
5430 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5431 return false;
5432
5433 bool bwasValid = VPoint.IsValid();
5434 VPoint.Validate(); // Mark this ViewPoint as OK
5435
5436 // Has the Viewport scale changed? If so, invalidate the vp
5437 if (last_vp.view_scale_ppm != scale_ppm) {
5438 m_cache_vp.Invalidate();
5439 InvalidateGL();
5440 }
5441
5442 // A preliminary value, may be tweaked below
5443 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5444
5445 // recompute cursor position
5446 // and send to interested plugins if the mouse is actually in this window
5447 int mouseX = mouse_x;
5448 int mouseY = mouse_y;
5449 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5450 (mouseY < VPoint.pix_height)) {
5451 double lat, lon;
5452 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5453 m_cursor_lat = lat;
5454 m_cursor_lon = lon;
5455 SendCursorLatLonToAllPlugIns(lat, lon);
5456 }
5457
5458 if (!VPoint.b_quilt && m_singleChart) {
5459 VPoint.SetBoxes();
5460
5461 // Allow the chart to adjust the new ViewPort for performance optimization
5462 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5463 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5464
5465 // If there is a sensible change in the chart render, refresh the whole
5466 // screen
5467 if ((!m_cache_vp.IsValid()) ||
5468 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5469 Refresh(false);
5470 b_ret = true;
5471 } else {
5472 wxPoint cp_last, cp_this;
5473 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5474 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5475
5476 if (cp_last != cp_this) {
5477 Refresh(false);
5478 b_ret = true;
5479 }
5480 }
5481 // Create the stack
5482 if (m_pCurrentStack) {
5483 assert(ChartData != 0);
5484 int current_db_index;
5485 current_db_index =
5486 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5487
5488 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5489 m_groupIndex);
5490 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5491 }
5492
5493 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5494 }
5495
5496 // Handle the quilted case
5497 if (VPoint.b_quilt) {
5498 VPoint.SetBoxes();
5499
5500 if (last_vp.view_scale_ppm != scale_ppm)
5501 m_pQuilt->InvalidateAllQuiltPatchs();
5502
5503 // Create the quilt
5504 if (ChartData /*&& ChartData->IsValid()*/) {
5505 if (!m_pCurrentStack) return false;
5506
5507 int current_db_index;
5508 current_db_index =
5509 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5510
5511 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5512 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5513
5514 // Check to see if the current quilt reference chart is in the new stack
5515 int current_ref_stack_index = -1;
5516 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5517 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5518 current_ref_stack_index = i;
5519 }
5520
5521 if (g_bFullScreenQuilt) {
5522 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5523 }
5524
5525 // We might need a new Reference Chart
5526 bool b_needNewRef = false;
5527
5528 // If the new stack does not contain the current ref chart....
5529 if ((-1 == current_ref_stack_index) &&
5530 (m_pQuilt->GetRefChartdbIndex() >= 0))
5531 b_needNewRef = true;
5532
5533 // Would the current Ref Chart be excessively underzoomed?
5534 // We need to check this here to be sure, since we cannot know where the
5535 // reference chart was assigned. For instance, the reference chart may
5536 // have been selected from the config file, or from a long jump with a
5537 // chart family switch implicit. Anyway, we check to be sure....
5538 bool renderable = true;
5539 ChartBase *referenceChart =
5540 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5541 if (referenceChart) {
5542 double chartMaxScale = referenceChart->GetNormalScaleMax(
5543 GetCanvasScaleFactor(), GetCanvasWidth());
5544 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5545 }
5546 if (!renderable) b_needNewRef = true;
5547
5548 // Need new refchart?
5549 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5550 const ChartTableEntry &cte_ref =
5551 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5552 int target_scale = cte_ref.GetScale();
5553 int target_type = cte_ref.GetChartType();
5554 int candidate_stack_index;
5555
5556 // reset the ref chart in a way that does not lead to excessive
5557 // underzoom, for performance reasons Try to find a chart that is the
5558 // same type, and has a scale of just smaller than the current ref
5559 // chart
5560
5561 candidate_stack_index = 0;
5562 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5563 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5564 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5565 int candidate_scale = cte_candidate.GetScale();
5566 int candidate_type = cte_candidate.GetChartType();
5567
5568 if ((candidate_scale >= target_scale) &&
5569 (candidate_type == target_type)) {
5570 bool renderable = true;
5571 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5572 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5573 if (tentative_referenceChart) {
5574 double chartMaxScale =
5575 tentative_referenceChart->GetNormalScaleMax(
5576 GetCanvasScaleFactor(), GetCanvasWidth());
5577 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5578 }
5579
5580 if (renderable) break;
5581 }
5582
5583 candidate_stack_index++;
5584 }
5585
5586 // If that did not work, look for a chart of just larger scale and
5587 // same type
5588 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5589 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5590 while (candidate_stack_index >= 0) {
5591 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5592 if (idx >= 0) {
5593 const ChartTableEntry &cte_candidate =
5594 ChartData->GetChartTableEntry(idx);
5595 int candidate_scale = cte_candidate.GetScale();
5596 int candidate_type = cte_candidate.GetChartType();
5597
5598 if ((candidate_scale <= target_scale) &&
5599 (candidate_type == target_type))
5600 break;
5601 }
5602 candidate_stack_index--;
5603 }
5604 }
5605
5606 // and if that did not work, chose stack entry 0
5607 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5608 (candidate_stack_index < 0))
5609 candidate_stack_index = 0;
5610
5611 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5612
5613 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5614 }
5615
5616 if (!g_bopengl) {
5617 // Preset the VPoint projection type to match what the quilt projection
5618 // type will be
5619 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5620
5621 // Always keep the default Mercator projection if the reference chart is
5622 // not in the PatchList or the scale is too small for it to render.
5623
5624 bool renderable = true;
5625 ChartBase *referenceChart =
5626 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5627 if (referenceChart) {
5628 double chartMaxScale = referenceChart->GetNormalScaleMax(
5629 GetCanvasScaleFactor(), GetCanvasWidth());
5630 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5631 proj = ChartData->GetDBChartProj(ref_db_index);
5632 } else
5633 proj = PROJECTION_MERCATOR;
5634
5635 VPoint.b_MercatorProjectionOverride =
5636 (m_pQuilt->GetnCharts() == 0 || !renderable);
5637
5638 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5639
5640 VPoint.SetProjectionType(proj);
5641 }
5642
5643 // If this quilt will be a perceptible delta from the existing quilt,
5644 // then refresh the entire screen
5645 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5646 // Allow the quilt to adjust the new ViewPort for performance
5647 // optimization This will normally be only a fractional (i.e.
5648 // sub-pixel) adjustment...
5649 if (b_adjust) {
5650 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5651 }
5652
5653 // ChartData->ClearCacheInUseFlags();
5654 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5655
5656 // wxStopWatch sw;
5657
5658#ifdef __ANDROID__
5659 // This is an optimization for panning on touch screen systems.
5660 // The quilt composition is deferred until the OnPaint() message gets
5661 // finally removed and processed from the message queue.
5662 // Takes advantage of the fact that touch-screen pan gestures are
5663 // usually short in distance,
5664 // so not requiring a full quilt rebuild until the pan gesture is
5665 // complete.
5666 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5667 // qDebug() << "Force compose";
5668 m_pQuilt->Compose(VPoint);
5669 } else {
5670 m_pQuilt->Invalidate();
5671 }
5672#else
5673 m_pQuilt->Compose(VPoint);
5674#endif
5675
5676 // printf("comp time %ld\n", sw.Time());
5677
5678 // If the extended chart stack has changed, invalidate any cached
5679 // render bitmap
5680 // if(m_pQuilt->GetXStackHash() != hash1) {
5681 // m_bm_cache_vp.Invalidate();
5682 // InvalidateGL();
5683 // }
5684
5685 ChartData->PurgeCacheUnusedCharts(0.7);
5686
5687 if (b_refresh) Refresh(false);
5688
5689 b_ret = true;
5690 }
5691 }
5692
5693 VPoint.skew = 0.; // Quilting supports 0 Skew
5694 } else if (!g_bopengl) {
5695 OcpnProjType projection = PROJECTION_UNKNOWN;
5696 if (m_singleChart) // viewport projection must match chart projection
5697 // without opengl
5698 projection = m_singleChart->GetChartProjectionType();
5699 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5700 VPoint.SetProjectionType(projection);
5701 }
5702
5703 // Has the Viewport projection changed? If so, invalidate the vp
5704 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5705 m_cache_vp.Invalidate();
5706 InvalidateGL();
5707 }
5708
5709 UpdateCanvasControlBar(); // Refresh the Piano
5710
5711 VPoint.chart_scale = 1.0; // fallback default value
5712
5713 if (VPoint.GetBBox().GetValid()) {
5714 // Update the viewpoint reference scale
5715 if (m_singleChart)
5716 VPoint.ref_scale = m_singleChart->GetNativeScale();
5717 else {
5718#ifdef __ANDROID__
5719 // This is an optimization for panning on touch screen systems.
5720 // See above.
5721 // Quilt might not be fully composed at this point, so for cm93
5722 // the reference scale may not be known.
5723 // In this case, do not update the VP ref_scale.
5724 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5725 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5726 }
5727#else
5728 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5729#endif
5730 }
5731
5732 // Calculate the on-screen displayed actual scale
5733 // by a simple traverse northward from the center point
5734 // of roughly one eighth of the canvas height
5735 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5736
5737 double delta_check =
5738 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5739 delta_check /= 8.;
5740
5741 double check_point = wxMin(89., VPoint.clat);
5742
5743 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5744
5745 double rhumbDist;
5746 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5747 VPoint.clon, 0, &rhumbDist);
5748
5749 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5750 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5751 // Calculate the distance between r1 and r in physical pixels.
5752 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5753 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5754
5755 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5756
5757 // A fall back in case of very high zoom-out, giving delta_y == 0
5758 // which can probably only happen with vector charts
5759 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5760
5761 // Another fallback, for highly zoomed out charts
5762 // This adjustment makes the displayed TrueScale correspond to the
5763 // same algorithm used to calculate the chart zoom-out limit for
5764 // ChartDummy.
5765 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5766
5767 if (m_true_scale_ppm)
5768 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5769 else
5770 VPoint.chart_scale = 1.0;
5771
5772 // Create a nice renderable string
5773 double round_factor = 1000.;
5774 if (VPoint.chart_scale <= 1000.)
5775 round_factor = 10.;
5776 else if (VPoint.chart_scale <= 10000.)
5777 round_factor = 100.;
5778 else if (VPoint.chart_scale <= 100000.)
5779 round_factor = 1000.;
5780
5781 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5782 double retina_coef = 1;
5783#ifdef ocpnUSE_GL
5784#ifdef __WXOSX__
5785 if (g_bopengl) {
5786 retina_coef = GetContentScaleFactor();
5787 }
5788#endif
5789#endif
5790
5791 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5792 // rounded to the nearest 10, 100 or 1000.
5793 //
5794 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5795 // true_scale_display. That does not make sense. The chart scale should be
5796 // the same as the true scale within the limits of the rounding factor.
5797 double true_scale_display =
5798 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5799 wxString text;
5800
5801 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5802
5803 if (m_displayed_scale_factor > 10.0)
5804 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5805 m_displayed_scale_factor);
5806 else if (m_displayed_scale_factor > 1.0)
5807 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5808 m_displayed_scale_factor);
5809 else if (m_displayed_scale_factor > 0.1) {
5810 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5811 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5812 } else if (m_displayed_scale_factor > 0.01) {
5813 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5814 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5815 } else {
5816 text.Printf(
5817 "%s %4.0f (---)", _("Scale"),
5818 true_scale_display); // Generally, no chart, so no chart scale factor
5819 }
5820
5821 m_scaleValue = true_scale_display;
5822 m_scaleText = text;
5823 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5824
5825 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5826 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5827 // Check to see if the text will fit in the StatusBar field...
5828 bool b_noshow = false;
5829 {
5830 int w = 0;
5831 int h;
5832 wxClientDC dc(parent_frame->GetStatusBar());
5833 if (dc.IsOk()) {
5834 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5835 dc.SetFont(*templateFont);
5836 dc.GetTextExtent(text, &w, &h);
5837
5838 // If text is too long for the allocated field, try to reduce the text
5839 // string a bit.
5840 wxRect rect;
5841 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5842 if (w && w > rect.width) {
5843 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5844 }
5845
5846 // Test again...if too big still, then give it up.
5847 dc.GetTextExtent(text, &w, &h);
5848
5849 if (w && w > rect.width) {
5850 b_noshow = true;
5851 }
5852 }
5853 }
5854
5855 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5856 }
5857 }
5858
5859 // Maintain member vLat/vLon
5860 m_vLat = VPoint.clat;
5861 m_vLon = VPoint.clon;
5862
5863 return b_ret;
5864}
5865
5866// Static Icon definitions for some symbols requiring
5867// scaling/rotation/translation Very specific wxDC draw commands are
5868// necessary to properly render these icons...See the code in
5869// ShipDraw()
5870
5871// This icon was adapted and scaled from the S52 Presentation Library
5872// version 3_03.
5873// Symbol VECGND02
5874
5875static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5876
5877// This ownship icon was adapted and scaled from the S52 Presentation
5878// Library version 3_03 Symbol OWNSHP05
5879static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5880 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5881
5882wxColour ChartCanvas::PredColor() {
5883 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5884 // visibility.
5885 if (SHIP_NORMAL == m_ownship_state)
5886 return GetGlobalColor("URED");
5887
5888 else if (SHIP_LOWACCURACY == m_ownship_state)
5889 return GetGlobalColor("YELO1");
5890
5891 return GetGlobalColor("NODTA");
5892}
5893
5894wxColour ChartCanvas::ShipColor() {
5895 // Establish ship color
5896 // It changes color based on GPS and Chart accuracy/availability
5897
5898 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5899
5900 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5901
5902 return GetGlobalColor("URED"); // default is OK
5903}
5904
5905void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5906 wxPoint2DDouble lShipMidPoint) {
5907 dc.SetPen(wxPen(PredColor(), 2));
5908
5909 if (SHIP_NORMAL == m_ownship_state)
5910 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5911 else
5912 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5913
5914 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5915 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5916
5917 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5918 lShipMidPoint.m_y);
5919 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5920 lShipMidPoint.m_y + 12);
5921}
5922
5923void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5924 wxPoint GPSOffsetPixels,
5925 wxPoint2DDouble lGPSPoint) {
5926 // if (m_animationActive) return;
5927 // Develop a uniform length for course predictor line dash length, based on
5928 // physical display size Use this reference length to size all other graphics
5929 // elements
5930 float ref_dim = m_display_size_mm / 24;
5931 ref_dim = wxMin(ref_dim, 12);
5932 ref_dim = wxMax(ref_dim, 6);
5933
5934 wxColour cPred;
5935 cPred.Set(g_cog_predictor_color);
5936 if (cPred == wxNullColour) cPred = PredColor();
5937
5938 // Establish some graphic element line widths dependent on the platform
5939 // display resolution
5940 // double nominal_line_width_pix = wxMax(1.0,
5941 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5942 // not less than 1 pixel
5943 double nominal_line_width_pix = wxMax(
5944 1.0,
5945 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5946
5947 // If the calculated value is greater than the config file spec value, then
5948 // use it.
5949 if (nominal_line_width_pix > g_cog_predictor_width)
5950 g_cog_predictor_width = nominal_line_width_pix;
5951
5952 // Calculate ownship Position Predictor
5953 wxPoint lPredPoint, lHeadPoint;
5954
5955 float pCog = std::isnan(gCog) ? 0 : gCog;
5956 float pSog = std::isnan(gSog) ? 0 : gSog;
5957
5958 double pred_lat, pred_lon;
5959 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5960 &pred_lat, &pred_lon);
5961 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5962
5963 // test to catch the case where COG/HDG line crosses the screen
5964 LLBBox box;
5965
5966 // Should we draw the Head vector?
5967 // Compare the points lHeadPoint and lPredPoint
5968 // If they differ by more than n pixels, and the head vector is valid, then
5969 // render the head vector
5970
5971 float ndelta_pix = 10.;
5972 double hdg_pred_lat, hdg_pred_lon;
5973 bool b_render_hdt = false;
5974 if (!std::isnan(gHdt)) {
5975 // Calculate ownship Heading pointer as a predictor
5976 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5977 &hdg_pred_lon);
5978 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5979 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5980 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5981 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5982 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5983 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5984 }
5985 }
5986
5987 // draw course over ground if they are longer than the ship
5988 wxPoint lShipMidPoint;
5989 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5990 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5991 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5992 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5993
5994 if (lpp >= img_height / 2) {
5995 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5996 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5997 !std::isnan(gSog)) {
5998 // COG Predictor
5999 float dash_length = ref_dim;
6000 wxDash dash_long[2];
6001 dash_long[0] =
6002 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6003 g_cog_predictor_width); // Long dash , in mm <---------+
6004 dash_long[1] = dash_long[0] / 2.0; // Short gap
6005
6006 // On ultra-hi-res displays, do not allow the dashes to be greater than
6007 // 250, since it is defined as (char)
6008 if (dash_length > 250.) {
6009 dash_long[0] = 250. / g_cog_predictor_width;
6010 dash_long[1] = dash_long[0] / 2;
6011 }
6012
6013 wxPen ppPen2(cPred, g_cog_predictor_width,
6014 (wxPenStyle)g_cog_predictor_style);
6015 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6016 ppPen2.SetDashes(2, dash_long);
6017 dc.SetPen(ppPen2);
6018 dc.StrokeLine(
6019 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6020 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6021
6022 if (g_cog_predictor_width > 1) {
6023 float line_width = g_cog_predictor_width / 3.;
6024
6025 wxDash dash_long3[2];
6026 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6027 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6028
6029 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6030 (wxPenStyle)g_cog_predictor_style);
6031 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6032 ppPen3.SetDashes(2, dash_long3);
6033 dc.SetPen(ppPen3);
6034 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6035 lGPSPoint.m_y + GPSOffsetPixels.y,
6036 lPredPoint.x + GPSOffsetPixels.x,
6037 lPredPoint.y + GPSOffsetPixels.y);
6038 }
6039
6040 if (g_cog_predictor_endmarker) {
6041 // Prepare COG predictor endpoint icon
6042 double png_pred_icon_scale_factor = .4;
6043 if (g_ShipScaleFactorExp > 1.0)
6044 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6045 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6046
6047 wxPoint icon[4];
6048
6049 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6050 (float)(lPredPoint.x - lShipMidPoint.x));
6051 cog_rad += (float)PI;
6052
6053 for (int i = 0; i < 4; i++) {
6054 int j = i * 2;
6055 double pxa = (double)(s_png_pred_icon[j]);
6056 double pya = (double)(s_png_pred_icon[j + 1]);
6057
6058 pya *= png_pred_icon_scale_factor;
6059 pxa *= png_pred_icon_scale_factor;
6060
6061 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6062 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6063
6064 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6065 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6066 }
6067
6068 // Render COG endpoint icon
6069 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6070 wxPENSTYLE_SOLID);
6071 dc.SetPen(ppPen1);
6072 dc.SetBrush(wxBrush(cPred));
6073
6074 dc.StrokePolygon(4, icon);
6075 }
6076 }
6077 }
6078
6079 // HDT Predictor
6080 if (b_render_hdt) {
6081 float hdt_dash_length = ref_dim * 0.4;
6082
6083 cPred.Set(g_ownship_HDTpredictor_color);
6084 if (cPred == wxNullColour) cPred = PredColor();
6085 float hdt_width =
6086 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6087 : g_cog_predictor_width * 0.8);
6088 wxDash dash_short[2];
6089 dash_short[0] =
6090 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6091 hdt_width); // Short dash , in mm <---------+
6092 dash_short[1] =
6093 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6094 hdt_width); // Short gap |
6095
6096 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6097 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6098 ppPen2.SetDashes(2, dash_short);
6099
6100 dc.SetPen(ppPen2);
6101 dc.StrokeLine(
6102 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6103 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6104
6105 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6106 dc.SetPen(ppPen1);
6107 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6108
6109 if (g_ownship_HDTpredictor_endmarker) {
6110 double nominal_circle_size_pixels = wxMax(
6111 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6112
6113 // Scale the circle to ChartScaleFactor, slightly softened....
6114 if (g_ShipScaleFactorExp > 1.0)
6115 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6116
6117 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6118 lHeadPoint.y + GPSOffsetPixels.y,
6119 nominal_circle_size_pixels / 2);
6120 }
6121 }
6122
6123 // Draw radar rings if activated
6124 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6125 double factor = 1.00;
6126 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6127 factor = 1 / 1.852;
6128 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6129 if (std::isnan(gSog))
6130 factor = 0.0;
6131 else
6132 factor = gSog / 60;
6133 }
6134 factor *= g_fNavAidRadarRingsStep;
6135
6136 double tlat, tlon;
6137 wxPoint r;
6138 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6139 GetCanvasPointPix(tlat, tlon, &r);
6140
6141 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6142 pow((double)(lGPSPoint.m_y - r.y), 2));
6143 int pix_radius = (int)lpp;
6144
6145 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6146
6147 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6148
6149 dc.SetPen(ppPen1);
6150 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6151
6152 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6153 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6154 }
6155}
6156
6157void ChartCanvas::ComputeShipScaleFactor(
6158 float icon_hdt, int ownShipWidth, int ownShipLength,
6159 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6160 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6161 float screenResolution = m_pix_per_mm;
6162
6163 // Calculate the true ship length in exact pixels
6164 double ship_bow_lat, ship_bow_lon;
6165 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6166 &ship_bow_lat, &ship_bow_lon);
6167 wxPoint lShipBowPoint;
6168 wxPoint2DDouble b_point =
6169 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6170 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6171
6172 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6173 powf((float)(b_point.m_y - a_point.m_y), 2));
6174
6175 // And in mm
6176 float shipLength_mm = shipLength_px / screenResolution;
6177
6178 // Set minimum ownship drawing size
6179 float ownship_min_mm = g_n_ownship_min_mm;
6180 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6181
6182 // Calculate Nautical Miles distance from midships to gps antenna
6183 float hdt_ant = icon_hdt + 180.;
6184 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6185 float dx = g_n_gps_antenna_offset_x / 1852.;
6186 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6187 {
6188 hdt_ant = icon_hdt;
6189 dy = -dy;
6190 }
6191
6192 // If the drawn ship size is going to be clamped, adjust the gps antenna
6193 // offsets
6194 if (shipLength_mm < ownship_min_mm) {
6195 dy /= shipLength_mm / ownship_min_mm;
6196 dx /= shipLength_mm / ownship_min_mm;
6197 }
6198
6199 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6200
6201 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6202 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6203 &ship_mid_lon1);
6204
6205 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6206 &lShipMidPoint);
6207
6208 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6209 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6210
6211 float scale_factor = shipLength_px / ownShipLength;
6212
6213 // Calculate a scale factor that would produce a reasonably sized icon
6214 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6215
6216 // And choose the correct one
6217 scale_factor = wxMax(scale_factor, scale_factor_min);
6218
6219 scale_factor_y = scale_factor;
6220 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6221 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6222}
6223
6224void ChartCanvas::ShipDraw(ocpnDC &dc) {
6225 if (!GetVP().IsValid()) return;
6226
6227 wxPoint GPSOffsetPixels(0, 0);
6228 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6229
6230 // COG/SOG may be undefined in NMEA data stream
6231 float pCog = std::isnan(gCog) ? 0 : gCog;
6232 float pSog = std::isnan(gSog) ? 0 : gSog;
6233
6234 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6235
6236 lShipMidPoint = lGPSPoint;
6237
6238 // Draw the icon rotated to the COG
6239 // or to the Hdt if available
6240 float icon_hdt = pCog;
6241 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6242
6243 // COG may be undefined in NMEA data stream
6244 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6245
6246 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6247 // predictor
6248 double osd_head_lat, osd_head_lon;
6249 wxPoint osd_head_point;
6250
6251 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6252 &osd_head_lon);
6253
6254 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6255
6256 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6257 (float)(osd_head_point.x - lShipMidPoint.m_x));
6258 icon_rad += (float)PI;
6259
6260 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6261
6262 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6263 // nominal size and is just barely outside the viewport ....
6264 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6265
6266 // TODO: fix to include actual size of boat that will be rendered
6267 int img_height = 0;
6268 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6269 if (GetVP().chart_scale >
6270 300000) // According to S52, this should be 50,000
6271 {
6272 ShipDrawLargeScale(dc, lShipMidPoint);
6273 img_height = 20;
6274 } else {
6275 wxImage pos_image;
6276
6277 // Substitute user ownship image if found
6278 if (m_pos_image_user)
6279 pos_image = m_pos_image_user->Copy();
6280 else if (SHIP_NORMAL == m_ownship_state)
6281 pos_image = m_pos_image_red->Copy();
6282 if (SHIP_LOWACCURACY == m_ownship_state)
6283 pos_image = m_pos_image_yellow->Copy();
6284 else if (SHIP_NORMAL != m_ownship_state)
6285 pos_image = m_pos_image_grey->Copy();
6286
6287 // Substitute user ownship image if found
6288 if (m_pos_image_user) {
6289 pos_image = m_pos_image_user->Copy();
6290
6291 if (SHIP_LOWACCURACY == m_ownship_state)
6292 pos_image = m_pos_image_user_yellow->Copy();
6293 else if (SHIP_NORMAL != m_ownship_state)
6294 pos_image = m_pos_image_user_grey->Copy();
6295 }
6296
6297 img_height = pos_image.GetHeight();
6298
6299 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6300 g_OwnShipIconType > 0) // use large ship
6301 {
6302 int ownShipWidth = 22; // Default values from s_ownship_icon
6303 int ownShipLength = 84;
6304 if (g_OwnShipIconType == 1) {
6305 ownShipWidth = pos_image.GetWidth();
6306 ownShipLength = pos_image.GetHeight();
6307 }
6308
6309 float scale_factor_x, scale_factor_y;
6310 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6311 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6312 scale_factor_x, scale_factor_y);
6313
6314 if (g_OwnShipIconType == 1) { // Scaled bitmap
6315 pos_image.Rescale(ownShipWidth * scale_factor_x,
6316 ownShipLength * scale_factor_y,
6317 wxIMAGE_QUALITY_HIGH);
6318 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6319 wxImage rot_image =
6320 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6321
6322 // Simple sharpening algorithm.....
6323 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6324 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6325 if (rot_image.GetAlpha(ip, jp) > 64)
6326 rot_image.SetAlpha(ip, jp, 255);
6327
6328 wxBitmap os_bm(rot_image);
6329
6330 int w = os_bm.GetWidth();
6331 int h = os_bm.GetHeight();
6332 img_height = h;
6333
6334 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6335 lShipMidPoint.m_y - h / 2, true);
6336
6337 // Maintain dirty box,, missing in __WXMSW__ library
6338 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6339 lShipMidPoint.m_y - h / 2);
6340 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6341 lShipMidPoint.m_y - h / 2 + h);
6342 }
6343
6344 else if (g_OwnShipIconType == 2) { // Scaled Vector
6345 wxPoint ownship_icon[10];
6346
6347 for (int i = 0; i < 10; i++) {
6348 int j = i * 2;
6349 float pxa = (float)(s_ownship_icon[j]);
6350 float pya = (float)(s_ownship_icon[j + 1]);
6351 pya *= scale_factor_y;
6352 pxa *= scale_factor_x;
6353
6354 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6355 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6356
6357 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6358 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6359 }
6360
6361 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6362 dc.SetPen(ppPen1);
6363 dc.SetBrush(wxBrush(ShipColor()));
6364
6365 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6366
6367 // draw reference point (midships) cross
6368 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6369 ownship_icon[7].y);
6370 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6371 ownship_icon[9].y);
6372 }
6373
6374 img_height = ownShipLength * scale_factor_y;
6375
6376 // Reference point, where the GPS antenna is
6377 int circle_rad = 3;
6378 if (m_pos_image_user) circle_rad = 1;
6379
6380 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6381 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6382 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6383 } else { // Fixed bitmap icon.
6384 /* non opengl, or suboptimal opengl via ocpndc: */
6385 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6386 wxImage rot_image =
6387 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6388
6389 // Simple sharpening algorithm.....
6390 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6391 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6392 if (rot_image.GetAlpha(ip, jp) > 64)
6393 rot_image.SetAlpha(ip, jp, 255);
6394
6395 wxBitmap os_bm(rot_image);
6396
6397 if (g_ShipScaleFactorExp > 1) {
6398 wxImage scaled_image = os_bm.ConvertToImage();
6399 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6400 1.0; // soften the scale factor a bit
6401 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6402 scaled_image.GetHeight() * factor,
6403 wxIMAGE_QUALITY_HIGH));
6404 }
6405 int w = os_bm.GetWidth();
6406 int h = os_bm.GetHeight();
6407 img_height = h;
6408
6409 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6410 lShipMidPoint.m_y - h / 2, true);
6411
6412 // Reference point, where the GPS antenna is
6413 int circle_rad = 3;
6414 if (m_pos_image_user) circle_rad = 1;
6415
6416 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6417 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6418 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6419
6420 // Maintain dirty box,, missing in __WXMSW__ library
6421 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6422 lShipMidPoint.m_y - h / 2);
6423 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6424 lShipMidPoint.m_y - h / 2 + h);
6425 }
6426 } // ownship draw
6427 }
6428
6429 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6430}
6431
6432/* @ChartCanvas::CalcGridSpacing
6433 **
6434 ** Calculate the major and minor spacing between the lat/lon grid
6435 **
6436 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6437 *window
6438 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6439 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6440 ** @return [void]
6441 */
6442void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6443 float &MinorSpacing) {
6444 // table for calculating the distance between the grids
6445 // [0] view_scale ppm
6446 // [1] spacing between major grid lines in degrees
6447 // [2] spacing between minor grid lines in degrees
6448 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6449 {.000001f, 45.0f, 15.0f},
6450 {.0002f, 30.0f, 10.0f},
6451 {.0003f, 10.0f, 2.0f},
6452 {.0008f, 5.0f, 1.0f},
6453 {.001f, 2.0f, 30.0f / 60.0f},
6454 {.003f, 1.0f, 20.0f / 60.0f},
6455 {.006f, 0.5f, 10.0f / 60.0f},
6456 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6457 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6458 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6459 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6460 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6461 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6462 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6463 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6464
6465 unsigned int tabi;
6466 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6467 if (view_scale_ppm < lltab[tabi][0]) break;
6468 MajorSpacing = lltab[tabi][1]; // major latitude distance
6469 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6470 return;
6471}
6472/* @ChartCanvas::CalcGridText *************************************
6473 **
6474 ** Calculates text to display at the major grid lines
6475 **
6476 ** @param [r] latlon [float] latitude or longitude of grid line
6477 ** @param [r] spacing [float] distance between two major grid lines
6478 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6479 **
6480 ** @return
6481 */
6482
6483wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6484 int deg = (int)fabs(latlon); // degrees
6485 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6486 char postfix;
6487
6488 // calculate postfix letter (NSEW)
6489 if (latlon > 0.0) {
6490 if (bPostfix) {
6491 postfix = 'N';
6492 } else {
6493 postfix = 'E';
6494 }
6495 } else if (latlon < 0.0) {
6496 if (bPostfix) {
6497 postfix = 'S';
6498 } else {
6499 postfix = 'W';
6500 }
6501 } else {
6502 postfix = ' '; // no postfix for equator and greenwich
6503 }
6504 // calculate text, display minutes only if spacing is smaller than one degree
6505
6506 wxString ret;
6507 if (spacing >= 1.0) {
6508 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6509 } else if (spacing >= (1.0 / 60.0)) {
6510 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6511 } else {
6512 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6513 }
6514
6515 return ret;
6516}
6517
6518/* @ChartCanvas::GridDraw *****************************************
6519 **
6520 ** Draws major and minor Lat/Lon Grid on the chart
6521 ** - distance between Grid-lm ines are calculated automatic
6522 ** - major grid lines will be across the whole chart window
6523 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6524 **
6525 ** @param [w] dc [wxDC&] the wx drawing context
6526 **
6527 ** @return [void]
6528 ************************************************************************/
6529void ChartCanvas::GridDraw(ocpnDC &dc) {
6530 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6531
6532 double nlat, elon, slat, wlon;
6533 float lat, lon;
6534 float dlon;
6535 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6536 wxCoord w, h;
6537 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6538 dc.SetPen(GridPen);
6539 if (!m_pgridFont) SetupGridFont();
6540 dc.SetFont(*m_pgridFont);
6541 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6542
6543 w = m_canvas_width;
6544 h = m_canvas_height;
6545
6546 GetCanvasPixPoint(0, 0, nlat,
6547 wlon); // get lat/lon of upper left point of the window
6548 GetCanvasPixPoint(w, h, slat,
6549 elon); // get lat/lon of lower right point of the window
6550 dlon =
6551 elon -
6552 wlon; // calculate how many degrees of longitude are shown in the window
6553 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6554 {
6555 dlon = dlon + 360.0;
6556 }
6557 // calculate distance between latitude grid lines
6558 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6559
6560 // calculate position of first major latitude grid line
6561 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6562
6563 // Draw Major latitude grid lines and text
6564 while (lat < nlat) {
6565 wxPoint r;
6566 wxString st =
6567 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6568 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6569 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6570 dc.DrawText(st, 0, r.y); // draw text
6571 lat = lat + gridlatMajor;
6572
6573 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6574 }
6575
6576 // calculate position of first minor latitude grid line
6577 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6578
6579 // Draw minor latitude grid lines
6580 while (lat < nlat) {
6581 wxPoint r;
6582 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6583 dc.DrawLine(0, r.y, 10, r.y, false);
6584 dc.DrawLine(w - 10, r.y, w, r.y, false);
6585 lat = lat + gridlatMinor;
6586 }
6587
6588 // calculate distance between grid lines
6589 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6590
6591 // calculate position of first major latitude grid line
6592 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6593
6594 // draw major longitude grid lines
6595 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6596 wxPoint r;
6597 wxString st = CalcGridText(lon, gridlonMajor, false);
6598 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6599 dc.DrawLine(r.x, 0, r.x, h, false);
6600 dc.DrawText(st, r.x, 0);
6601 lon = lon + gridlonMajor;
6602 if (lon > 180.0) {
6603 lon = lon - 360.0;
6604 }
6605
6606 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6607 }
6608
6609 // calculate position of first minor longitude grid line
6610 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6611 // draw minor longitude grid lines
6612 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6613 wxPoint r;
6614 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6615 dc.DrawLine(r.x, 0, r.x, 10, false);
6616 dc.DrawLine(r.x, h - 10, r.x, h, false);
6617 lon = lon + gridlonMinor;
6618 if (lon > 180.0) {
6619 lon = lon - 360.0;
6620 }
6621 }
6622}
6623
6624void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6625 if (0 ) {
6626 double blat, blon, tlat, tlon;
6627 wxPoint r;
6628
6629 int x_origin = m_bDisplayGrid ? 60 : 20;
6630 int y_origin = m_canvas_height - 50;
6631
6632 float dist;
6633 int count;
6634 wxPen pen1, pen2;
6635
6636 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6637 {
6638 dist = 10.0;
6639 count = 5;
6640 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6641 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6642 } else // Draw 1 mile scale as SCALEB10
6643 {
6644 dist = 1.0;
6645 count = 10;
6646 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6647 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6648 }
6649
6650 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6651 double rotation = -VPoint.rotation;
6652
6653 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6654 GetCanvasPointPix(tlat, tlon, &r);
6655 int l1 = (y_origin - r.y) / count;
6656
6657 for (int i = 0; i < count; i++) {
6658 int y = l1 * i;
6659 if (i & 1)
6660 dc.SetPen(pen1);
6661 else
6662 dc.SetPen(pen2);
6663
6664 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6665 }
6666 } else {
6667 double blat, blon, tlat, tlon;
6668
6669 int x_origin = 5.0 * GetPixPerMM();
6670 int chartbar_height = GetChartbarHeight();
6671 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6672 // if (style->chartStatusWindowTransparent)
6673 // chartbar_height = 0;
6674 int y_origin = m_canvas_height - chartbar_height - 5;
6675#ifdef __WXOSX__
6676 if (!g_bopengl)
6677 y_origin =
6678 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6679#endif
6680
6681 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6682 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6683
6684 double d;
6685 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6686 d /= 2;
6687
6688 int unit = g_iDistanceFormat;
6689 if (d < .5 &&
6690 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6691 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6692
6693 // nice number
6694 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6695 float places = floor(logdist), rem = logdist - places;
6696 dist = pow(10, places);
6697
6698 if (rem < .2)
6699 dist /= 5;
6700 else if (rem < .5)
6701 dist /= 2;
6702
6703 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6704 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6705 double rotation = -VPoint.rotation;
6706
6707 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6708 &tlat, &tlon);
6709 wxPoint r;
6710 GetCanvasPointPix(tlat, tlon, &r);
6711 int l1 = r.x - x_origin;
6712
6713 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6714 12); // Store this for later reference
6715
6716 dc.SetPen(pen1);
6717
6718 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6719 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6720 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6721
6722 if (!m_pgridFont) SetupGridFont();
6723 dc.SetFont(*m_pgridFont);
6724 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6725 int w, h;
6726 dc.GetTextExtent(s, &w, &h);
6727 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6728 if (g_bopengl) {
6729 w /= dpi_factor;
6730 h /= dpi_factor;
6731 }
6732 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6733 }
6734}
6735
6736void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6737 // Constants?
6738 double da_min = 2.;
6739 double da_max = 6.;
6740 double ra_min = 0.;
6741 double ra_max = 40.;
6742
6743 wxPen pen_save = dc.GetPen();
6744
6745 wxDateTime now = wxDateTime::Now();
6746
6747 dc.SetPen(pen);
6748
6749 int x0, y0, x1, y1;
6750
6751 x0 = x1 = x + radius; // Start point
6752 y0 = y1 = y;
6753 double angle = 0.;
6754 int i = 0;
6755
6756 while (angle < 360.) {
6757 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6758 angle += da;
6759
6760 if (angle > 360.) angle = 360.;
6761
6762 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6763
6764 double r;
6765 if (i & 1)
6766 r = radius + ra;
6767 else
6768 r = radius - ra;
6769
6770 x1 = (int)(x + cos(angle * PI / 180.) * r);
6771 y1 = (int)(y + sin(angle * PI / 180.) * r);
6772
6773 dc.DrawLine(x0, y0, x1, y1);
6774
6775 x0 = x1;
6776 y0 = y1;
6777
6778 i++;
6779 }
6780
6781 dc.DrawLine(x + radius, y, x1, y1); // closure
6782
6783 dc.SetPen(pen_save);
6784}
6785
6786static bool bAnchorSoundPlaying = false;
6787
6788static void onAnchorSoundFinished(void *ptr) {
6789 o_sound::g_anchorwatch_sound->UnLoad();
6790 bAnchorSoundPlaying = false;
6791}
6792
6793void ChartCanvas::AlertDraw(ocpnDC &dc) {
6794 using namespace o_sound;
6795 // Visual and audio alert for anchorwatch goes here
6796 bool play_sound = false;
6797 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6798 if (AnchorAlertOn1) {
6799 wxPoint TargetPoint;
6801 &TargetPoint);
6802 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6803 TargetPoint.y, 100);
6804 play_sound = true;
6805 }
6806 } else
6807 AnchorAlertOn1 = false;
6808
6809 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6810 if (AnchorAlertOn2) {
6811 wxPoint TargetPoint;
6813 &TargetPoint);
6814 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6815 TargetPoint.y, 100);
6816 play_sound = true;
6817 }
6818 } else
6819 AnchorAlertOn2 = false;
6820
6821 if (play_sound) {
6822 if (!bAnchorSoundPlaying) {
6823 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6824 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6825 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6826 if (g_anchorwatch_sound->IsOk()) {
6827 bAnchorSoundPlaying = true;
6828 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6829 g_anchorwatch_sound->Play();
6830 }
6831 }
6832 }
6833}
6834
6835void ChartCanvas::UpdateShips() {
6836 // Get the rectangle in the current dc which bounds the "ownship" symbol
6837
6838 wxClientDC dc(this);
6839 if (!dc.IsOk()) return;
6840
6841 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6842 if (!test_bitmap.IsOk()) return;
6843
6844 wxMemoryDC temp_dc(test_bitmap);
6845
6846 temp_dc.ResetBoundingBox();
6847 temp_dc.DestroyClippingRegion();
6848 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6849
6850 // Draw the ownship on the temp_dc
6851 ocpnDC ocpndc = ocpnDC(temp_dc);
6852 ShipDraw(ocpndc);
6853
6854 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6855 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6856 if (p) {
6857 wxPoint px;
6858 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6859 ocpndc.CalcBoundingBox(px.x, px.y);
6860 }
6861 }
6862
6863 ship_draw_rect =
6864 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6865 temp_dc.MaxY() - temp_dc.MinY());
6866
6867 wxRect own_ship_update_rect = ship_draw_rect;
6868
6869 if (!own_ship_update_rect.IsEmpty()) {
6870 // The required invalidate rectangle is the union of the last drawn
6871 // rectangle and this drawn rectangle
6872 own_ship_update_rect.Union(ship_draw_last_rect);
6873 own_ship_update_rect.Inflate(2);
6874 }
6875
6876 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6877
6878 ship_draw_last_rect = ship_draw_rect;
6879
6880 temp_dc.SelectObject(wxNullBitmap);
6881}
6882
6883void ChartCanvas::UpdateAlerts() {
6884 // Get the rectangle in the current dc which bounds the detected Alert
6885 // targets
6886
6887 // Use this dc
6888 wxClientDC dc(this);
6889
6890 // Get dc boundary
6891 int sx, sy;
6892 dc.GetSize(&sx, &sy);
6893
6894 // Need a bitmap
6895 wxBitmap test_bitmap(sx, sy, -1);
6896
6897 // Create a memory DC
6898 wxMemoryDC temp_dc;
6899 temp_dc.SelectObject(test_bitmap);
6900
6901 temp_dc.ResetBoundingBox();
6902 temp_dc.DestroyClippingRegion();
6903 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6904
6905 // Draw the Alert Targets on the temp_dc
6906 ocpnDC ocpndc = ocpnDC(temp_dc);
6907 AlertDraw(ocpndc);
6908
6909 // Retrieve the drawing extents
6910 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6911 temp_dc.MaxX() - temp_dc.MinX(),
6912 temp_dc.MaxY() - temp_dc.MinY());
6913
6914 if (!alert_rect.IsEmpty())
6915 alert_rect.Inflate(2); // clear all drawing artifacts
6916
6917 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6918 // The required invalidate rectangle is the union of the last drawn
6919 // rectangle and this drawn rectangle
6920 wxRect alert_update_rect = alert_draw_rect;
6921 alert_update_rect.Union(alert_rect);
6922
6923 // Invalidate the rectangular region
6924 RefreshRect(alert_update_rect, false);
6925 }
6926
6927 // Save this rectangle for next time
6928 alert_draw_rect = alert_rect;
6929
6930 temp_dc.SelectObject(wxNullBitmap); // clean up
6931}
6932
6933void ChartCanvas::UpdateAIS() {
6934 if (!g_pAIS) return;
6935
6936 // Get the rectangle in the current dc which bounds the detected AIS targets
6937
6938 // Use this dc
6939 wxClientDC dc(this);
6940
6941 // Get dc boundary
6942 int sx, sy;
6943 dc.GetSize(&sx, &sy);
6944
6945 wxRect ais_rect;
6946
6947 // How many targets are there?
6948
6949 // If more than "some number", it will be cheaper to refresh the entire
6950 // screen than to build update rectangles for each target.
6951 if (g_pAIS->GetTargetList().size() > 10) {
6952 ais_rect = wxRect(0, 0, sx, sy); // full screen
6953 } else {
6954 // Need a bitmap
6955 wxBitmap test_bitmap(sx, sy, -1);
6956
6957 // Create a memory DC
6958 wxMemoryDC temp_dc;
6959 temp_dc.SelectObject(test_bitmap);
6960
6961 temp_dc.ResetBoundingBox();
6962 temp_dc.DestroyClippingRegion();
6963 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6964
6965 // Draw the AIS Targets on the temp_dc
6966 ocpnDC ocpndc = ocpnDC(temp_dc);
6967 AISDraw(ocpndc, GetVP(), this);
6968 AISDrawAreaNotices(ocpndc, GetVP(), this);
6969
6970 // Retrieve the drawing extents
6971 ais_rect =
6972 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6973 temp_dc.MaxY() - temp_dc.MinY());
6974
6975 if (!ais_rect.IsEmpty())
6976 ais_rect.Inflate(2); // clear all drawing artifacts
6977
6978 temp_dc.SelectObject(wxNullBitmap); // clean up
6979 }
6980
6981 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6982 // The required invalidate rectangle is the union of the last drawn
6983 // rectangle and this drawn rectangle
6984 wxRect ais_update_rect = ais_draw_rect;
6985 ais_update_rect.Union(ais_rect);
6986
6987 // Invalidate the rectangular region
6988 RefreshRect(ais_update_rect, false);
6989 }
6990
6991 // Save this rectangle for next time
6992 ais_draw_rect = ais_rect;
6993}
6994
6995void ChartCanvas::ToggleCPAWarn() {
6996 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6997 wxString mess;
6998 if (g_bCPAWarn) {
6999 g_bTCPA_Max = true;
7000 mess = _("ON");
7001 } else {
7002 g_bTCPA_Max = false;
7003 mess = _("OFF");
7004 }
7005 // Print to status bar if available.
7006 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7007 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7008 } else {
7009 if (!g_AisFirstTimeUse) {
7010 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7011 _("CPA") + " " + mess, 4, 4);
7012 }
7013 }
7014}
7015
7016void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7017
7018void ChartCanvas::OnSize(wxSizeEvent &event) {
7019 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7020 // GetClientSize returns the size of the canvas area in logical pixels.
7021 GetClientSize(&m_canvas_width, &m_canvas_height);
7022
7023#ifdef __WXOSX__
7024 // Support scaled HDPI displays.
7025 m_displayScale = GetContentScaleFactor();
7026#endif
7027
7028 // Convert to physical pixels.
7029 m_canvas_width *= m_displayScale;
7030 m_canvas_height *= m_displayScale;
7031
7032 // Resize the current viewport
7033 VPoint.pix_width = m_canvas_width;
7034 VPoint.pix_height = m_canvas_height;
7035 VPoint.SetPixelScale(m_displayScale);
7036
7037 // Get some canvas metrics
7038
7039 // Rescale to current value, in order to rebuild VPoint data
7040 // structures for new canvas size
7042
7043 m_absolute_min_scale_ppm =
7044 m_canvas_width /
7045 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7046
7047 // Inform the parent Frame that I am being resized...
7048 gFrame->ProcessCanvasResize();
7049
7050 // if MUIBar is active, size the bar
7051 // if(g_useMUI && !m_muiBar){ // rebuild if
7052 // necessary
7053 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7054 // m_muiBarHOSize = m_muiBar->GetSize();
7055 // }
7056
7057 if (m_muiBar) {
7058 SetMUIBarPosition();
7059 UpdateFollowButtonState();
7060 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7061 }
7062
7063 // Set up the scroll margins
7064 xr_margin = m_canvas_width * 95 / 100;
7065 xl_margin = m_canvas_width * 5 / 100;
7066 yt_margin = m_canvas_height * 5 / 100;
7067 yb_margin = m_canvas_height * 95 / 100;
7068
7069 if (m_pQuilt)
7070 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7071
7072 // Resize the scratch BM
7073 delete pscratch_bm;
7074 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7075 m_brepaint_piano = true;
7076
7077 // Resize the Route Calculation BM
7078 m_dc_route.SelectObject(wxNullBitmap);
7079 delete proute_bm;
7080 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7081 m_dc_route.SelectObject(*proute_bm);
7082
7083 // Resize the saved Bitmap
7084 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7085
7086 // Resize the working Bitmap
7087 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7088
7089 // Rescale again, to capture all the changes for new canvas size
7091
7092#ifdef ocpnUSE_GL
7093 if (/*g_bopengl &&*/ m_glcc) {
7094 // FIXME (dave) This can go away?
7095 m_glcc->OnSize(event);
7096 }
7097#endif
7098
7099 FormatPianoKeys();
7100 // Invalidate the whole window
7101 ReloadVP();
7102}
7103
7104void ChartCanvas::ProcessNewGUIScale() {
7105 // m_muiBar->Hide();
7106 delete m_muiBar;
7107 m_muiBar = 0;
7108
7109 CreateMUIBar();
7110}
7111
7112void ChartCanvas::CreateMUIBar() {
7113 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7114 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7115 m_muiBar->SetColorScheme(m_cs);
7116 m_muiBarHOSize = m_muiBar->m_size;
7117 }
7118
7119 if (m_muiBar) {
7120 // We need to update the m_bENCGroup flag, not least for the initial
7121 // creation of a MUIBar
7122 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7123
7124 SetMUIBarPosition();
7125 UpdateFollowButtonState();
7126 m_muiBar->UpdateDynamicValues();
7127 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7128 }
7129}
7130
7131void ChartCanvas::SetMUIBarPosition() {
7132 // if MUIBar is active, size the bar
7133 if (m_muiBar) {
7134 // We estimate the piano width based on the canvas width
7135 int pianoWidth = GetClientSize().x * 0.6f;
7136 // If the piano already exists, we can use its exact width
7137 // if(m_Piano)
7138 // pianoWidth = m_Piano->GetWidth();
7139
7140 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7141 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7142 delete m_muiBar;
7143 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7144 m_muiBar->SetColorScheme(m_cs);
7145 }
7146 }
7147
7148 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7149 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7150 delete m_muiBar;
7151 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7152 m_muiBar->SetColorScheme(m_cs);
7153 }
7154 }
7155
7156 m_muiBar->SetBestPosition();
7157 }
7158}
7159
7160void ChartCanvas::DestroyMuiBar() {
7161 if (m_muiBar) {
7162 delete m_muiBar;
7163 m_muiBar = NULL;
7164 }
7165}
7166
7167void ChartCanvas::ShowCompositeInfoWindow(
7168 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7169 if (n_charts > 0) {
7170 if (NULL == m_pCIWin) {
7171 m_pCIWin = new ChInfoWin(this);
7172 m_pCIWin->Hide();
7173 }
7174
7175 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7176 wxString s;
7177
7178 s = _("Composite of ");
7179
7180 wxString s1;
7181 s1.Printf("%d ", n_charts);
7182 if (n_charts > 1)
7183 s1 += _("charts");
7184 else
7185 s1 += _("chart");
7186 s += s1;
7187 s += '\n';
7188
7189 s1.Printf(_("Chart scale"));
7190 s1 += ": ";
7191 wxString s2;
7192 s2.Printf("1:%d\n", scale);
7193 s += s1;
7194 s += s2;
7195
7196 s1 = _("Zoom in for more information");
7197 s += s1;
7198 s += '\n';
7199
7200 int char_width = s1.Length();
7201 int char_height = 3;
7202
7203 if (g_bChartBarEx) {
7204 s += '\n';
7205 int j = 0;
7206 for (int i : index_vector) {
7207 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7208 wxString path = cte.GetFullSystemPath();
7209 s += path;
7210 s += '\n';
7211 char_height++;
7212 char_width = wxMax(char_width, path.Length());
7213 if (j++ >= 9) break;
7214 }
7215 if (j >= 9) {
7216 s += " .\n .\n .\n";
7217 char_height += 3;
7218 }
7219 s += '\n';
7220 char_height += 1;
7221
7222 char_width += 4; // Fluff
7223 }
7224
7225 m_pCIWin->SetString(s);
7226
7227 m_pCIWin->FitToChars(char_width, char_height);
7228
7229 wxPoint p;
7230 p.x = x / GetContentScaleFactor();
7231 if ((p.x + m_pCIWin->GetWinSize().x) >
7232 (m_canvas_width / GetContentScaleFactor()))
7233 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7234 m_pCIWin->GetWinSize().x) /
7235 2; // centered
7236
7237 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7238 4 - m_pCIWin->GetWinSize().y;
7239
7240 m_pCIWin->dbIndex = 0;
7241 m_pCIWin->chart_scale = 0;
7242 m_pCIWin->SetPosition(p);
7243 m_pCIWin->SetBitmap();
7244 m_pCIWin->Refresh();
7245 m_pCIWin->Show();
7246 }
7247 } else {
7248 HideChartInfoWindow();
7249 }
7250}
7251
7252void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7253 if (dbIndex >= 0) {
7254 if (NULL == m_pCIWin) {
7255 m_pCIWin = new ChInfoWin(this);
7256 m_pCIWin->Hide();
7257 }
7258
7259 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7260 wxString s;
7261 ChartBase *pc = NULL;
7262
7263 // TOCTOU race but worst case will reload chart.
7264 // need to lock it or the background spooler may evict charts in
7265 // OpenChartFromDBAndLock
7266 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7267 pc = ChartData->OpenChartFromDBAndLock(
7268 dbIndex, FULL_INIT); // this must come from cache
7269
7270 int char_width, char_height;
7271 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7272 if (pc) ChartData->UnLockCacheChart(dbIndex);
7273
7274 m_pCIWin->SetString(s);
7275 m_pCIWin->FitToChars(char_width, char_height);
7276
7277 wxPoint p;
7278 p.x = x / GetContentScaleFactor();
7279 if ((p.x + m_pCIWin->GetWinSize().x) >
7280 (m_canvas_width / GetContentScaleFactor()))
7281 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7282 m_pCIWin->GetWinSize().x) /
7283 2; // centered
7284
7285 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7286 4 - m_pCIWin->GetWinSize().y;
7287
7288 m_pCIWin->dbIndex = dbIndex;
7289 m_pCIWin->SetPosition(p);
7290 m_pCIWin->SetBitmap();
7291 m_pCIWin->Refresh();
7292 m_pCIWin->Show();
7293 }
7294 } else {
7295 HideChartInfoWindow();
7296 }
7297}
7298
7299void ChartCanvas::HideChartInfoWindow() {
7300 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7301 m_pCIWin->Hide();
7302 m_pCIWin->Destroy();
7303 m_pCIWin = NULL;
7304
7305#ifdef __ANDROID__
7306 androidForceFullRepaint();
7307#endif
7308 }
7309}
7310
7311void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7312 wxMouseEvent ev(wxEVT_MOTION);
7313 ev.m_x = mouse_x;
7314 ev.m_y = mouse_y;
7315 ev.m_leftDown = mouse_leftisdown;
7316
7317 wxEvtHandler *evthp = GetEventHandler();
7318
7319 ::wxPostEvent(evthp, ev);
7320}
7321
7322void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7323 if ((m_panx_target_final - m_panx_target_now) ||
7324 (m_pany_target_final - m_pany_target_now)) {
7325 DoTimedMovementTarget();
7326 } else
7327 DoTimedMovement();
7328}
7329
7330void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7331
7332bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7333 int delta) {
7334 if (m_disable_edge_pan) return false;
7335
7336 bool bft = false;
7337 int pan_margin = m_canvas_width * margin / 100;
7338 int pan_timer_set = 200;
7339 double pan_delta = GetVP().pix_width * delta / 100;
7340 int pan_x = 0;
7341 int pan_y = 0;
7342
7343 if (x > m_canvas_width - pan_margin) {
7344 bft = true;
7345 pan_x = pan_delta;
7346 }
7347
7348 else if (x < pan_margin) {
7349 bft = true;
7350 pan_x = -pan_delta;
7351 }
7352
7353 if (y < pan_margin) {
7354 bft = true;
7355 pan_y = -pan_delta;
7356 }
7357
7358 else if (y > m_canvas_height - pan_margin) {
7359 bft = true;
7360 pan_y = pan_delta;
7361 }
7362
7363 // Of course, if dragging, and the mouse left button is not down, we must
7364 // stop the event injection
7365 if (bdragging) {
7366 if (!g_btouch) {
7367 wxMouseState state = ::wxGetMouseState();
7368#if wxCHECK_VERSION(3, 0, 0)
7369 if (!state.LeftIsDown())
7370#else
7371 if (!state.LeftDown())
7372#endif
7373 bft = false;
7374 }
7375 }
7376 if ((bft) && !pPanTimer->IsRunning()) {
7377 PanCanvas(pan_x, pan_y);
7378 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7379 return true;
7380 }
7381
7382 // This mouse event must not be due to pan timer event injector
7383 // Mouse is out of the pan zone, so prevent any orphan event injection
7384 if ((!bft) && pPanTimer->IsRunning()) {
7385 pPanTimer->Stop();
7386 }
7387
7388 return (false);
7389}
7390
7391// Look for waypoints at the current position.
7392// Used to determine what a mouse event should act on.
7393
7394void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7395 bool setBeingEdited) {
7396 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7397 m_pRoutePointEditTarget = NULL;
7398 m_pFoundPoint = NULL;
7399
7400 SelectItem *pFind = NULL;
7401 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7402 SelectableItemList SelList = pSelect->FindSelectionList(
7403 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7404 for (SelectItem *pFind : SelList) {
7405 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7406
7407 // Get an array of all routes using this point
7408 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7409 // TODO: delete m_pEditRouteArray after use?
7410
7411 // Use route array to determine actual visibility for the point
7412 bool brp_viz = false;
7413 if (m_pEditRouteArray) {
7414 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7415 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7416 if (pr->IsVisible()) {
7417 brp_viz = true;
7418 break;
7419 }
7420 }
7421 } else
7422 brp_viz = frp->IsVisible(); // isolated point
7423
7424 if (brp_viz) {
7425 // Use route array to rubberband all affected routes
7426 if (m_pEditRouteArray) // Editing Waypoint as part of route
7427 {
7428 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7429 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7430 pr->m_bIsBeingEdited = setBeingEdited;
7431 }
7432 m_bRouteEditing = setBeingEdited;
7433 } else // editing Mark
7434 {
7435 frp->m_bRPIsBeingEdited = setBeingEdited;
7436 m_bMarkEditing = setBeingEdited;
7437 }
7438
7439 m_pRoutePointEditTarget = frp;
7440 m_pFoundPoint = pFind;
7441 break; // out of the while(node)
7442 }
7443 } // for (SelectItem...
7444}
7445std::shared_ptr<HostApi121::PiPointContext>
7446ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7447 // General Right Click
7448 // Look for selectable objects
7449 double slat, slon;
7450 GetCanvasPixPoint(x, y, slat, slon);
7451
7452 SelectItem *pFindAIS;
7453 SelectItem *pFindRP;
7454 SelectItem *pFindRouteSeg;
7455 SelectItem *pFindTrackSeg;
7456 SelectItem *pFindCurrent = NULL;
7457 SelectItem *pFindTide = NULL;
7458
7459 // Get all the selectable things at the selected point
7460 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7461 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7462 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7463 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7464 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7465
7466 if (m_bShowCurrent)
7467 pFindCurrent =
7468 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7469
7470 if (m_bShowTide) // look for tide stations
7471 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7472
7473 int seltype = 0;
7474
7475 // Try for AIS targets first
7476 int FoundAIS_MMSI = 0;
7477 if (pFindAIS) {
7478 FoundAIS_MMSI = pFindAIS->GetUserData();
7479
7480 // Make sure the target data is available
7481 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7482 seltype |= SELTYPE_AISTARGET;
7483 }
7484
7485 // Now the various Route Parts
7486
7487 RoutePoint *FoundRoutePoint = NULL;
7488 Route *SelectedRoute = NULL;
7489
7490 if (pFindRP) {
7491 RoutePoint *pFirstVizPoint = NULL;
7492 RoutePoint *pFoundActiveRoutePoint = NULL;
7493 RoutePoint *pFoundVizRoutePoint = NULL;
7494 Route *pSelectedActiveRoute = NULL;
7495 Route *pSelectedVizRoute = NULL;
7496
7497 // There is at least one routepoint, so get the whole list
7498 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7499 SelectableItemList SelList =
7500 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7501 for (SelectItem *pFindSel : SelList) {
7502 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7503
7504 // Get an array of all routes using this point
7505 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7506
7507 // Use route array (if any) to determine actual visibility for this point
7508 bool brp_viz = false;
7509 if (proute_array) {
7510 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7511 Route *pr = (Route *)proute_array->Item(ir);
7512 if (pr->IsVisible()) {
7513 brp_viz = true;
7514 break;
7515 }
7516 }
7517 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7518 // but still exists as a waypoint
7519 brp_viz = prp->IsVisible(); // so treat as isolated point
7520
7521 } else
7522 brp_viz = prp->IsVisible(); // isolated point
7523
7524 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7525
7526 // Use route array to choose the appropriate route
7527 // Give preference to any active route, otherwise select the first visible
7528 // route in the array for this point
7529 if (proute_array) {
7530 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7531 Route *pr = (Route *)proute_array->Item(ir);
7532 if (pr->m_bRtIsActive) {
7533 pSelectedActiveRoute = pr;
7534 pFoundActiveRoutePoint = prp;
7535 break;
7536 }
7537 }
7538
7539 if (NULL == pSelectedVizRoute) {
7540 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7541 Route *pr = (Route *)proute_array->Item(ir);
7542 if (pr->IsVisible()) {
7543 pSelectedVizRoute = pr;
7544 pFoundVizRoutePoint = prp;
7545 break;
7546 }
7547 }
7548 }
7549
7550 delete proute_array;
7551 }
7552 }
7553
7554 // Now choose the "best" selections
7555 if (pFoundActiveRoutePoint) {
7556 FoundRoutePoint = pFoundActiveRoutePoint;
7557 SelectedRoute = pSelectedActiveRoute;
7558 } else if (pFoundVizRoutePoint) {
7559 FoundRoutePoint = pFoundVizRoutePoint;
7560 SelectedRoute = pSelectedVizRoute;
7561 } else
7562 // default is first visible point in list
7563 FoundRoutePoint = pFirstVizPoint;
7564
7565 if (SelectedRoute) {
7566 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7567 } else if (FoundRoutePoint) {
7568 seltype |= SELTYPE_MARKPOINT;
7569 }
7570
7571 // Highlight the selected point, to verify the proper right click selection
7572#if 0
7573 if (m_pFoundRoutePoint) {
7574 m_pFoundRoutePoint->m_bPtIsSelected = true;
7575 wxRect wp_rect;
7576 RoutePointGui(*m_pFoundRoutePoint)
7577 .CalculateDCRect(m_dc_route, this, &wp_rect);
7578 RefreshRect(wp_rect, true);
7579 }
7580#endif
7581 }
7582
7583 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7584 // routes But call the popup handler with identifier appropriate to the type
7585 if (pFindRouteSeg) // there is at least one select item
7586 {
7587 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7588 SelectableItemList SelList =
7589 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7590
7591 if (NULL == SelectedRoute) // the case where a segment only is selected
7592 {
7593 // Choose the first visible route containing segment in the list
7594 for (SelectItem *pFindSel : SelList) {
7595 Route *pr = (Route *)pFindSel->m_pData3;
7596 if (pr->IsVisible()) {
7597 SelectedRoute = pr;
7598 break;
7599 }
7600 }
7601 }
7602
7603 if (SelectedRoute) {
7604 if (NULL == FoundRoutePoint)
7605 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7606
7607 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7608 seltype |= SELTYPE_ROUTESEGMENT;
7609 }
7610 }
7611
7612 if (pFindTrackSeg) {
7613 m_pSelectedTrack = NULL;
7614 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7615 SelectableItemList SelList =
7616 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7617
7618 // Choose the first visible track containing segment in the list
7619 for (SelectItem *pFindSel : SelList) {
7620 Track *pt = (Track *)pFindSel->m_pData3;
7621 if (pt->IsVisible()) {
7622 m_pSelectedTrack = pt;
7623 break;
7624 }
7625 }
7626 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7627 }
7628
7629 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7630
7631 // Populate the return struct
7632 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7633 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7634 rstruct->object_ident = "";
7635
7636 if (seltype == SELTYPE_AISTARGET) {
7637 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7638 wxString val;
7639 val.Printf("%d", FoundAIS_MMSI);
7640 rstruct->object_ident = val.ToStdString();
7641 } else if (seltype & SELTYPE_MARKPOINT) {
7642 if (FoundRoutePoint) {
7643 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7644 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7645 }
7646 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7647 if (SelectedRoute) {
7648 rstruct->object_type =
7649 HostApi121::PiContextObjectType::kObjectRoutesegment;
7650 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7651 }
7652 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7653 if (m_pSelectedTrack) {
7654 rstruct->object_type =
7655 HostApi121::PiContextObjectType::kObjectTracksegment;
7656 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7657 }
7658 }
7659
7660 return rstruct;
7661}
7662
7663void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7664 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7665 singleClickEventIsValid = false;
7666 m_DoubleClickTimer->Stop();
7667}
7668
7669bool leftIsDown;
7670
7671bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7672 if (!m_bChartDragging && !m_bDrawingRoute) {
7673 /*
7674 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7675 * mouse event coordinates are in logical pixels.
7676 */
7677 if (m_Compass && m_Compass->IsShown()) {
7678 wxRect logicalRect = m_Compass->GetLogicalRect();
7679 bool isInCompass = logicalRect.Contains(event.GetPosition());
7680 if (isInCompass || m_mouseWasInCompass) {
7681 if (m_Compass->MouseEvent(event)) {
7682 cursor_region = CENTER;
7683 if (!g_btouch) SetCanvasCursor(event);
7684 m_mouseWasInCompass = isInCompass;
7685 return true;
7686 }
7687 }
7688 m_mouseWasInCompass = isInCompass;
7689 }
7690
7691 if (m_notification_button && m_notification_button->IsShown()) {
7692 wxRect logicalRect = m_notification_button->GetLogicalRect();
7693 bool isinButton = logicalRect.Contains(event.GetPosition());
7694 if (isinButton) {
7695 SetCursor(*pCursorArrow);
7696 if (event.LeftDown()) HandleNotificationMouseClick();
7697 return true;
7698 }
7699 }
7700
7701 if (MouseEventToolbar(event)) return true;
7702
7703 if (MouseEventChartBar(event)) return true;
7704
7705 if (MouseEventMUIBar(event)) return true;
7706
7707 if (MouseEventIENCBar(event)) return true;
7708 }
7709 return false;
7710}
7711
7712void ChartCanvas::HandleNotificationMouseClick() {
7713 if (!m_NotificationsList) {
7714 m_NotificationsList = new NotificationsList(this);
7715
7716 // calculate best size for Notification list
7717 m_NotificationsList->RecalculateSize();
7718 m_NotificationsList->Hide();
7719 }
7720
7721 if (m_NotificationsList->IsShown()) {
7722 m_NotificationsList->Hide();
7723 } else {
7724 m_NotificationsList->RecalculateSize();
7725 m_NotificationsList->ReloadNotificationList();
7726 m_NotificationsList->Show();
7727 }
7728}
7729bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7730 if (!g_bShowChartBar) return false;
7731
7732 if (!m_Piano->MouseEvent(event)) return false;
7733
7734 cursor_region = CENTER;
7735 if (!g_btouch) SetCanvasCursor(event);
7736 return true;
7737}
7738
7739bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7740 if (!IsPrimaryCanvas()) return false;
7741
7742 if (g_MainToolbar) {
7743 if (!g_MainToolbar->MouseEvent(event))
7744 return false;
7745 else
7746 g_MainToolbar->RefreshToolbar();
7747 }
7748
7749 cursor_region = CENTER;
7750 if (!g_btouch) SetCanvasCursor(event);
7751 return true;
7752}
7753
7754bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7755 if (!IsPrimaryCanvas()) return false;
7756
7757 if (g_iENCToolbar) {
7758 if (!g_iENCToolbar->MouseEvent(event))
7759 return false;
7760 else {
7761 g_iENCToolbar->RefreshToolbar();
7762 return true;
7763 }
7764 }
7765 return false;
7766}
7767
7768bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7769 if (m_muiBar) {
7770 if (!m_muiBar->MouseEvent(event)) return false;
7771 }
7772
7773 cursor_region = CENTER;
7774 if (!g_btouch) SetCanvasCursor(event);
7775 if (m_muiBar)
7776 return true;
7777 else
7778 return false;
7779}
7780
7781bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7782 int x, y;
7783
7784 bool bret = false;
7785
7786 event.GetPosition(&x, &y);
7787
7788 x *= m_displayScale;
7789 y *= m_displayScale;
7790
7791 m_MouseDragging = event.Dragging();
7792
7793 // Some systems produce null drag events, where the pointer position has not
7794 // changed from the previous value. Detect this case, and abort further
7795 // processing (FS#1748)
7796#ifdef __WXMSW__
7797 if (event.Dragging()) {
7798 if ((x == mouse_x) && (y == mouse_y)) return true;
7799 }
7800#endif
7801
7802 mouse_x = x;
7803 mouse_y = y;
7804 mouse_leftisdown = event.LeftDown();
7806
7807 // Establish the event region
7808 cursor_region = CENTER;
7809
7810 int chartbar_height = GetChartbarHeight();
7811
7812 if (m_Compass && m_Compass->IsShown() &&
7813 m_Compass->GetRect().Contains(event.GetPosition())) {
7814 cursor_region = CENTER;
7815 } else if (x > xr_margin) {
7816 cursor_region = MID_RIGHT;
7817 } else if (x < xl_margin) {
7818 cursor_region = MID_LEFT;
7819 } else if (y > yb_margin - chartbar_height &&
7820 y < m_canvas_height - chartbar_height) {
7821 cursor_region = MID_TOP;
7822 } else if (y < yt_margin) {
7823 cursor_region = MID_BOT;
7824 } else {
7825 cursor_region = CENTER;
7826 }
7827
7828 if (!g_btouch) SetCanvasCursor(event);
7829
7830 // Protect from leftUp's coming from event handlers in child
7831 // windows who return focus to the canvas.
7832 leftIsDown = event.LeftDown();
7833
7834#ifndef __WXOSX__
7835 if (event.LeftDown()) {
7836 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7837 // The menu bar is temporarily visible due to alt having been pressed.
7838 // Clicking will hide it, and do nothing else.
7839 g_bTempShowMenuBar = false;
7840 parent_frame->ApplyGlobalSettings(false);
7841 return (true);
7842 }
7843 }
7844#endif
7845
7846 // Update modifiers here; some window managers never send the key event
7847 m_modkeys = 0;
7848 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7849 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7850
7851#ifdef __WXMSW__
7852 // TODO Test carefully in other platforms, remove ifdef....
7853 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7854 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7855#endif
7856
7857 event.SetEventObject(this);
7858 if (SendMouseEventToPlugins(event))
7859 return (true); // PlugIn did something, and does not want the canvas to
7860 // do anything else
7861
7862 // Capture LeftUp's and time them, unless it already came from the timer.
7863
7864 // Detect end of chart dragging
7865 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7866 StartChartDragInertia();
7867 }
7868
7869 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7870 !singleClickEventIsValid) {
7871 // Ignore the second LeftUp after the DClick.
7872 if (m_DoubleClickTimer->IsRunning()) {
7873 m_DoubleClickTimer->Stop();
7874 return (true);
7875 }
7876
7877 // Save the event for later running if there is no DClick.
7878 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7879 singleClickEvent = event;
7880 singleClickEventIsValid = true;
7881 return (true);
7882 }
7883
7884 // This logic is necessary on MSW to handle the case where
7885 // a context (right-click) menu is dismissed without action
7886 // by clicking on the chart surface.
7887 // We need to avoid an unintentional pan by eating some clicks...
7888#ifdef __WXMSW__
7889 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7890 if (g_click_stop > 0) {
7891 g_click_stop--;
7892 return (true);
7893 }
7894 }
7895#endif
7896
7897 // Kick off the Rotation control timer
7898 if (GetUpMode() == COURSE_UP_MODE) {
7899 m_b_rot_hidef = false;
7900 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7901 } else
7902 pRotDefTimer->Stop();
7903
7904 // Retrigger the route leg / AIS target popup timer
7905 bool bRoll = !g_btouch;
7906#ifdef __ANDROID__
7907 bRoll = g_bRollover;
7908#endif
7909 if (bRoll) {
7910 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7911 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7912 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7913 m_RolloverPopupTimer.Start(
7914 10,
7915 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7916 else
7917 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7918 }
7919
7920 // Retrigger the cursor tracking timer
7921 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7922
7923// Show cursor position on Status Bar, if present
7924// except for GTK, under which status bar updates are very slow
7925// due to Update() call.
7926// In this case, as a workaround, update the status window
7927// after an interval timer (pCurTrackTimer) pops, which will happen
7928// whenever the mouse has stopped moving for specified interval.
7929// See the method OnCursorTrackTimerEvent()
7930#if !defined(__WXGTK__) && !defined(__WXQT__)
7931 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7932#endif
7933
7934 // Send the current cursor lat/lon to all PlugIns requesting it
7935 if (g_pi_manager) {
7936 // Occasionally, MSW will produce nonsense events on right click....
7937 // This results in an error in cursor geo position, so we skip this case
7938 if ((x >= 0) && (y >= 0))
7939 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7940 }
7941
7942 if (!g_btouch) {
7943 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7944 wxPoint p = ClientToScreen(wxPoint(x, y));
7945 }
7946 }
7947
7948 if (1 ) {
7949 // Route Creation Rubber Banding
7950 if (m_routeState >= 2) {
7951 r_rband.x = x;
7952 r_rband.y = y;
7953 m_bDrawingRoute = true;
7954
7955 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7956 Refresh(false);
7957 }
7958
7959 // Measure Tool Rubber Banding
7960 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7961 r_rband.x = x;
7962 r_rband.y = y;
7963 m_bDrawingRoute = true;
7964
7965 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7966 Refresh(false);
7967 }
7968 }
7969 return bret;
7970}
7971
7972int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7973 // On general Right Click
7974 // Look for selectable objects
7975 double slat = lat;
7976 double slon = lon;
7977
7978#if defined(__WXMAC__) || defined(__ANDROID__)
7979 wxScreenDC sdc;
7980 ocpnDC dc(sdc);
7981#else
7982 wxClientDC cdc(GetParent());
7983 ocpnDC dc(cdc);
7984#endif
7985
7986 SelectItem *pFindAIS;
7987 SelectItem *pFindRP;
7988 SelectItem *pFindRouteSeg;
7989 SelectItem *pFindTrackSeg;
7990 SelectItem *pFindCurrent = NULL;
7991 SelectItem *pFindTide = NULL;
7992
7993 // Deselect any current objects
7994 if (m_pSelectedRoute) {
7995 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7996 m_pSelectedRoute->DeSelectRoute();
7997#ifdef ocpnUSE_GL
7998 if (g_bopengl && m_glcc) {
7999 InvalidateGL();
8000 Update();
8001 } else
8002#endif
8003 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8004 }
8005
8006 if (m_pFoundRoutePoint) {
8007 m_pFoundRoutePoint->m_bPtIsSelected = false;
8008 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8009 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8010 }
8011
8014 if (g_btouch && m_pRoutePointEditTarget) {
8015 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8016 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8017 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8018 }
8019
8020 // Get all the selectable things at the cursor
8021 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8022 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8023 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8024 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8025 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8026
8027 if (m_bShowCurrent)
8028 pFindCurrent =
8029 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8030
8031 if (m_bShowTide) // look for tide stations
8032 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8033
8034 int seltype = 0;
8035
8036 // Try for AIS targets first
8037 if (pFindAIS) {
8038 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8039
8040 // Make sure the target data is available
8041 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8042 seltype |= SELTYPE_AISTARGET;
8043 }
8044
8045 // Now examine the various Route parts
8046
8047 m_pFoundRoutePoint = NULL;
8048 if (pFindRP) {
8049 RoutePoint *pFirstVizPoint = NULL;
8050 RoutePoint *pFoundActiveRoutePoint = NULL;
8051 RoutePoint *pFoundVizRoutePoint = NULL;
8052 Route *pSelectedActiveRoute = NULL;
8053 Route *pSelectedVizRoute = NULL;
8054
8055 // There is at least one routepoint, so get the whole list
8056 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8057 SelectableItemList SelList =
8058 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8059 for (SelectItem *pFindSel : SelList) {
8060 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8061
8062 // Get an array of all routes using this point
8063 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8064
8065 // Use route array (if any) to determine actual visibility for this point
8066 bool brp_viz = false;
8067 if (proute_array) {
8068 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8069 Route *pr = (Route *)proute_array->Item(ir);
8070 if (pr->IsVisible()) {
8071 brp_viz = true;
8072 break;
8073 }
8074 }
8075 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8076 // but still exists as a waypoint
8077 brp_viz = prp->IsVisible(); // so treat as isolated point
8078
8079 } else
8080 brp_viz = prp->IsVisible(); // isolated point
8081
8082 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8083
8084 // Use route array to choose the appropriate route
8085 // Give preference to any active route, otherwise select the first visible
8086 // route in the array for this point
8087 m_pSelectedRoute = NULL;
8088 if (proute_array) {
8089 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8090 Route *pr = (Route *)proute_array->Item(ir);
8091 if (pr->m_bRtIsActive) {
8092 pSelectedActiveRoute = pr;
8093 pFoundActiveRoutePoint = prp;
8094 break;
8095 }
8096 }
8097
8098 if (NULL == pSelectedVizRoute) {
8099 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8100 Route *pr = (Route *)proute_array->Item(ir);
8101 if (pr->IsVisible()) {
8102 pSelectedVizRoute = pr;
8103 pFoundVizRoutePoint = prp;
8104 break;
8105 }
8106 }
8107 }
8108
8109 delete proute_array;
8110 }
8111 }
8112
8113 // Now choose the "best" selections
8114 if (pFoundActiveRoutePoint) {
8115 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8116 m_pSelectedRoute = pSelectedActiveRoute;
8117 } else if (pFoundVizRoutePoint) {
8118 m_pFoundRoutePoint = pFoundVizRoutePoint;
8119 m_pSelectedRoute = pSelectedVizRoute;
8120 } else
8121 // default is first visible point in list
8122 m_pFoundRoutePoint = pFirstVizPoint;
8123
8124 if (m_pSelectedRoute) {
8125 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8126 } else if (m_pFoundRoutePoint) {
8127 seltype |= SELTYPE_MARKPOINT;
8128 }
8129
8130 // Highlight the selected point, to verify the proper right click selection
8131 if (m_pFoundRoutePoint) {
8132 m_pFoundRoutePoint->m_bPtIsSelected = true;
8133 wxRect wp_rect;
8134 RoutePointGui(*m_pFoundRoutePoint)
8135 .CalculateDCRect(m_dc_route, this, &wp_rect);
8136 RefreshRect(wp_rect, true);
8137 }
8138 }
8139
8140 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8141 // routes But call the popup handler with identifier appropriate to the type
8142 if (pFindRouteSeg) // there is at least one select item
8143 {
8144 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8145 SelectableItemList SelList =
8146 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8147
8148 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8149 {
8150 // Choose the first visible route containing segment in the list
8151 for (SelectItem *pFindSel : SelList) {
8152 Route *pr = (Route *)pFindSel->m_pData3;
8153 if (pr->IsVisible()) {
8154 m_pSelectedRoute = pr;
8155 break;
8156 }
8157 }
8158 }
8159
8160 if (m_pSelectedRoute) {
8161 if (NULL == m_pFoundRoutePoint)
8162 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8163
8164 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8165 if (m_pSelectedRoute->m_bRtIsSelected) {
8166#ifdef ocpnUSE_GL
8167 if (g_bopengl && m_glcc) {
8168 InvalidateGL();
8169 Update();
8170 } else
8171#endif
8172 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8173 }
8174 seltype |= SELTYPE_ROUTESEGMENT;
8175 }
8176 }
8177
8178 if (pFindTrackSeg) {
8179 m_pSelectedTrack = NULL;
8180 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8181 SelectableItemList SelList =
8182 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8183
8184 // Choose the first visible track containing segment in the list
8185 for (SelectItem *pFindSel : SelList) {
8186 Track *pt = (Track *)pFindSel->m_pData3;
8187 if (pt->IsVisible()) {
8188 m_pSelectedTrack = pt;
8189 break;
8190 }
8191 }
8192 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8193 }
8194
8195#if 0 // disable tide and current graph on right click
8196 {
8197 if (pFindCurrent) {
8198 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8199 seltype |= SELTYPE_CURRENTPOINT;
8200 }
8201
8202 else if (pFindTide) {
8203 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8204 seltype |= SELTYPE_TIDEPOINT;
8205 }
8206 }
8207#endif
8208
8209 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8210
8211 return seltype;
8212}
8213
8214IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8215 // There may be multiple current entries at the same point.
8216 // For example, there often is a current substation (with directions
8217 // specified) co-located with its master. We want to select the
8218 // substation, so that the direction will be properly indicated on the
8219 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8220 // substation)
8221 IDX_entry *pIDX_best_candidate;
8222
8223 SelectItem *pFind = NULL;
8224 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8225 SelectableItemList SelList =
8226 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8227
8228 // Default is first entry
8229 pFind = *SelList.begin();
8230 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8231
8232 auto node = SelList.begin();
8233 if (SelList.size() > 1) {
8234 for (++node; node != SelList.end(); ++node) {
8235 pFind = *node;
8236 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8237 if (pIDX_candidate->IDX_type == 'c') {
8238 pIDX_best_candidate = pIDX_candidate;
8239 break;
8240 }
8241 } // while (node)
8242 } else {
8243 pFind = *SelList.begin();
8244 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8245 }
8246
8247 return pIDX_best_candidate;
8248}
8249void ChartCanvas::CallPopupMenu(int x, int y) {
8250 last_drag.x = x;
8251 last_drag.y = y;
8252 if (m_routeState) { // creating route?
8253 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8254 return;
8255 }
8256
8258
8259 // If tide or current point is selected, then show the TC dialog immediately
8260 // without context menu
8261 if (SELTYPE_CURRENTPOINT == seltype) {
8262 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8263 Refresh(false);
8264 return;
8265 }
8266
8267 if (SELTYPE_TIDEPOINT == seltype) {
8268 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8269 Refresh(false);
8270 return;
8271 }
8272
8273 InvokeCanvasMenu(x, y, seltype);
8274
8275 // Clean up if not deleted in InvokeCanvasMenu
8276 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8277 m_pSelectedRoute->m_bRtIsSelected = false;
8278 }
8279
8280 m_pSelectedRoute = NULL;
8281
8282 if (m_pFoundRoutePoint) {
8283 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8284 m_pFoundRoutePoint->m_bPtIsSelected = false;
8285 }
8286 m_pFoundRoutePoint = NULL;
8287
8288 Refresh(true);
8289 // Refresh(false); // needed for MSW, not GTK Why??
8290}
8291
8292bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8293 // For now just bail out completely if the point clicked is not on the chart
8294 if (std::isnan(m_cursor_lat)) return false;
8295
8296 // Mouse Clicks
8297 bool ret = false; // return true if processed
8298
8299 int x, y, mx, my;
8300 event.GetPosition(&x, &y);
8301 mx = x;
8302 my = y;
8303
8304 // Calculate meaningful SelectRadius
8305 float SelectRadius;
8306 SelectRadius = g_Platform->GetSelectRadiusPix() /
8307 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8308
8310 // We start with Double Click processing. The first left click just starts a
8311 // timer and is remembered, then we actually do something if there is a
8312 // LeftDClick. If there is, the two single clicks are ignored.
8313
8314 if (event.LeftDClick() && (cursor_region == CENTER)) {
8315 m_DoubleClickTimer->Start();
8316 singleClickEventIsValid = false;
8317
8318 double zlat, zlon;
8320 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8321
8322 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8323 if (m_bShowAIS) {
8324 SelectItem *pFindAIS;
8325 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8326
8327 if (pFindAIS) {
8328 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8329 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8330 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8331 }
8332 return true;
8333 }
8334 }
8335
8336 SelectableItemList rpSelList =
8337 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8338 bool b_onRPtarget = false;
8339 for (SelectItem *pFind : rpSelList) {
8340 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8341 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8342 b_onRPtarget = true;
8343 break;
8344 }
8345 }
8346
8347 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8348
8349 // Get and honor the plugin API ContextMenuMask
8350 std::unique_ptr<HostApi> host_api = GetHostApi();
8351 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8352
8353 if (m_pRoutePointEditTarget) {
8354 if (b_onRPtarget) {
8355 if ((api_121->GetContextMenuMask() &
8356 api_121->kContextMenuDisableWaypoint))
8357 return true;
8358 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8359 return true;
8360 } else {
8361 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8362 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8363 if (g_btouch)
8364 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8365 wxRect wp_rect;
8366 RoutePointGui(*m_pRoutePointEditTarget)
8367 .CalculateDCRect(m_dc_route, this, &wp_rect);
8368 m_pRoutePointEditTarget = NULL; // cancel selection
8369 RefreshRect(wp_rect, true);
8370 return true;
8371 }
8372 } else {
8373 auto node = rpSelList.begin();
8374 if (node != rpSelList.end()) {
8375 SelectItem *pFind = *node;
8376 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8377 if (frp) {
8378 wxArrayPtrVoid *proute_array =
8380
8381 // Use route array (if any) to determine actual visibility for this
8382 // point
8383 bool brp_viz = false;
8384 if (proute_array) {
8385 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8386 Route *pr = (Route *)proute_array->Item(ir);
8387 if (pr->IsVisible()) {
8388 brp_viz = true;
8389 break;
8390 }
8391 }
8392 delete proute_array;
8393 if (!brp_viz &&
8394 frp->IsShared()) // is not visible as part of route, but
8395 // still exists as a waypoint
8396 brp_viz = frp->IsVisible(); // so treat as isolated point
8397 } else
8398 brp_viz = frp->IsVisible(); // isolated point
8399
8400 if (brp_viz) {
8401 if ((api_121->GetContextMenuMask() &
8402 api_121->kContextMenuDisableWaypoint))
8403 return true;
8404
8405 ShowMarkPropertiesDialog(frp);
8406 return true;
8407 }
8408 }
8409 }
8410 }
8411
8412 SelectItem *cursorItem;
8413
8414 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8415 if (cursorItem) {
8416 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8417 return true;
8418 Route *pr = (Route *)cursorItem->m_pData3;
8419 if (pr->IsVisible()) {
8420 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8421 return true;
8422 }
8423 }
8424
8425 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8426 if (cursorItem) {
8427 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8428 return true;
8429 Track *pt = (Track *)cursorItem->m_pData3;
8430 if (pt->IsVisible()) {
8431 ShowTrackPropertiesDialog(pt);
8432 return true;
8433 }
8434 }
8435
8436 // Tide and current points
8437 SelectItem *pFindCurrent = NULL;
8438 SelectItem *pFindTide = NULL;
8439
8440 if (m_bShowCurrent) { // look for current stations
8441 pFindCurrent =
8442 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8443 if (pFindCurrent) {
8444 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8445 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8446 Refresh(false);
8447 return true;
8448 }
8449 }
8450
8451 if (m_bShowTide) { // look for tide stations
8452 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8453 if (pFindTide) {
8454 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8455 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8456 Refresh(false);
8457 return true;
8458 }
8459 }
8460
8461 // Found no object to act on, so show chart info.
8462 ShowObjectQueryWindow(x, y, zlat, zlon);
8463 return true;
8464 }
8465
8467 if (event.LeftDown()) {
8468 // This really should not be needed, but....
8469 // on Windows, when using wxAUIManager, sometimes the focus is lost
8470 // when clicking into another pane, e.g.the AIS target list, and then back
8471 // to this pane. Oddly, some mouse events are not lost, however. Like this
8472 // one....
8473 SetFocus();
8474
8475 last_drag.x = mx;
8476 last_drag.y = my;
8477 leftIsDown = true;
8478
8479 if (!g_btouch) {
8480 if (m_routeState) // creating route?
8481 {
8482 double rlat, rlon;
8483 bool appending = false;
8484 bool inserting = false;
8485 Route *tail = 0;
8486
8487 SetCursor(*pCursorPencil);
8488 rlat = m_cursor_lat;
8489 rlon = m_cursor_lon;
8490
8491 m_bRouteEditing = true;
8492
8493 if (m_routeState == 1) {
8494 m_pMouseRoute = new Route();
8495 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8496 pRouteList->push_back(m_pMouseRoute);
8497 r_rband.x = x;
8498 r_rband.y = y;
8499 }
8500
8501 // Check to see if there is a nearby point which may be reused
8502 RoutePoint *pMousePoint = NULL;
8503
8504 // Calculate meaningful SelectRadius
8505 double nearby_radius_meters =
8506 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8507
8508 RoutePoint *pNearbyPoint =
8509 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8510 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8511 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8512 wxArrayPtrVoid *proute_array =
8513 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8514
8515 // Use route array (if any) to determine actual visibility for this
8516 // point
8517 bool brp_viz = false;
8518 if (proute_array) {
8519 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8520 Route *pr = (Route *)proute_array->Item(ir);
8521 if (pr->IsVisible()) {
8522 brp_viz = true;
8523 break;
8524 }
8525 }
8526 delete proute_array;
8527 if (!brp_viz &&
8528 pNearbyPoint->IsShared()) // is not visible as part of route,
8529 // but still exists as a waypoint
8530 brp_viz =
8531 pNearbyPoint->IsVisible(); // so treat as isolated point
8532 } else
8533 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8534
8535 if (brp_viz) {
8536 wxString msg = _("Use nearby waypoint?");
8537 // Don't add a mark without name to the route. Name it if needed
8538 const bool noname(pNearbyPoint->GetName() == "");
8539 if (noname) {
8540 msg =
8541 _("Use nearby nameless waypoint and name it M with"
8542 " a unique number?");
8543 }
8544 // Avoid route finish on focus change for message dialog
8545 m_FinishRouteOnKillFocus = false;
8546 int dlg_return =
8547 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8548 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8549 m_FinishRouteOnKillFocus = true;
8550 if (dlg_return == wxID_YES) {
8551 if (noname) {
8552 if (m_pMouseRoute) {
8553 int last_wp_num = m_pMouseRoute->GetnPoints();
8554 // AP-ECRMB will truncate to 6 characters
8555 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8556 wxString wp_name = wxString::Format(
8557 "M%002i-%s", last_wp_num + 1, guid_short);
8558 pNearbyPoint->SetName(wp_name);
8559 } else
8560 pNearbyPoint->SetName("WPXX");
8561 }
8562 pMousePoint = pNearbyPoint;
8563
8564 // Using existing waypoint, so nothing to delete for undo.
8565 if (m_routeState > 1)
8566 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8567 Undo_HasParent, NULL);
8568
8569 tail =
8570 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8571 bool procede = false;
8572 if (tail) {
8573 procede = true;
8574 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8575 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8576 procede = false;
8577 }
8578
8579 if (procede) {
8580 int dlg_return;
8581 m_FinishRouteOnKillFocus = false;
8582 if (m_routeState ==
8583 1) { // first point in new route, preceeding route to be
8584 // added? Not touch case
8585
8586 wxString dmsg =
8587 _("Insert first part of this route in the new route?");
8588 if (tail->GetIndexOf(pMousePoint) ==
8589 tail->GetnPoints()) // Starting on last point of another
8590 // route?
8591 dmsg = _("Insert this route in the new route?");
8592
8593 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8594 dlg_return = OCPNMessageBox(
8595 this, dmsg, _("OpenCPN Route Create"),
8596 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8597 m_FinishRouteOnKillFocus = true;
8598
8599 if (dlg_return == wxID_YES) {
8600 inserting = true; // part of the other route will be
8601 // preceeding the new route
8602 }
8603 }
8604 } else {
8605 wxString dmsg =
8606 _("Append last part of this route to the new route?");
8607 if (tail->GetIndexOf(pMousePoint) == 1)
8608 dmsg = _(
8609 "Append this route to the new route?"); // Picking the
8610 // first point
8611 // of another
8612 // route?
8613
8614 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8615 dlg_return = OCPNMessageBox(
8616 this, dmsg, _("OpenCPN Route Create"),
8617 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8618 m_FinishRouteOnKillFocus = true;
8619
8620 if (dlg_return == wxID_YES) {
8621 appending = true; // part of the other route will be
8622 // appended to the new route
8623 }
8624 }
8625 }
8626 }
8627
8628 // check all other routes to see if this point appears in any
8629 // other route If it appears in NO other route, then it should e
8630 // considered an isolated mark
8631 if (!FindRouteContainingWaypoint(pMousePoint))
8632 pMousePoint->SetShared(true);
8633 }
8634 }
8635 }
8636
8637 if (NULL == pMousePoint) { // need a new point
8638 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8639 "", wxEmptyString);
8640 pMousePoint->SetNameShown(false);
8641
8642 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8643
8644 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8645
8646 if (m_routeState > 1)
8647 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8648 Undo_IsOrphanded, NULL);
8649 }
8650
8651 if (m_pMouseRoute) {
8652 if (m_routeState == 1) {
8653 // First point in the route.
8654 m_pMouseRoute->AddPoint(pMousePoint);
8655 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8656 } else {
8657 if (m_pMouseRoute->m_NextLegGreatCircle) {
8658 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8659 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8660 &rhumbBearing, &rhumbDist);
8661 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8662 rlat, &gcDist, &gcBearing, NULL);
8663 double gcDistNM = gcDist / 1852.0;
8664
8665 // Empirically found expression to get reasonable route segments.
8666 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8667 pow(rhumbDist - gcDistNM - 1, 0.5);
8668
8669 wxString msg;
8670 msg << _("For this leg the Great Circle route is ")
8671 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8672 << _(" shorter than rhumbline.\n\n")
8673 << _("Would you like include the Great Circle routing points "
8674 "for this leg?");
8675
8676 m_FinishRouteOnKillFocus = false;
8677 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8678 // does not fully capture mouse
8679
8680 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8681 wxYES_NO | wxNO_DEFAULT);
8682
8683 m_disable_edge_pan = false;
8684 m_FinishRouteOnKillFocus = true;
8685
8686 if (answer == wxID_YES) {
8687 RoutePoint *gcPoint;
8688 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8689 wxRealPoint gcCoord;
8690
8691 for (int i = 1; i <= segmentCount; i++) {
8692 double fraction = (double)i * (1.0 / (double)segmentCount);
8693 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8694 gcDist * fraction, gcBearing,
8695 &gcCoord.x, &gcCoord.y, NULL);
8696
8697 if (i < segmentCount) {
8698 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8699 wxEmptyString);
8700 gcPoint->SetNameShown(false);
8701 // pConfig->AddNewWayPoint(gcPoint, -1);
8702 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8703
8704 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8705 gcPoint);
8706 } else {
8707 gcPoint = pMousePoint; // Last point, previously exsisting!
8708 }
8709
8710 m_pMouseRoute->AddPoint(gcPoint);
8711 pSelect->AddSelectableRouteSegment(
8712 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8713 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8714 prevGcPoint = gcPoint;
8715 }
8716
8717 undo->CancelUndoableAction(true);
8718
8719 } else {
8720 m_pMouseRoute->AddPoint(pMousePoint);
8721 pSelect->AddSelectableRouteSegment(
8722 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8723 pMousePoint, m_pMouseRoute);
8724 undo->AfterUndoableAction(m_pMouseRoute);
8725 }
8726 } else {
8727 // Ordinary rhumblinesegment.
8728 m_pMouseRoute->AddPoint(pMousePoint);
8729 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8730 rlon, m_prev_pMousePoint,
8731 pMousePoint, m_pMouseRoute);
8732 undo->AfterUndoableAction(m_pMouseRoute);
8733 }
8734 }
8735 }
8736 m_prev_rlat = rlat;
8737 m_prev_rlon = rlon;
8738 m_prev_pMousePoint = pMousePoint;
8739 if (m_pMouseRoute)
8740 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8741
8742 m_routeState++;
8743
8744 if (appending ||
8745 inserting) { // Appending a route or making a new route
8746 int connect = tail->GetIndexOf(pMousePoint);
8747 if (connect == 1) {
8748 inserting = false; // there is nothing to insert
8749 appending = true; // so append
8750 }
8751 int length = tail->GetnPoints();
8752
8753 int i;
8754 int start, stop;
8755 if (appending) {
8756 start = connect + 1;
8757 stop = length;
8758 } else { // inserting
8759 start = 1;
8760 stop = connect;
8761 m_pMouseRoute->RemovePoint(
8762 m_pMouseRoute
8763 ->GetLastPoint()); // Remove the first and only point
8764 }
8765 for (i = start; i <= stop; i++) {
8766 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8767 if (m_pMouseRoute)
8768 m_pMouseRoute->m_lastMousePointIndex =
8769 m_pMouseRoute->GetnPoints();
8770 m_routeState++;
8771 gFrame->RefreshAllCanvas();
8772 ret = true;
8773 }
8774 m_prev_rlat =
8775 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8776 m_prev_rlon =
8777 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8778 m_pMouseRoute->FinalizeForRendering();
8779 }
8780 gFrame->RefreshAllCanvas();
8781 ret = true;
8782 }
8783
8784 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8785 {
8786 SetCursor(*pCursorPencil);
8787
8788 if (!m_pMeasureRoute) {
8789 m_pMeasureRoute = new Route();
8790 pRouteList->push_back(m_pMeasureRoute);
8791 }
8792
8793 if (m_nMeasureState == 1) {
8794 r_rband.x = x;
8795 r_rband.y = y;
8796 }
8797
8798 RoutePoint *pMousePoint =
8799 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8800 wxEmptyString, wxEmptyString);
8801 pMousePoint->m_bShowName = false;
8802 pMousePoint->SetShowWaypointRangeRings(false);
8803
8804 m_pMeasureRoute->AddPoint(pMousePoint);
8805
8806 m_prev_rlat = m_cursor_lat;
8807 m_prev_rlon = m_cursor_lon;
8808 m_prev_pMousePoint = pMousePoint;
8809 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8810
8811 m_nMeasureState++;
8812 gFrame->RefreshAllCanvas();
8813 ret = true;
8814 }
8815
8816 else {
8817 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8818 }
8819 } // !g_btouch
8820 else { // g_btouch
8821 m_last_touch_down_pos = event.GetPosition();
8822
8823 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8824 // if near screen edge, pan with injection
8825 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8826 // return;
8827 // }
8828 }
8829 }
8830
8831 if (ret) return true;
8832 }
8833
8834 if (event.Dragging()) {
8835 // in touch screen mode ensure the finger/cursor is on the selected point's
8836 // radius to allow dragging
8837 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8838 if (g_btouch) {
8839 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8840 SelectItem *pFind = NULL;
8841 SelectableItemList SelList = pSelect->FindSelectionList(
8842 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8843 for (SelectItem *pFind : SelList) {
8844 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8845 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8846 }
8847 }
8848
8849 // Check for use of dragHandle
8850 if (m_pRoutePointEditTarget &&
8851 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8852 SelectItem *pFind = NULL;
8853 SelectableItemList SelList = pSelect->FindSelectionList(
8854 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8855 for (SelectItem *pFind : SelList) {
8856 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8857 if (m_pRoutePointEditTarget == frp) {
8858 m_bIsInRadius = true;
8859 break;
8860 }
8861 }
8862
8863 if (!m_dragoffsetSet) {
8864 RoutePointGui(*m_pRoutePointEditTarget)
8865 .PresetDragOffset(this, mouse_x, mouse_y);
8866 m_dragoffsetSet = true;
8867 }
8868 }
8869 }
8870
8871 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8872 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8873
8874 if (NULL == g_pMarkInfoDialog) {
8875 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8876 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8877 DraggingAllowed = false;
8878
8879 if (m_pRoutePointEditTarget &&
8880 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8881 DraggingAllowed = false;
8882
8883 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8884
8885 if (DraggingAllowed) {
8886 if (!undo->InUndoableAction()) {
8887 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8888 Undo_NeedsCopy, m_pFoundPoint);
8889 }
8890
8891 // Get the update rectangle for the union of the un-edited routes
8892 wxRect pre_rect;
8893
8894 if (!g_bopengl && m_pEditRouteArray) {
8895 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8896 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8897 // Need to validate route pointer
8898 // Route may be gone due to drgging close to ownship with
8899 // "Delete On Arrival" state set, as in the case of
8900 // navigating to an isolated waypoint on a temporary route
8901 if (g_pRouteMan->IsRouteValid(pr)) {
8902 wxRect route_rect;
8903 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8904 pre_rect.Union(route_rect);
8905 }
8906 }
8907 }
8908
8909 double new_cursor_lat = m_cursor_lat;
8910 double new_cursor_lon = m_cursor_lon;
8911
8912 if (CheckEdgePan(x, y, true, 5, 2))
8913 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8914
8915 // update the point itself
8916 if (g_btouch) {
8917 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8918 // new_cursor_lat, new_cursor_lon);
8919 RoutePointGui(*m_pRoutePointEditTarget)
8920 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8921 // update the Drag Handle entry in the pSelect list
8922 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8923 m_pRoutePointEditTarget,
8924 SELTYPE_DRAGHANDLE);
8925 m_pFoundPoint->m_slat =
8926 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8927 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8928 } else {
8929 m_pRoutePointEditTarget->m_lat =
8930 new_cursor_lat; // update the RoutePoint entry
8931 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8932 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8933 m_pFoundPoint->m_slat =
8934 new_cursor_lat; // update the SelectList entry
8935 m_pFoundPoint->m_slon = new_cursor_lon;
8936 }
8937
8938 // Update the MarkProperties Dialog, if currently shown
8939 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8940 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8941 g_pMarkInfoDialog->UpdateProperties(true);
8942 }
8943
8944 if (g_bopengl) {
8945 // InvalidateGL();
8946 Refresh(false);
8947 } else {
8948 // Get the update rectangle for the edited route
8949 wxRect post_rect;
8950
8951 if (m_pEditRouteArray) {
8952 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8953 ir++) {
8954 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8955 if (g_pRouteMan->IsRouteValid(pr)) {
8956 wxRect route_rect;
8957 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8958 post_rect.Union(route_rect);
8959 }
8960 }
8961 }
8962
8963 // Invalidate the union region
8964 pre_rect.Union(post_rect);
8965 RefreshRect(pre_rect, false);
8966 }
8967 gFrame->RefreshCanvasOther(this);
8968 m_bRoutePoinDragging = true;
8969 }
8970 ret = true;
8971 } // if Route Editing
8972
8973 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8974 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8975
8976 if (NULL == g_pMarkInfoDialog) {
8977 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8978 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8979 DraggingAllowed = false;
8980
8981 if (m_pRoutePointEditTarget &&
8982 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8983 DraggingAllowed = false;
8984
8985 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8986
8987 if (DraggingAllowed) {
8988 if (!undo->InUndoableAction()) {
8989 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8990 Undo_NeedsCopy, m_pFoundPoint);
8991 }
8992
8993 // The mark may be an anchorwatch
8994 double lpp1 = 0.;
8995 double lpp2 = 0.;
8996 double lppmax;
8997
8998 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8999 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9000 }
9001 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9002 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9003 }
9004 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9005
9006 // Get the update rectangle for the un-edited mark
9007 wxRect pre_rect;
9008 if (!g_bopengl) {
9009 RoutePointGui(*m_pRoutePointEditTarget)
9010 .CalculateDCRect(m_dc_route, this, &pre_rect);
9011 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9012 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9013 (int)(lppmax - (pre_rect.height / 2)));
9014 }
9015
9016 // update the point itself
9017 if (g_btouch) {
9018 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9019 // m_cursor_lat, m_cursor_lon);
9020 RoutePointGui(*m_pRoutePointEditTarget)
9021 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9022 // update the Drag Handle entry in the pSelect list
9023 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9024 m_pRoutePointEditTarget,
9025 SELTYPE_DRAGHANDLE);
9026 m_pFoundPoint->m_slat =
9027 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9028 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9029 } else {
9030 m_pRoutePointEditTarget->m_lat =
9031 m_cursor_lat; // update the RoutePoint entry
9032 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9033 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9034 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9035 m_pFoundPoint->m_slon = m_cursor_lon;
9036 }
9037
9038 // Update the MarkProperties Dialog, if currently shown
9039 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9040 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9041 g_pMarkInfoDialog->UpdateProperties(true);
9042 }
9043
9044 // Invalidate the union region
9045 if (g_bopengl) {
9046 if (!g_btouch) InvalidateGL();
9047 Refresh(false);
9048 } else {
9049 // Get the update rectangle for the edited mark
9050 wxRect post_rect;
9051 RoutePointGui(*m_pRoutePointEditTarget)
9052 .CalculateDCRect(m_dc_route, this, &post_rect);
9053 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9054 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9055 (int)(lppmax - (post_rect.height / 2)));
9056
9057 // Invalidate the union region
9058 pre_rect.Union(post_rect);
9059 RefreshRect(pre_rect, false);
9060 }
9061 gFrame->RefreshCanvasOther(this);
9062 m_bRoutePoinDragging = true;
9063 }
9064 ret = g_btouch ? m_bRoutePoinDragging : true;
9065 }
9066
9067 if (ret) return true;
9068 } // dragging
9069
9070 if (event.LeftUp()) {
9071 bool b_startedit_route = false;
9072 m_dragoffsetSet = false;
9073
9074 if (g_btouch) {
9075 m_bChartDragging = false;
9076 m_bIsInRadius = false;
9077
9078 if (m_routeState) // creating route?
9079 {
9080 if (m_ignore_next_leftup) {
9081 m_ignore_next_leftup = false;
9082 return false;
9083 }
9084
9085 if (m_bedge_pan) {
9086 m_bedge_pan = false;
9087 return false;
9088 }
9089
9090 double rlat, rlon;
9091 bool appending = false;
9092 bool inserting = false;
9093 Route *tail = 0;
9094
9095 rlat = m_cursor_lat;
9096 rlon = m_cursor_lon;
9097
9098 if (m_pRoutePointEditTarget) {
9099 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9100 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9101 if (!g_bopengl) {
9102 wxRect wp_rect;
9103 RoutePointGui(*m_pRoutePointEditTarget)
9104 .CalculateDCRect(m_dc_route, this, &wp_rect);
9105 RefreshRect(wp_rect, true);
9106 }
9107 m_pRoutePointEditTarget = NULL;
9108 }
9109 m_bRouteEditing = true;
9110
9111 if (m_routeState == 1) {
9112 m_pMouseRoute = new Route();
9113 m_pMouseRoute->SetHiLite(50);
9114 pRouteList->push_back(m_pMouseRoute);
9115 r_rband.x = x;
9116 r_rband.y = y;
9117 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9118 }
9119
9120 // Check to see if there is a nearby point which may be reused
9121 RoutePoint *pMousePoint = NULL;
9122
9123 // Calculate meaningful SelectRadius
9124 double nearby_radius_meters =
9125 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9126
9127 RoutePoint *pNearbyPoint =
9128 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9129 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9130 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9131 int dlg_return;
9132#ifndef __WXOSX__
9133 m_FinishRouteOnKillFocus =
9134 false; // Avoid route finish on focus change for message dialog
9135 dlg_return = OCPNMessageBox(
9136 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9137 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9138 m_FinishRouteOnKillFocus = true;
9139#else
9140 dlg_return = wxID_YES;
9141#endif
9142 if (dlg_return == wxID_YES) {
9143 pMousePoint = pNearbyPoint;
9144
9145 // Using existing waypoint, so nothing to delete for undo.
9146 if (m_routeState > 1)
9147 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9148 Undo_HasParent, NULL);
9149 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9150
9151 bool procede = false;
9152 if (tail) {
9153 procede = true;
9154 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9155 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9156 procede = false;
9157 }
9158
9159 if (procede) {
9160 int dlg_return;
9161 m_FinishRouteOnKillFocus = false;
9162 if (m_routeState == 1) { // first point in new route, preceeding
9163 // route to be added? touch case
9164
9165 wxString dmsg =
9166 _("Insert first part of this route in the new route?");
9167 if (tail->GetIndexOf(pMousePoint) ==
9168 tail->GetnPoints()) // Starting on last point of another
9169 // route?
9170 dmsg = _("Insert this route in the new route?");
9171
9172 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9173 dlg_return =
9174 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9175 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9176 m_FinishRouteOnKillFocus = true;
9177
9178 if (dlg_return == wxID_YES) {
9179 inserting = true; // part of the other route will be
9180 // preceeding the new route
9181 }
9182 }
9183 } else {
9184 wxString dmsg =
9185 _("Append last part of this route to the new route?");
9186 if (tail->GetIndexOf(pMousePoint) == 1)
9187 dmsg = _(
9188 "Append this route to the new route?"); // Picking the
9189 // first point of
9190 // another route?
9191
9192 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9193 dlg_return =
9194 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9195 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9196 m_FinishRouteOnKillFocus = true;
9197
9198 if (dlg_return == wxID_YES) {
9199 appending = true; // part of the other route will be
9200 // appended to the new route
9201 }
9202 }
9203 }
9204 }
9205
9206 // check all other routes to see if this point appears in any other
9207 // route If it appears in NO other route, then it should e
9208 // considered an isolated mark
9209 if (!FindRouteContainingWaypoint(pMousePoint))
9210 pMousePoint->SetShared(true);
9211 }
9212 }
9213
9214 if (NULL == pMousePoint) { // need a new point
9215 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9216 "", wxEmptyString);
9217 pMousePoint->SetNameShown(false);
9218
9219 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9220
9221 if (m_routeState > 1)
9222 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9223 Undo_IsOrphanded, NULL);
9224 }
9225
9226 if (m_routeState == 1) {
9227 // First point in the route.
9228 m_pMouseRoute->AddPoint(pMousePoint);
9229 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9230
9231 } else {
9232 if (m_pMouseRoute->m_NextLegGreatCircle) {
9233 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9234 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9235 &rhumbBearing, &rhumbDist);
9236 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9237 &gcDist, &gcBearing, NULL);
9238 double gcDistNM = gcDist / 1852.0;
9239
9240 // Empirically found expression to get reasonable route segments.
9241 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9242 pow(rhumbDist - gcDistNM - 1, 0.5);
9243
9244 wxString msg;
9245 msg << _("For this leg the Great Circle route is ")
9246 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9247 << _(" shorter than rhumbline.\n\n")
9248 << _("Would you like include the Great Circle routing points "
9249 "for this leg?");
9250
9251#ifndef __WXOSX__
9252 m_FinishRouteOnKillFocus = false;
9253 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9254 wxYES_NO | wxNO_DEFAULT);
9255 m_FinishRouteOnKillFocus = true;
9256#else
9257 int answer = wxID_NO;
9258#endif
9259
9260 if (answer == wxID_YES) {
9261 RoutePoint *gcPoint;
9262 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9263 wxRealPoint gcCoord;
9264
9265 for (int i = 1; i <= segmentCount; i++) {
9266 double fraction = (double)i * (1.0 / (double)segmentCount);
9267 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9268 gcDist * fraction, gcBearing,
9269 &gcCoord.x, &gcCoord.y, NULL);
9270
9271 if (i < segmentCount) {
9272 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9273 wxEmptyString);
9274 gcPoint->SetNameShown(false);
9275 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9276 gcPoint);
9277 } else {
9278 gcPoint = pMousePoint; // Last point, previously exsisting!
9279 }
9280
9281 m_pMouseRoute->AddPoint(gcPoint);
9282 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9283
9284 pSelect->AddSelectableRouteSegment(
9285 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9286 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9287 prevGcPoint = gcPoint;
9288 }
9289
9290 undo->CancelUndoableAction(true);
9291
9292 } else {
9293 m_pMouseRoute->AddPoint(pMousePoint);
9294 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9295 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9296 rlon, m_prev_pMousePoint,
9297 pMousePoint, m_pMouseRoute);
9298 undo->AfterUndoableAction(m_pMouseRoute);
9299 }
9300 } else {
9301 // Ordinary rhumblinesegment.
9302 m_pMouseRoute->AddPoint(pMousePoint);
9303 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9304
9305 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9306 rlon, m_prev_pMousePoint,
9307 pMousePoint, m_pMouseRoute);
9308 undo->AfterUndoableAction(m_pMouseRoute);
9309 }
9310 }
9311
9312 m_prev_rlat = rlat;
9313 m_prev_rlon = rlon;
9314 m_prev_pMousePoint = pMousePoint;
9315 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9316
9317 m_routeState++;
9318
9319 if (appending ||
9320 inserting) { // Appending a route or making a new route
9321 int connect = tail->GetIndexOf(pMousePoint);
9322 if (connect == 1) {
9323 inserting = false; // there is nothing to insert
9324 appending = true; // so append
9325 }
9326 int length = tail->GetnPoints();
9327
9328 int i;
9329 int start, stop;
9330 if (appending) {
9331 start = connect + 1;
9332 stop = length;
9333 } else { // inserting
9334 start = 1;
9335 stop = connect;
9336 m_pMouseRoute->RemovePoint(
9337 m_pMouseRoute
9338 ->GetLastPoint()); // Remove the first and only point
9339 }
9340 for (i = start; i <= stop; i++) {
9341 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9342 if (m_pMouseRoute)
9343 m_pMouseRoute->m_lastMousePointIndex =
9344 m_pMouseRoute->GetnPoints();
9345 m_routeState++;
9346 gFrame->RefreshAllCanvas();
9347 ret = true;
9348 }
9349 m_prev_rlat =
9350 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9351 m_prev_rlon =
9352 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9353 m_pMouseRoute->FinalizeForRendering();
9354 }
9355
9356 Refresh(true);
9357 ret = true;
9358 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9359 {
9360 if (m_bedge_pan) {
9361 m_bedge_pan = false;
9362 return false;
9363 }
9364
9365 if (m_ignore_next_leftup) {
9366 m_ignore_next_leftup = false;
9367 return false;
9368 }
9369
9370 if (m_nMeasureState == 1) {
9371 m_pMeasureRoute = new Route();
9372 pRouteList->push_back(m_pMeasureRoute);
9373 r_rband.x = x;
9374 r_rband.y = y;
9375 }
9376
9377 if (m_pMeasureRoute) {
9378 RoutePoint *pMousePoint =
9379 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9380 wxEmptyString, wxEmptyString);
9381 pMousePoint->m_bShowName = false;
9382
9383 m_pMeasureRoute->AddPoint(pMousePoint);
9384
9385 m_prev_rlat = m_cursor_lat;
9386 m_prev_rlon = m_cursor_lon;
9387 m_prev_pMousePoint = pMousePoint;
9388 m_pMeasureRoute->m_lastMousePointIndex =
9389 m_pMeasureRoute->GetnPoints();
9390
9391 m_nMeasureState++;
9392 } else {
9393 CancelMeasureRoute();
9394 }
9395
9396 Refresh(true);
9397 ret = true;
9398 } else {
9399 bool bSelectAllowed = true;
9400 if (NULL == g_pMarkInfoDialog) {
9401 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9402 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9403 bSelectAllowed = false;
9404
9405 // Avoid accidental selection of routepoint if last touchdown started
9406 // a significant chart drag operation
9407 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9408 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9409 significant_drag) ||
9410 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9411 significant_drag)) {
9412 bSelectAllowed = false;
9413 }
9414
9415 /*if this left up happens at the end of a route point dragging and if
9416 the cursor/thumb is on the draghandle icon, not on the point iself a new
9417 selection will select nothing and the drag will never be ended, so the
9418 legs around this point never selectable. At this step we don't need a
9419 new selection, just keep the previoulsly selected and dragged point */
9420 if (m_bRoutePoinDragging) bSelectAllowed = false;
9421
9422 if (bSelectAllowed) {
9423 bool b_was_editing_mark = m_bMarkEditing;
9424 bool b_was_editing_route = m_bRouteEditing;
9425 FindRoutePointsAtCursor(SelectRadius,
9426 true); // Possibly selecting a point in a
9427 // route for later dragging
9428
9429 /*route and a mark points in layer can't be dragged so should't be
9430 * selected and no draghandle icon*/
9431 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9432 m_pRoutePointEditTarget = NULL;
9433
9434 if (!b_was_editing_route) {
9435 if (m_pEditRouteArray) {
9436 b_startedit_route = true;
9437
9438 // Hide the track and route rollover during route point edit, not
9439 // needed, and may be confusing
9440 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9441 m_pTrackRolloverWin->IsActive(false);
9442 }
9443 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9444 m_pRouteRolloverWin->IsActive(false);
9445 }
9446
9447 wxRect pre_rect;
9448 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9449 ir++) {
9450 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9451 // Need to validate route pointer
9452 // Route may be gone due to drgging close to ownship with
9453 // "Delete On Arrival" state set, as in the case of
9454 // navigating to an isolated waypoint on a temporary route
9455 if (g_pRouteMan->IsRouteValid(pr)) {
9456 // pr->SetHiLite(50);
9457 wxRect route_rect;
9458 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9459 pre_rect.Union(route_rect);
9460 }
9461 }
9462 RefreshRect(pre_rect, true);
9463 }
9464 } else {
9465 b_startedit_route = false;
9466 }
9467
9468 // Mark editing in touch mode, left-up event.
9469 if (m_pRoutePointEditTarget) {
9470 if (b_was_editing_mark ||
9471 b_was_editing_route) { // kill previous hilight
9472 if (m_lastRoutePointEditTarget) {
9473 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9474 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9475 RoutePointGui(*m_lastRoutePointEditTarget)
9476 .EnableDragHandle(false);
9477 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9478 SELTYPE_DRAGHANDLE);
9479 }
9480 }
9481
9482 if (m_pRoutePointEditTarget) {
9483 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9484 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9485 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9486 wxPoint2DDouble dragHandlePoint =
9487 RoutePointGui(*m_pRoutePointEditTarget)
9488 .GetDragHandlePoint(this);
9489 pSelect->AddSelectablePoint(
9490 dragHandlePoint.m_y, dragHandlePoint.m_x,
9491 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9492 }
9493 } else { // Deselect everything
9494 if (m_lastRoutePointEditTarget) {
9495 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9496 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9497 RoutePointGui(*m_lastRoutePointEditTarget)
9498 .EnableDragHandle(false);
9499 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9500 SELTYPE_DRAGHANDLE);
9501
9502 // Clear any routes being edited, probably orphans
9503 wxArrayPtrVoid *lastEditRouteArray =
9505 m_lastRoutePointEditTarget);
9506 if (lastEditRouteArray) {
9507 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9508 ir++) {
9509 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9510 if (g_pRouteMan->IsRouteValid(pr)) {
9511 pr->m_bIsBeingEdited = false;
9512 }
9513 }
9514 delete lastEditRouteArray;
9515 }
9516 }
9517 }
9518
9519 // Do the refresh
9520
9521 if (g_bopengl) {
9522 InvalidateGL();
9523 Refresh(false);
9524 } else {
9525 if (m_lastRoutePointEditTarget) {
9526 wxRect wp_rect;
9527 RoutePointGui(*m_lastRoutePointEditTarget)
9528 .CalculateDCRect(m_dc_route, this, &wp_rect);
9529 RefreshRect(wp_rect, true);
9530 }
9531
9532 if (m_pRoutePointEditTarget) {
9533 wxRect wp_rect;
9534 RoutePointGui(*m_pRoutePointEditTarget)
9535 .CalculateDCRect(m_dc_route, this, &wp_rect);
9536 RefreshRect(wp_rect, true);
9537 }
9538 }
9539 }
9540 } // bSelectAllowed
9541
9542 // Check to see if there is a route or AIS target under the cursor
9543 // If so, start the rollover timer which creates the popup
9544 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9545 bool b_start_rollover = false;
9546 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9547 SelectItem *pFind = pSelectAIS->FindSelection(
9548 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9549 if (pFind) b_start_rollover = true;
9550 }
9551
9552 if (!b_start_rollover && !b_startedit_route) {
9553 SelectableItemList SelList = pSelect->FindSelectionList(
9554 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9555 for (SelectItem *pFindSel : SelList) {
9556 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9557 if (pr && pr->IsVisible()) {
9558 b_start_rollover = true;
9559 break;
9560 }
9561 } // while
9562 }
9563
9564 if (!b_start_rollover && !b_startedit_route) {
9565 SelectableItemList SelList = pSelect->FindSelectionList(
9566 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9567 for (SelectItem *pFindSel : SelList) {
9568 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9569 if (tr && tr->IsVisible()) {
9570 b_start_rollover = true;
9571 break;
9572 }
9573 } // while
9574 }
9575
9576 if (b_start_rollover)
9577 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9578 wxTIMER_ONE_SHOT);
9579 Route *tail = 0;
9580 Route *current = 0;
9581 bool appending = false;
9582 bool inserting = false;
9583 int connect = 0;
9584 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9585 // drag
9586 if (m_pRoutePointEditTarget) {
9587 // Check to see if there is a nearby point which may replace the
9588 // dragged one
9589 RoutePoint *pMousePoint = NULL;
9590
9591 int index_last;
9592 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9593 double nearby_radius_meters =
9594 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9595 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9596 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9597 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9598 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9599 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9600 bool duplicate =
9601 false; // ensure we won't create duplicate point in routes
9602 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9603 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9604 ir++) {
9605 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9606 if (pr && pr->pRoutePointList) {
9607 auto *list = pr->pRoutePointList;
9608 auto pos =
9609 std::find(list->begin(), list->end(), pNearbyPoint);
9610 if (pos != list->end()) {
9611 duplicate = true;
9612 break;
9613 }
9614 }
9615 }
9616 }
9617
9618 // Special case:
9619 // Allow "re-use" of a route's waypoints iff it is a simple
9620 // isolated route. This allows, for instance, creation of a closed
9621 // polygon route
9622 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9623
9624 if (!duplicate) {
9625 int dlg_return;
9626 dlg_return =
9627 OCPNMessageBox(this,
9628 _("Replace this RoutePoint by the nearby "
9629 "Waypoint?"),
9630 _("OpenCPN RoutePoint change"),
9631 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9632 if (dlg_return == wxID_YES) {
9633 /*double confirmation if the dragged point has been manually
9634 * created which can be important and could be deleted
9635 * unintentionally*/
9636
9637 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9638 pNearbyPoint);
9639 current =
9640 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9641
9642 if (tail && current && (tail != current)) {
9643 int dlg_return1;
9644 connect = tail->GetIndexOf(pNearbyPoint);
9645 int index_current_route =
9646 current->GetIndexOf(m_pRoutePointEditTarget);
9647 index_last = current->GetIndexOf(current->GetLastPoint());
9648 dlg_return1 = wxID_NO;
9649 if (index_last ==
9650 index_current_route) { // we are dragging the last
9651 // point of the route
9652 if (connect != tail->GetnPoints()) { // anything to do?
9653
9654 wxString dmsg(
9655 _("Last part of route to be appended to dragged "
9656 "route?"));
9657 if (connect == 1)
9658 dmsg =
9659 _("Full route to be appended to dragged route?");
9660
9661 dlg_return1 = OCPNMessageBox(
9662 this, dmsg, _("OpenCPN Route Create"),
9663 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9664 if (dlg_return1 == wxID_YES) {
9665 appending = true;
9666 }
9667 }
9668 } else if (index_current_route ==
9669 1) { // dragging the first point of the route
9670 if (connect != 1) { // anything to do?
9671
9672 wxString dmsg(
9673 _("First part of route to be inserted into dragged "
9674 "route?"));
9675 if (connect == tail->GetnPoints())
9676 dmsg = _(
9677 "Full route to be inserted into dragged route?");
9678
9679 dlg_return1 = OCPNMessageBox(
9680 this, dmsg, _("OpenCPN Route Create"),
9681 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9682 if (dlg_return1 == wxID_YES) {
9683 inserting = true;
9684 }
9685 }
9686 }
9687 }
9688
9689 if (m_pRoutePointEditTarget->IsShared()) {
9690 // dlg_return = wxID_NO;
9691 dlg_return = OCPNMessageBox(
9692 this,
9693 _("Do you really want to delete and replace this "
9694 "WayPoint") +
9695 "\n" + _("which has been created manually?"),
9696 ("OpenCPN RoutePoint warning"),
9697 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9698 }
9699 }
9700 if (dlg_return == wxID_YES) {
9701 pMousePoint = pNearbyPoint;
9702 if (pMousePoint->m_bIsolatedMark) {
9703 pMousePoint->SetShared(true);
9704 }
9705 pMousePoint->m_bIsolatedMark =
9706 false; // definitely no longer isolated
9707 pMousePoint->m_bIsInRoute = true;
9708 }
9709 }
9710 }
9711 }
9712 if (!pMousePoint)
9713 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9714
9715 if (m_pEditRouteArray) {
9716 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9717 ir++) {
9718 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9719 if (g_pRouteMan->IsRouteValid(pr)) {
9720 if (pMousePoint) { // remove the dragged point and insert the
9721 // nearby
9722 auto *list = pr->pRoutePointList;
9723 auto pos = std::find(list->begin(), list->end(),
9724 m_pRoutePointEditTarget);
9725
9726 pSelect->DeleteAllSelectableRoutePoints(pr);
9727 pSelect->DeleteAllSelectableRouteSegments(pr);
9728
9729 pr->pRoutePointList->insert(pos, pMousePoint);
9730 pos = std::find(list->begin(), list->end(),
9731 m_pRoutePointEditTarget);
9732 pr->pRoutePointList->erase(pos);
9733
9734 pSelect->AddAllSelectableRouteSegments(pr);
9735 pSelect->AddAllSelectableRoutePoints(pr);
9736 }
9737 pr->FinalizeForRendering();
9738 pr->UpdateSegmentDistances();
9739 if (m_bRoutePoinDragging) {
9740 // pConfig->UpdateRoute(pr);
9741 NavObj_dB::GetInstance().UpdateRoute(pr);
9742 }
9743 }
9744 }
9745 }
9746
9747 // Update the RouteProperties Dialog, if currently shown
9748 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9749 if (m_pEditRouteArray) {
9750 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9751 ir++) {
9752 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9753 if (g_pRouteMan->IsRouteValid(pr)) {
9754 if (pRoutePropDialog->GetRoute() == pr) {
9755 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9756 }
9757 /* cannot edit track points anyway
9758 else if ( ( NULL !=
9759 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9760 pTrackPropDialog->m_pTrack == pr ) {
9761 pTrackPropDialog->SetTrackAndUpdate(
9762 pr );
9763 }
9764 */
9765 }
9766 }
9767 }
9768 }
9769 if (pMousePoint) { // clear all about the dragged point
9770 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9771 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9772 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9773 // Hide mark properties dialog if open on the replaced point
9774 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9775 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9776 g_pMarkInfoDialog->Hide();
9777
9778 delete m_pRoutePointEditTarget;
9779 m_lastRoutePointEditTarget = NULL;
9780 m_pRoutePointEditTarget = NULL;
9781 undo->AfterUndoableAction(pMousePoint);
9782 undo->InvalidateUndo();
9783 }
9784 }
9785 }
9786
9787 else if (m_bMarkEditing) { // End of way point drag
9788 if (m_pRoutePointEditTarget)
9789 if (m_bRoutePoinDragging) {
9790 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9791 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9792 }
9793 }
9794
9795 if (m_pRoutePointEditTarget)
9796 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9797
9798 if (!m_pRoutePointEditTarget) {
9799 delete m_pEditRouteArray;
9800 m_pEditRouteArray = NULL;
9801 m_bRouteEditing = false;
9802 }
9803 m_bRoutePoinDragging = false;
9804
9805 if (appending) { // Appending to the route of which the last point is
9806 // dragged onto another route
9807
9808 // copy tail from connect until length to end of current after dragging
9809
9810 int length = tail->GetnPoints();
9811 for (int i = connect + 1; i <= length; i++) {
9812 current->AddPointAndSegment(tail->GetPoint(i), false);
9813 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9814 m_routeState++;
9815 gFrame->RefreshAllCanvas();
9816 ret = true;
9817 }
9818 current->FinalizeForRendering();
9819 current->m_bIsBeingEdited = false;
9820 FinishRoute();
9821 g_pRouteMan->DeleteRoute(tail);
9822 }
9823 if (inserting) {
9824 pSelect->DeleteAllSelectableRoutePoints(current);
9825 pSelect->DeleteAllSelectableRouteSegments(current);
9826 for (int i = 1; i < connect; i++) { // numbering in the tail route
9827 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9828 }
9829 pSelect->AddAllSelectableRouteSegments(current);
9830 pSelect->AddAllSelectableRoutePoints(current);
9831 current->FinalizeForRendering();
9832 current->m_bIsBeingEdited = false;
9833 g_pRouteMan->DeleteRoute(tail);
9834 }
9835
9836 // Update the RouteProperties Dialog, if currently shown
9837 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9838 if (m_pEditRouteArray) {
9839 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9840 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9841 if (g_pRouteMan->IsRouteValid(pr)) {
9842 if (pRoutePropDialog->GetRoute() == pr) {
9843 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9844 }
9845 }
9846 }
9847 }
9848 }
9849
9850 } // g_btouch
9851
9852 else { // !g_btouch
9853 if (m_bRouteEditing) { // End of RoutePoint drag
9854 Route *tail = 0;
9855 Route *current = 0;
9856 bool appending = false;
9857 bool inserting = false;
9858 int connect = 0;
9859 int index_last;
9860 if (m_pRoutePointEditTarget) {
9861 m_pRoutePointEditTarget->m_bBlink = false;
9862 // Check to see if there is a nearby point which may replace the
9863 // dragged one
9864 RoutePoint *pMousePoint = NULL;
9865 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9866 double nearby_radius_meters =
9867 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9868 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9869 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9870 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9871 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9872 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9873 bool duplicate = false; // don't create duplicate point in routes
9874 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9875 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9876 ir++) {
9877 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9878 if (pr && pr->pRoutePointList) {
9879 auto *list = pr->pRoutePointList;
9880 auto pos =
9881 std::find(list->begin(), list->end(), pNearbyPoint);
9882 if (pos != list->end()) {
9883 duplicate = true;
9884 break;
9885 }
9886 }
9887 }
9888 }
9889
9890 // Special case:
9891 // Allow "re-use" of a route's waypoints iff it is a simple
9892 // isolated route. This allows, for instance, creation of a closed
9893 // polygon route
9894 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9895
9896 if (!duplicate) {
9897 int dlg_return;
9898 dlg_return =
9899 OCPNMessageBox(this,
9900 _("Replace this RoutePoint by the nearby "
9901 "Waypoint?"),
9902 _("OpenCPN RoutePoint change"),
9903 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9904 if (dlg_return == wxID_YES) {
9905 /*double confirmation if the dragged point has been manually
9906 * created which can be important and could be deleted
9907 * unintentionally*/
9908 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9909 pNearbyPoint);
9910 current =
9911 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9912
9913 if (tail && current && (tail != current)) {
9914 int dlg_return1;
9915 connect = tail->GetIndexOf(pNearbyPoint);
9916 int index_current_route =
9917 current->GetIndexOf(m_pRoutePointEditTarget);
9918 index_last = current->GetIndexOf(current->GetLastPoint());
9919 dlg_return1 = wxID_NO;
9920 if (index_last ==
9921 index_current_route) { // we are dragging the last
9922 // point of the route
9923 if (connect != tail->GetnPoints()) { // anything to do?
9924
9925 wxString dmsg(
9926 _("Last part of route to be appended to dragged "
9927 "route?"));
9928 if (connect == 1)
9929 dmsg =
9930 _("Full route to be appended to dragged route?");
9931
9932 dlg_return1 = OCPNMessageBox(
9933 this, dmsg, _("OpenCPN Route Create"),
9934 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9935 if (dlg_return1 == wxID_YES) {
9936 appending = true;
9937 }
9938 }
9939 } else if (index_current_route ==
9940 1) { // dragging the first point of the route
9941 if (connect != 1) { // anything to do?
9942
9943 wxString dmsg(
9944 _("First part of route to be inserted into dragged "
9945 "route?"));
9946 if (connect == tail->GetnPoints())
9947 dmsg = _(
9948 "Full route to be inserted into dragged route?");
9949
9950 dlg_return1 = OCPNMessageBox(
9951 this, dmsg, _("OpenCPN Route Create"),
9952 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9953 if (dlg_return1 == wxID_YES) {
9954 inserting = true;
9955 }
9956 }
9957 }
9958 }
9959
9960 if (m_pRoutePointEditTarget->IsShared()) {
9961 dlg_return = wxID_NO;
9962 dlg_return = OCPNMessageBox(
9963 this,
9964 _("Do you really want to delete and replace this "
9965 "WayPoint") +
9966 "\n" + _("which has been created manually?"),
9967 ("OpenCPN RoutePoint warning"),
9968 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9969 }
9970 }
9971 if (dlg_return == wxID_YES) {
9972 pMousePoint = pNearbyPoint;
9973 if (pMousePoint->m_bIsolatedMark) {
9974 pMousePoint->SetShared(true);
9975 }
9976 pMousePoint->m_bIsolatedMark =
9977 false; // definitely no longer isolated
9978 pMousePoint->m_bIsInRoute = true;
9979 }
9980 }
9981 }
9982 }
9983 if (!pMousePoint)
9984 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9985
9986 if (m_pEditRouteArray) {
9987 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9988 ir++) {
9989 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9990 if (g_pRouteMan->IsRouteValid(pr)) {
9991 if (pMousePoint) { // replace dragged point by nearby one
9992 auto *list = pr->pRoutePointList;
9993 auto pos = std::find(list->begin(), list->end(),
9994 m_pRoutePointEditTarget);
9995
9996 pSelect->DeleteAllSelectableRoutePoints(pr);
9997 pSelect->DeleteAllSelectableRouteSegments(pr);
9998
9999 pr->pRoutePointList->insert(pos, pMousePoint);
10000 pos = std::find(list->begin(), list->end(),
10001 m_pRoutePointEditTarget);
10002 if (pos != list->end()) list->erase(pos);
10003 // pr->pRoutePointList->erase(pos + 1);
10004
10005 pSelect->AddAllSelectableRouteSegments(pr);
10006 pSelect->AddAllSelectableRoutePoints(pr);
10007 }
10008 pr->FinalizeForRendering();
10009 pr->UpdateSegmentDistances();
10010 pr->m_bIsBeingEdited = false;
10011
10012 if (m_bRoutePoinDragging) {
10013 // Special case optimization.
10014 // Dragging a single point of a route
10015 // without any point additions or re-ordering
10016 if (!pMousePoint)
10017 NavObj_dB::GetInstance().UpdateRoutePoint(
10018 m_pRoutePointEditTarget);
10019 else
10020 NavObj_dB::GetInstance().UpdateRoute(pr);
10021 }
10022 pr->SetHiLite(0);
10023 }
10024 }
10025 Refresh(false);
10026 }
10027
10028 if (appending) {
10029 // copy tail from connect until length to end of current after
10030 // dragging
10031
10032 int length = tail->GetnPoints();
10033 for (int i = connect + 1; i <= length; i++) {
10034 current->AddPointAndSegment(tail->GetPoint(i), false);
10035 if (current)
10036 current->m_lastMousePointIndex = current->GetnPoints();
10037 m_routeState++;
10038 gFrame->RefreshAllCanvas();
10039 ret = true;
10040 }
10041 current->FinalizeForRendering();
10042 current->m_bIsBeingEdited = false;
10043 FinishRoute();
10044 g_pRouteMan->DeleteRoute(tail);
10045 }
10046 if (inserting) {
10047 pSelect->DeleteAllSelectableRoutePoints(current);
10048 pSelect->DeleteAllSelectableRouteSegments(current);
10049 for (int i = 1; i < connect; i++) { // numbering in the tail route
10050 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10051 }
10052 pSelect->AddAllSelectableRouteSegments(current);
10053 pSelect->AddAllSelectableRoutePoints(current);
10054 current->FinalizeForRendering();
10055 current->m_bIsBeingEdited = false;
10056 g_pRouteMan->DeleteRoute(tail);
10057 }
10058
10059 // Update the RouteProperties Dialog, if currently shown
10060 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10061 if (m_pEditRouteArray) {
10062 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10063 ir++) {
10064 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10065 if (g_pRouteMan->IsRouteValid(pr)) {
10066 if (pRoutePropDialog->GetRoute() == pr) {
10067 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10068 }
10069 }
10070 }
10071 }
10072 }
10073
10074 if (pMousePoint) {
10075 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10076 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10077 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10078 // Hide mark properties dialog if open on the replaced point
10079 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10080 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10081 g_pMarkInfoDialog->Hide();
10082
10083 delete m_pRoutePointEditTarget;
10084 m_lastRoutePointEditTarget = NULL;
10085 undo->AfterUndoableAction(pMousePoint);
10086 undo->InvalidateUndo();
10087 } else {
10088 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10089 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10090
10091 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10092 }
10093
10094 delete m_pEditRouteArray;
10095 m_pEditRouteArray = NULL;
10096 }
10097
10098 InvalidateGL();
10099 m_bRouteEditing = false;
10100 m_pRoutePointEditTarget = NULL;
10101
10102 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10103 ret = true;
10104 }
10105
10106 else if (m_bMarkEditing) { // end of Waypoint drag
10107 if (m_pRoutePointEditTarget) {
10108 if (m_bRoutePoinDragging) {
10109 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10110 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10111 }
10112 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10113 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10114 if (!g_bopengl) {
10115 wxRect wp_rect;
10116 RoutePointGui(*m_pRoutePointEditTarget)
10117 .CalculateDCRect(m_dc_route, this, &wp_rect);
10118 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10119 RefreshRect(wp_rect, true);
10120 }
10121 }
10122 m_pRoutePointEditTarget = NULL;
10123 m_bMarkEditing = false;
10124 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10125 ret = true;
10126 }
10127
10128 else if (leftIsDown) { // left click for chart center
10129 leftIsDown = false;
10130 ret = false;
10131
10132 if (!g_btouch) {
10133 if (!m_bChartDragging && !m_bMeasure_Active) {
10134 } else {
10135 m_bChartDragging = false;
10136 }
10137 }
10138 }
10139 m_bRoutePoinDragging = false;
10140 } // !btouch
10141
10142 if (ret) return true;
10143 } // left up
10144
10145 if (event.RightDown()) {
10146 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10147 last_drag.x = mx;
10148 last_drag.y = my;
10149
10150 if (g_btouch) {
10151 // if( m_pRoutePointEditTarget )
10152 // return false;
10153 }
10154
10155 ret = true;
10156 m_FinishRouteOnKillFocus = false;
10157 CallPopupMenu(mx, my);
10158 m_FinishRouteOnKillFocus = true;
10159 } // Right down
10160
10161 return ret;
10162}
10163
10164bool panleftIsDown;
10165bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10166 // Skip all mouse processing if shift is held.
10167 // This allows plugins to implement shift+drag behaviors.
10168 if (event.ShiftDown()) {
10169 return false;
10170 }
10171 int x, y;
10172 event.GetPosition(&x, &y);
10173
10174 x *= m_displayScale;
10175 y *= m_displayScale;
10176
10177 // Check for wheel rotation
10178 // ideally, should be just longer than the time between
10179 // processing accumulated mouse events from the event queue
10180 // as would happen during screen redraws.
10181 int wheel_dir = event.GetWheelRotation();
10182
10183 if (wheel_dir) {
10184 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10185 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10186
10187 double factor = g_mouse_zoom_sensitivity;
10188 if (wheel_dir < 0) factor = 1 / factor;
10189
10190 if (g_bsmoothpanzoom) {
10191 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10192 if (wheel_dir == m_last_wheel_dir) {
10193 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10194 // m_zoom_target /= factor;
10195 } else
10196 StopMovement();
10197 } else {
10198 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10199 m_wheelstopwatch.Start(0);
10200 // m_zoom_target = VPoint.chart_scale / factor;
10201 }
10202 }
10203
10204 m_last_wheel_dir = wheel_dir;
10205
10206 ZoomCanvas(factor, true, false);
10207 }
10208
10209 if (event.LeftDown()) {
10210 // Skip the first left click if it will cause a canvas focus shift
10211 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10212 return false;
10213 }
10214
10215 last_drag.x = x, last_drag.y = y;
10216 panleftIsDown = true;
10217 }
10218
10219 if (event.LeftUp()) {
10220 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10221 // seen here.
10222 panleftIsDown = false;
10223
10224 if (!g_btouch) {
10225 if (!m_bChartDragging && !m_bMeasure_Active) {
10226 switch (cursor_region) {
10227 case MID_RIGHT: {
10228 PanCanvas(100, 0);
10229 break;
10230 }
10231
10232 case MID_LEFT: {
10233 PanCanvas(-100, 0);
10234 break;
10235 }
10236
10237 case MID_TOP: {
10238 PanCanvas(0, 100);
10239 break;
10240 }
10241
10242 case MID_BOT: {
10243 PanCanvas(0, -100);
10244 break;
10245 }
10246
10247 case CENTER: {
10248 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10249 break;
10250 }
10251 }
10252 } else {
10253 m_bChartDragging = false;
10254 }
10255 }
10256 }
10257 }
10258
10259 if (event.Dragging() && event.LeftIsDown()) {
10260 /*
10261 * fixed dragging.
10262 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10263 * before the drag event. Hence, as there is no mouse down event, last_drag
10264 * is not reset before the drag. And that results in one single drag
10265 * session, meaning you cannot drag the map a few miles north, lift your
10266 * finger, and the go even further north. Instead, the map resets itself
10267 * always to the very first drag start (since there is not reset of
10268 * last_drag).
10269 *
10270 * Besides, should not left down and dragging be enough of a situation to
10271 * start a drag procedure?
10272 *
10273 * Anyways, guarded it to be active in touch situations only.
10274 */
10275 if (g_btouch && !m_inPinch) {
10276 struct timespec now;
10277 clock_gettime(CLOCK_MONOTONIC, &now);
10278 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10279
10280 bool trigger_hold = false;
10281 if (false == m_bChartDragging) {
10282 if (m_DragTrigger < 0) {
10283 // printf("\ntrigger1\n");
10284 m_DragTrigger = 0;
10285 m_DragTriggerStartTime = tnow;
10286 trigger_hold = true;
10287 } else {
10288 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10289 m_DragTrigger = -1; // Reset trigger
10290 // printf("trigger fired\n");
10291 }
10292 }
10293 }
10294 if (trigger_hold) return true;
10295
10296 if (false == m_bChartDragging) {
10297 // printf("starting drag\n");
10298 // Reset drag calculation members
10299 last_drag.x = x - 1, last_drag.y = y - 1;
10300 m_bChartDragging = true;
10301 m_chart_drag_total_time = 0;
10302 m_chart_drag_total_x = 0;
10303 m_chart_drag_total_y = 0;
10304 m_inertia_last_drag_x = x;
10305 m_inertia_last_drag_y = y;
10306 m_drag_vec_x.clear();
10307 m_drag_vec_y.clear();
10308 m_drag_vec_t.clear();
10309 m_last_drag_time = tnow;
10310 }
10311
10312 // Calculate and store drag dynamics.
10313 uint64_t delta_t = tnow - m_last_drag_time;
10314 double delta_tf = delta_t / 1e9;
10315
10316 m_chart_drag_total_time += delta_tf;
10317 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10318 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10319
10320 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10321 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10322 m_drag_vec_t.push_back(delta_tf);
10323
10324 m_inertia_last_drag_x = x;
10325 m_inertia_last_drag_y = y;
10326 m_last_drag_time = tnow;
10327
10328 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10329 m_bChartDragging = true;
10330 StartTimedMovement();
10331 m_pan_drag.x += last_drag.x - x;
10332 m_pan_drag.y += last_drag.y - y;
10333 last_drag.x = x, last_drag.y = y;
10334 }
10335 } else if (!g_btouch) {
10336 if ((last_drag.x != x) || (last_drag.y != y)) {
10337 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10338 // dragging on route create.
10339 // github #2994
10340 m_bChartDragging = true;
10341 StartTimedMovement();
10342 m_pan_drag.x += last_drag.x - x;
10343 m_pan_drag.y += last_drag.y - y;
10344 last_drag.x = x, last_drag.y = y;
10345 }
10346 }
10347 }
10348
10349 // Handle some special cases
10350 if (g_btouch) {
10351 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10352 // deactivate next LeftUp to ovoid creating an unexpected point
10353 m_ignore_next_leftup = true;
10354 m_DoubleClickTimer->Start();
10355 singleClickEventIsValid = false;
10356 }
10357 }
10358 }
10359
10360 return true;
10361}
10362
10363void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10364 if (MouseEventOverlayWindows(event)) return;
10365
10366 if (MouseEventSetup(event)) return; // handled, no further action required
10367
10368 bool nm = MouseEventProcessObjects(event);
10369 if (!nm) MouseEventProcessCanvas(event);
10370}
10371
10372void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10373 // Switch to the appropriate cursor on mouse movement
10374
10375 wxCursor *ptarget_cursor = pCursorArrow;
10376 if (!pPlugIn_Cursor) {
10377 ptarget_cursor = pCursorArrow;
10378 if ((!m_routeState) &&
10379 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10380 if (cursor_region == MID_RIGHT) {
10381 ptarget_cursor = pCursorRight;
10382 } else if (cursor_region == MID_LEFT) {
10383 ptarget_cursor = pCursorLeft;
10384 } else if (cursor_region == MID_TOP) {
10385 ptarget_cursor = pCursorDown;
10386 } else if (cursor_region == MID_BOT) {
10387 ptarget_cursor = pCursorUp;
10388 } else {
10389 ptarget_cursor = pCursorArrow;
10390 }
10391 } else if (m_bMeasure_Active ||
10392 m_routeState) // If Measure tool use Pencil Cursor
10393 ptarget_cursor = pCursorPencil;
10394 } else {
10395 ptarget_cursor = pPlugIn_Cursor;
10396 }
10397
10398 SetCursor(*ptarget_cursor);
10399}
10400
10401void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10402 SetCursor(*pCursorArrow);
10403}
10404
10405void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10406 ChartPlugInWrapper *target_plugin_chart = NULL;
10407 s57chart *Chs57 = NULL;
10408 wxFileName file;
10409 wxArrayString files;
10410
10411 ChartBase *target_chart = GetChartAtCursor();
10412 if (target_chart) {
10413 file.Assign(target_chart->GetFullPath());
10414 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10415 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10416 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10417 else
10418 Chs57 = dynamic_cast<s57chart *>(target_chart);
10419 } else { // target_chart = null, might be mbtiles
10420 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10421 unsigned int im = stackIndexArray.size();
10422 int scale = 2147483647; // max 32b integer
10423 if (VPoint.b_quilt && im > 0) {
10424 for (unsigned int is = 0; is < im; is++) {
10425 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10426 CHART_TYPE_MBTILES) {
10427 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10428 double lat, lon;
10429 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10430 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10431 .GetBBox()
10432 .Contains(lat, lon)) {
10433 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10434 scale) {
10435 scale =
10436 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10437 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10438 }
10439 }
10440 }
10441 }
10442 }
10443 }
10444
10445 std::vector<Ais8_001_22 *> area_notices;
10446
10447 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10448 float vp_scale = GetVPScale();
10449
10450 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10451 auto target_data = target.second;
10452 if (!target_data->area_notices.empty()) {
10453 for (auto &ani : target_data->area_notices) {
10454 Ais8_001_22 &area_notice = ani.second;
10455
10456 BoundingBox bbox;
10457
10458 for (Ais8_001_22_SubAreaList::iterator sa =
10459 area_notice.sub_areas.begin();
10460 sa != area_notice.sub_areas.end(); ++sa) {
10461 switch (sa->shape) {
10462 case AIS8_001_22_SHAPE_CIRCLE: {
10463 wxPoint target_point;
10464 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10465 bbox.Expand(target_point);
10466 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10467 break;
10468 }
10469 case AIS8_001_22_SHAPE_RECT: {
10470 wxPoint target_point;
10471 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10472 bbox.Expand(target_point);
10473 if (sa->e_dim_m > sa->n_dim_m)
10474 bbox.EnLarge(sa->e_dim_m * vp_scale);
10475 else
10476 bbox.EnLarge(sa->n_dim_m * vp_scale);
10477 break;
10478 }
10479 case AIS8_001_22_SHAPE_POLYGON:
10480 case AIS8_001_22_SHAPE_POLYLINE: {
10481 for (int i = 0; i < 4; ++i) {
10482 double lat = sa->latitude;
10483 double lon = sa->longitude;
10484 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10485 &lat, &lon);
10486 wxPoint target_point;
10487 GetCanvasPointPix(lat, lon, &target_point);
10488 bbox.Expand(target_point);
10489 }
10490 break;
10491 }
10492 case AIS8_001_22_SHAPE_SECTOR: {
10493 double lat1 = sa->latitude;
10494 double lon1 = sa->longitude;
10495 double lat, lon;
10496 wxPoint target_point;
10497 GetCanvasPointPix(lat1, lon1, &target_point);
10498 bbox.Expand(target_point);
10499 for (int i = 0; i < 18; ++i) {
10500 ll_gc_ll(
10501 lat1, lon1,
10502 sa->left_bound_deg +
10503 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10504 sa->radius_m / 1852.0, &lat, &lon);
10505 GetCanvasPointPix(lat, lon, &target_point);
10506 bbox.Expand(target_point);
10507 }
10508 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10509 &lat, &lon);
10510 GetCanvasPointPix(lat, lon, &target_point);
10511 bbox.Expand(target_point);
10512 break;
10513 }
10514 }
10515 }
10516
10517 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10518 area_notices.push_back(&area_notice);
10519 }
10520 }
10521 }
10522 }
10523 }
10524
10525 if (target_chart || !area_notices.empty() || file.HasName()) {
10526 // Go get the array of all objects at the cursor lat/lon
10527 int sel_rad_pix = 5;
10528 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10529
10530 // Make sure we always get the lights from an object, even if we are
10531 // currently not displaying lights on the chart.
10532
10533 SetCursor(wxCURSOR_WAIT);
10534 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10535 if (!lightsVis) SetShowENCLights(true);
10536 ;
10537
10538 ListOfObjRazRules *rule_list = NULL;
10539 ListOfPI_S57Obj *pi_rule_list = NULL;
10540 if (Chs57)
10541 rule_list =
10542 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10543 else if (target_plugin_chart)
10544 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10545 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10546
10547 ListOfObjRazRules *overlay_rule_list = NULL;
10548 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10549 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10550
10551 if (CHs57_Overlay) {
10552 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10553 zlat, zlon, SelectRadius, &GetVP());
10554 }
10555
10556 if (!lightsVis) SetShowENCLights(false);
10557
10558 wxString objText;
10559 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10560 wxString face = dFont->GetFaceName();
10561
10562 if (NULL == g_pObjectQueryDialog) {
10564 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10565 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10566 }
10567
10568 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10569 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10570
10571#ifdef __WXOSX__
10572 // Auto Adjustment for dark mode
10573 fg = g_pObjectQueryDialog->GetForegroundColour();
10574#endif
10575
10576 objText.Printf(
10577 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10578 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10579
10580#ifdef __WXOSX__
10581 int points = dFont->GetPointSize();
10582#else
10583 int points = dFont->GetPointSize() + 1;
10584#endif
10585
10586 int sizes[7];
10587 for (int i = -2; i < 5; i++) {
10588 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10589 }
10590 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10591
10592 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10593
10594 if (overlay_rule_list && CHs57_Overlay) {
10595 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10596 objText << "<hr noshade>";
10597 }
10598
10599 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10600 an != area_notices.end(); ++an) {
10601 objText << "<b>AIS Area Notice:</b> ";
10602 objText << ais8_001_22_notice_names[(*an)->notice_type];
10603 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10604 (*an)->sub_areas.begin();
10605 sa != (*an)->sub_areas.end(); ++sa)
10606 if (!sa->text.empty()) objText << sa->text;
10607 objText << "<br>expires: " << (*an)->expiry_time.Format();
10608 objText << "<hr noshade>";
10609 }
10610
10611 if (Chs57)
10612 objText << Chs57->CreateObjDescriptions(rule_list);
10613 else if (target_plugin_chart)
10614 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10615 pi_rule_list);
10616
10617 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10618
10619 // Add the additional info files
10620 wxString AddFiles, filenameOK;
10621 int filecount = 0;
10622 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10623 // plugin
10624
10625 AddFiles = wxString::Format(
10626 "<hr noshade><br><b>Additional info files attached to: </b> "
10627 "<font "
10628 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10629 "cellpadding=3>",
10630 file.GetFullName());
10631 file.Normalize();
10632 file.Assign(file.GetPath(), "");
10633 wxDir dir(file.GetFullPath());
10634 wxString filename;
10635 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10636 while (cont) {
10637 file.Assign(dir.GetNameWithSep().append(filename));
10638 wxString FormatString =
10639 "<td valign=top><font size=-2><a "
10640 "href=\"%s\">%s</a></font></td>";
10641 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10642 filenameOK = file.GetFullPath(); // remember last valid name
10643 // we are making a 3 columns table. New row only every third file
10644 if (3 * ((int)filecount / 3) == filecount)
10645 FormatString.Prepend("<tr>"); // new row
10646 else
10647 FormatString.Prepend(
10648 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10649 // spacer column
10650
10651 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10652 file.GetFullName());
10653 filecount++;
10654 }
10655 cont = dir.GetNext(&filename);
10656 }
10657 objText << AddFiles << "</table>";
10658 }
10659 objText << "</font>";
10660 objText << "</body></html>";
10661
10662 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10663 g_pObjectQueryDialog->SetHTMLPage(objText);
10664 g_pObjectQueryDialog->Show();
10665 }
10666 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10667 // generate an event to avoid double code
10668 wxHtmlLinkInfo hli(filenameOK);
10669 wxHtmlLinkEvent hle(1, hli);
10670 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10671 }
10672
10673 if (rule_list) rule_list->Clear();
10674 delete rule_list;
10675
10676 if (overlay_rule_list) overlay_rule_list->Clear();
10677 delete overlay_rule_list;
10678
10679 if (pi_rule_list) pi_rule_list->Clear();
10680 delete pi_rule_list;
10681
10682 SetCursor(wxCURSOR_ARROW);
10683 }
10684}
10685
10686void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10687 bool bNew = false;
10688 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10689 // Dialog
10690 g_pMarkInfoDialog = new MarkInfoDlg(this);
10691 bNew = true;
10692 }
10693
10694 if (1 /*g_bresponsive*/) {
10695 wxSize canvas_size = GetSize();
10696
10697 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10698 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10699
10700 g_pMarkInfoDialog->Layout();
10701
10702 wxPoint canvas_pos = GetPosition();
10703 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10704
10705 bool newFit = false;
10706 if (canvas_size.x < fitted_size.x) {
10707 fitted_size.x = canvas_size.x - 40;
10708 if (canvas_size.y < fitted_size.y)
10709 fitted_size.y -= 40; // scrollbar added
10710 }
10711 if (canvas_size.y < fitted_size.y) {
10712 fitted_size.y = canvas_size.y - 40;
10713 if (canvas_size.x < fitted_size.x)
10714 fitted_size.x -= 40; // scrollbar added
10715 }
10716
10717 if (newFit) {
10718 g_pMarkInfoDialog->SetSize(fitted_size);
10719 g_pMarkInfoDialog->Centre();
10720 }
10721 }
10722
10723 markPoint->m_bRPIsBeingEdited = false;
10724
10725 wxString title_base = _("Mark Properties");
10726 if (markPoint->m_bIsInRoute) {
10727 title_base = _("Waypoint Properties");
10728 }
10729 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10730 g_pMarkInfoDialog->UpdateProperties();
10731 if (markPoint->m_bIsInLayer) {
10732 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10733 GetLayerName(markPoint->m_LayerID)));
10734 g_pMarkInfoDialog->SetDialogTitle(caption);
10735 } else
10736 g_pMarkInfoDialog->SetDialogTitle(title_base);
10737
10738 g_pMarkInfoDialog->Show();
10739 g_pMarkInfoDialog->Raise();
10740 g_pMarkInfoDialog->InitialFocus();
10741 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10742}
10743
10744void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10745 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10746 pRoutePropDialog->SetRouteAndUpdate(selected);
10747 // pNew->UpdateProperties();
10748 pRoutePropDialog->Show();
10749 pRoutePropDialog->Raise();
10750 return;
10751 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10752 this); // There is one global instance of the RouteProp Dialog
10753
10754 if (g_bresponsive) {
10755 wxSize canvas_size = GetSize();
10756 wxPoint canvas_pos = GetPosition();
10757 wxSize fitted_size = pRoutePropDialog->GetSize();
10758 ;
10759
10760 if (canvas_size.x < fitted_size.x) {
10761 fitted_size.x = canvas_size.x;
10762 if (canvas_size.y < fitted_size.y)
10763 fitted_size.y -= 20; // scrollbar added
10764 }
10765 if (canvas_size.y < fitted_size.y) {
10766 fitted_size.y = canvas_size.y;
10767 if (canvas_size.x < fitted_size.x)
10768 fitted_size.x -= 20; // scrollbar added
10769 }
10770
10771 pRoutePropDialog->SetSize(fitted_size);
10772 pRoutePropDialog->Centre();
10773
10774 // int xp = (canvas_size.x - fitted_size.x)/2;
10775 // int yp = (canvas_size.y - fitted_size.y)/2;
10776
10777 wxPoint xxp = ClientToScreen(canvas_pos);
10778 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10779 }
10780
10781 pRoutePropDialog->SetRouteAndUpdate(selected);
10782
10783 pRoutePropDialog->Show();
10784
10785 Refresh(false);
10786}
10787
10788void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10789 pTrackPropDialog = TrackPropDlg::getInstance(
10790 this); // There is one global instance of the RouteProp Dialog
10791
10792 pTrackPropDialog->SetTrackAndUpdate(selected);
10794
10795 pTrackPropDialog->Show();
10796
10797 Refresh(false);
10798}
10799
10800void pupHandler_PasteWaypoint() {
10801 Kml kml;
10802
10803 int pasteBuffer = kml.ParsePasteBuffer();
10804 RoutePoint *pasted = kml.GetParsedRoutePoint();
10805 if (!pasted) return;
10806
10807 double nearby_radius_meters =
10808 g_Platform->GetSelectRadiusPix() /
10809 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10810
10811 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10812 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10813
10814 int answer = wxID_NO;
10815 if (nearPoint && !nearPoint->m_bIsInLayer) {
10816 wxString msg;
10817 msg << _(
10818 "There is an existing waypoint at the same location as the one you are "
10819 "pasting. Would you like to merge the pasted data with it?\n\n");
10820 msg << _("Answering 'No' will create a new waypoint at the same location.");
10821 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10822 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10823 }
10824
10825 if (answer == wxID_YES) {
10826 nearPoint->SetName(pasted->GetName());
10827 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10828 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10829 pRouteManagerDialog->UpdateWptListCtrl();
10830 }
10831
10832 if (answer == wxID_NO) {
10833 RoutePoint *newPoint = new RoutePoint(pasted);
10834 newPoint->m_bIsolatedMark = true;
10835 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10836 newPoint);
10837 // pConfig->AddNewWayPoint(newPoint, -1);
10838 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10839
10840 pWayPointMan->AddRoutePoint(newPoint);
10841 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10842 pRouteManagerDialog->UpdateWptListCtrl();
10843 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10844 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10845 }
10846
10847 gFrame->InvalidateAllGL();
10848 gFrame->RefreshAllCanvas(false);
10849}
10850
10851void pupHandler_PasteRoute() {
10852 Kml kml;
10853
10854 int pasteBuffer = kml.ParsePasteBuffer();
10855 Route *pasted = kml.GetParsedRoute();
10856 if (!pasted) return;
10857
10858 double nearby_radius_meters =
10859 g_Platform->GetSelectRadiusPix() /
10860 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10861
10862 RoutePoint *curPoint;
10863 RoutePoint *nearPoint;
10864 RoutePoint *prevPoint = NULL;
10865
10866 bool mergepoints = false;
10867 bool createNewRoute = true;
10868 int existingWaypointCounter = 0;
10869
10870 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10871 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10872 nearPoint = pWayPointMan->GetNearbyWaypoint(
10873 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10874 if (nearPoint) {
10875 mergepoints = true;
10876 existingWaypointCounter++;
10877 // Small hack here to avoid both extending RoutePoint and repeating all
10878 // the GetNearbyWaypoint calculations. Use existin data field in
10879 // RoutePoint as temporary storage.
10880 curPoint->m_bPtIsSelected = true;
10881 }
10882 }
10883
10884 int answer = wxID_NO;
10885 if (mergepoints) {
10886 wxString msg;
10887 msg << _(
10888 "There are existing waypoints at the same location as some of the ones "
10889 "you are pasting. Would you like to just merge the pasted data into "
10890 "them?\n\n");
10891 msg << _("Answering 'No' will create all new waypoints for this route.");
10892 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10893 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10894
10895 if (answer == wxID_CANCEL) {
10896 return;
10897 }
10898 }
10899
10900 // If all waypoints exist since before, and a route with the same name, we
10901 // don't create a new route.
10902 if (mergepoints && answer == wxID_YES &&
10903 existingWaypointCounter == pasted->GetnPoints()) {
10904 for (Route *proute : *pRouteList) {
10905 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10906 createNewRoute = false;
10907 break;
10908 }
10909 }
10910 }
10911
10912 Route *newRoute = 0;
10913 RoutePoint *newPoint = 0;
10914
10915 if (createNewRoute) {
10916 newRoute = new Route();
10917 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10918 }
10919
10920 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10921 curPoint = pasted->GetPoint(i);
10922 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10923 curPoint->m_bPtIsSelected = false;
10924 newPoint = pWayPointMan->GetNearbyWaypoint(
10925 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10926 newPoint->SetName(curPoint->GetName());
10927 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10928
10929 if (createNewRoute) newRoute->AddPoint(newPoint);
10930 } else {
10931 curPoint->m_bPtIsSelected = false;
10932
10933 newPoint = new RoutePoint(curPoint);
10934 newPoint->m_bIsolatedMark = false;
10935 newPoint->SetIconName("circle");
10936 newPoint->m_bIsVisible = true;
10937 newPoint->m_bShowName = false;
10938 newPoint->SetShared(false);
10939
10940 newRoute->AddPoint(newPoint);
10941 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10942 newPoint);
10943 // pConfig->AddNewWayPoint(newPoint, -1);
10944 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10945 pWayPointMan->AddRoutePoint(newPoint);
10946 }
10947 if (i > 1 && createNewRoute)
10948 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10949 curPoint->m_lat, curPoint->m_lon,
10950 prevPoint, newPoint, newRoute);
10951 prevPoint = newPoint;
10952 }
10953
10954 if (createNewRoute) {
10955 pRouteList->push_back(newRoute);
10956 // pConfig->AddNewRoute(newRoute); // use auto next num
10957 NavObj_dB::GetInstance().InsertRoute(newRoute);
10958
10959 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10960 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10961 }
10962
10963 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10964 pRouteManagerDialog->UpdateRouteListCtrl();
10965 pRouteManagerDialog->UpdateWptListCtrl();
10966 }
10967 gFrame->InvalidateAllGL();
10968 gFrame->RefreshAllCanvas(false);
10969 }
10970 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10971 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10972}
10973
10974void pupHandler_PasteTrack() {
10975 Kml kml;
10976
10977 int pasteBuffer = kml.ParsePasteBuffer();
10978 Track *pasted = kml.GetParsedTrack();
10979 if (!pasted) return;
10980
10981 TrackPoint *curPoint;
10982
10983 Track *newTrack = new Track();
10984 TrackPoint *newPoint;
10985 TrackPoint *prevPoint = NULL;
10986
10987 newTrack->SetName(pasted->GetName());
10988
10989 for (int i = 0; i < pasted->GetnPoints(); i++) {
10990 curPoint = pasted->GetPoint(i);
10991
10992 newPoint = new TrackPoint(curPoint);
10993
10994 wxDateTime now = wxDateTime::Now();
10995 newPoint->SetCreateTime(curPoint->GetCreateTime());
10996
10997 newTrack->AddPoint(newPoint);
10998
10999 if (prevPoint)
11000 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11001 newPoint->m_lat, newPoint->m_lon,
11002 prevPoint, newPoint, newTrack);
11003
11004 prevPoint = newPoint;
11005 }
11006
11007 g_TrackList.push_back(newTrack);
11008 // pConfig->AddNewTrack(newTrack);
11009 NavObj_dB::GetInstance().InsertTrack(newTrack);
11010
11011 gFrame->InvalidateAllGL();
11012 gFrame->RefreshAllCanvas(false);
11013}
11014
11015bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11016 wxJSONValue v;
11017 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11018 v["CursorPosition_x"] = x;
11019 v["CursorPosition_y"] = y;
11020 // Send a limited set of selection types depending on what is
11021 // found under the mouse point.
11022 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11023 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11024 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11025
11026 wxJSONWriter w;
11027 wxString out;
11028 w.Write(v, out);
11029 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11030
11031 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11032
11033#if 0
11034#define SELTYPE_UNKNOWN 0x0001
11035#define SELTYPE_ROUTEPOINT 0x0002
11036#define SELTYPE_ROUTESEGMENT 0x0004
11037#define SELTYPE_TIDEPOINT 0x0008
11038#define SELTYPE_CURRENTPOINT 0x0010
11039#define SELTYPE_ROUTECREATE 0x0020
11040#define SELTYPE_AISTARGET 0x0040
11041#define SELTYPE_MARKPOINT 0x0080
11042#define SELTYPE_TRACKSEGMENT 0x0100
11043#define SELTYPE_DRAGHANDLE 0x0200
11044#endif
11045
11046 if (g_bhide_context_menus) return true;
11047 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11048 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11049 m_pIDXCandidate, m_nmea_log);
11050
11051 Connect(
11052 wxEVT_COMMAND_MENU_SELECTED,
11053 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11054
11055#ifdef __WXGTK__
11056 // Funny requirement here for gtk, to clear the menu trigger event
11057 // TODO
11058 // Causes a slight "flasH" of the menu,
11059 if (m_inLongPress) {
11060 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11061 m_inLongPress = false;
11062 }
11063#endif
11064
11065 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11066
11067 Disconnect(
11068 wxEVT_COMMAND_MENU_SELECTED,
11069 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11070
11071 delete m_canvasMenu;
11072 m_canvasMenu = NULL;
11073
11074#ifdef __WXQT__
11075 // gFrame->SurfaceToolbar();
11076 // g_MainToolbar->Raise();
11077#endif
11078
11079 return true;
11080}
11081
11082void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11083 // Pass menu events from the canvas to the menu handler
11084 // This is necessarily in ChartCanvas since that is the menu's parent.
11085 if (m_canvasMenu) {
11086 m_canvasMenu->PopupMenuHandler(event);
11087 }
11088 return;
11089}
11090
11091void ChartCanvas::StartRoute() {
11092 // Do not allow more than one canvas to create a route at one time.
11093 if (g_brouteCreating) return;
11094
11095 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11096
11097 g_brouteCreating = true;
11098 m_routeState = 1;
11099 m_bDrawingRoute = false;
11100 SetCursor(*pCursorPencil);
11101 // SetCanvasToolbarItemState(ID_ROUTE, true);
11102 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11103
11104 HideGlobalToolbar();
11105
11106#ifdef __ANDROID__
11107 androidSetRouteAnnunciator(true);
11108#endif
11109}
11110
11111wxString ChartCanvas::FinishRoute() {
11112 m_routeState = 0;
11113 m_prev_pMousePoint = NULL;
11114 m_bDrawingRoute = false;
11115 wxString rv = "";
11116 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11117
11118 // SetCanvasToolbarItemState(ID_ROUTE, false);
11119 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11120#ifdef __ANDROID__
11121 androidSetRouteAnnunciator(false);
11122#endif
11123
11124 SetCursor(*pCursorArrow);
11125
11126 if (m_pMouseRoute) {
11127 if (m_bAppendingRoute) {
11128 // pConfig->UpdateRoute(m_pMouseRoute);
11129 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11130 } else {
11131 if (m_pMouseRoute->GetnPoints() > 1) {
11132 // pConfig->AddNewRoute(m_pMouseRoute);
11133 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11134 } else {
11135 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11136 m_pMouseRoute = NULL;
11137 }
11138 }
11139 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11140
11141 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11142 (pRoutePropDialog->IsShown())) {
11143 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11144 }
11145
11146 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11147 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11148 pRouteManagerDialog->UpdateRouteListCtrl();
11149 }
11150 }
11151 m_bAppendingRoute = false;
11152 m_pMouseRoute = NULL;
11153
11154 m_pSelectedRoute = NULL;
11155
11156 undo->InvalidateUndo();
11157 gFrame->RefreshAllCanvas(true);
11158
11159 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11160
11161 ShowGlobalToolbar();
11162
11163 g_brouteCreating = false;
11164
11165 return rv;
11166}
11167
11168void ChartCanvas::HideGlobalToolbar() {
11169 if (m_canvasIndex == 0) {
11170 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11171 }
11172}
11173
11174void ChartCanvas::ShowGlobalToolbar() {
11175 if (m_canvasIndex == 0) {
11176 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11177 }
11178}
11179
11180void ChartCanvas::ShowAISTargetList() {
11181 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11182 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11183 }
11184
11185 g_pAISTargetList->UpdateAISTargetList();
11186}
11187
11188void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11189 if (!m_bShowOutlines) return;
11190
11191 if (!ChartData) return;
11192
11193 int nEntry = ChartData->GetChartTableEntries();
11194
11195 for (int i = 0; i < nEntry; i++) {
11196 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11197
11198 // Check to see if the candidate chart is in the currently active group
11199 bool b_group_draw = false;
11200 if (m_groupIndex > 0) {
11201 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11202 int index = pt->GetGroupArray()[ig];
11203 if (m_groupIndex == index) {
11204 b_group_draw = true;
11205 break;
11206 }
11207 }
11208 } else
11209 b_group_draw = true;
11210
11211 if (b_group_draw) RenderChartOutline(dc, i, vp);
11212 }
11213
11214 // On CM93 Composite Charts, draw the outlines of the next smaller
11215 // scale cell
11216 cm93compchart *pcm93 = NULL;
11217 if (VPoint.b_quilt) {
11218 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11219 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11220 pcm93 = (cm93compchart *)pch;
11221 break;
11222 }
11223 } else if (m_singleChart &&
11224 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11225 pcm93 = (cm93compchart *)m_singleChart;
11226
11227 if (pcm93) {
11228 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11229 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11230
11231 if (zoom_factor > 8.0) {
11232 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11233 dc.SetPen(mPen);
11234 } else {
11235 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11236 dc.SetPen(mPen);
11237 }
11238
11239 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11240 }
11241}
11242
11243void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11244#ifdef ocpnUSE_GL
11245 if (g_bopengl && m_glcc) {
11246 /* opengl version specially optimized */
11247 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11248 return;
11249 }
11250#endif
11251
11252 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11253 if (!ChartData->IsChartAvailable(dbIndex)) return;
11254 }
11255
11256 float plylat, plylon;
11257 float plylat1, plylon1;
11258
11259 int pixx, pixy, pixx1, pixy1;
11260
11261 LLBBox box;
11262 ChartData->GetDBBoundingBox(dbIndex, box);
11263
11264 // Don't draw an outline in the case where the chart covers the entire world
11265 // */
11266 if (box.GetLonRange() == 360) return;
11267
11268 double lon_bias = 0;
11269 // chart is outside of viewport lat/lon bounding box
11270 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11271
11272 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11273
11274 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11275 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11276
11277 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11278 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11279
11280 else
11281 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11282
11283 // Are there any aux ply entries?
11284 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11285 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11286 {
11287 wxPoint r, r1;
11288
11289 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11290 plylon += lon_bias;
11291
11292 GetCanvasPointPix(plylat, plylon, &r);
11293 pixx = r.x;
11294 pixy = r.y;
11295
11296 for (int i = 0; i < nPly - 1; i++) {
11297 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11298 plylon1 += lon_bias;
11299
11300 GetCanvasPointPix(plylat1, plylon1, &r1);
11301 pixx1 = r1.x;
11302 pixy1 = r1.y;
11303
11304 int pixxs1 = pixx1;
11305 int pixys1 = pixy1;
11306
11307 bool b_skip = false;
11308
11309 if (vp.chart_scale > 5e7) {
11310 // calculate projected distance between these two points in meters
11311 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11312 pow((double)(pixy1 - pixy), 2)) /
11313 vp.view_scale_ppm;
11314
11315 if (dist > 0.0) {
11316 // calculate GC distance between these two points in meters
11317 double distgc =
11318 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11319
11320 // If the distances are nonsense, it means that the scale is very
11321 // small and the segment wrapped the world So skip it....
11322 // TODO improve this to draw two segments
11323 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11324 b_skip = true;
11325 } else
11326 b_skip = true;
11327 }
11328
11329 ClipResult res = cohen_sutherland_line_clip_i(
11330 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11331 if (res != Invisible && !b_skip)
11332 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11333
11334 plylat = plylat1;
11335 plylon = plylon1;
11336 pixx = pixxs1;
11337 pixy = pixys1;
11338 }
11339
11340 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11341 plylon1 += lon_bias;
11342
11343 GetCanvasPointPix(plylat1, plylon1, &r1);
11344 pixx1 = r1.x;
11345 pixy1 = r1.y;
11346
11347 ClipResult res = cohen_sutherland_line_clip_i(
11348 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11349 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11350 }
11351
11352 else // Use Aux PlyPoints
11353 {
11354 wxPoint r, r1;
11355
11356 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11357 for (int j = 0; j < nAuxPlyEntries; j++) {
11358 int nAuxPly =
11359 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11360 GetCanvasPointPix(plylat, plylon, &r);
11361 pixx = r.x;
11362 pixy = r.y;
11363
11364 for (int i = 0; i < nAuxPly - 1; i++) {
11365 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11366
11367 GetCanvasPointPix(plylat1, plylon1, &r1);
11368 pixx1 = r1.x;
11369 pixy1 = r1.y;
11370
11371 int pixxs1 = pixx1;
11372 int pixys1 = pixy1;
11373
11374 bool b_skip = false;
11375
11376 if (vp.chart_scale > 5e7) {
11377 // calculate projected distance between these two points in meters
11378 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11379 ((pixy1 - pixy) * (pixy1 - pixy))) /
11380 vp.view_scale_ppm;
11381 if (dist > 0.0) {
11382 // calculate GC distance between these two points in meters
11383 double distgc =
11384 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11385
11386 // If the distances are nonsense, it means that the scale is very
11387 // small and the segment wrapped the world So skip it....
11388 // TODO improve this to draw two segments
11389 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11390 b_skip = true;
11391 } else
11392 b_skip = true;
11393 }
11394
11395 ClipResult res = cohen_sutherland_line_clip_i(
11396 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11397 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11398
11399 plylat = plylat1;
11400 plylon = plylon1;
11401 pixx = pixxs1;
11402 pixy = pixys1;
11403 }
11404
11405 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11406 GetCanvasPointPix(plylat1, plylon1, &r1);
11407 pixx1 = r1.x;
11408 pixy1 = r1.y;
11409
11410 ClipResult res = cohen_sutherland_line_clip_i(
11411 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11412 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11413 }
11414 }
11415}
11416
11417static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11418 const wxArrayString &legend) {
11419 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11420
11421 int pointsize = dFont->GetPointSize();
11422 pointsize /= OCPN_GetWinDIPScaleFactor();
11423
11424 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11425 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11426 false, dFont->GetFaceName());
11427
11428 dc.SetFont(*psRLI_font);
11429
11430 int h = 0;
11431 int w = 0;
11432 int hl, wl;
11433
11434 int xp, yp;
11435 int hilite_offset = 3;
11436
11437 for (wxString line : legend) {
11438#ifdef __WXMAC__
11439 wxScreenDC sdc;
11440 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11441#else
11442 dc.GetTextExtent(line, &wl, &hl);
11443 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11444 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11445#endif
11446 h += hl;
11447 w = wxMax(w, wl);
11448 }
11449 w += (hl / 2); // Add a little right pad
11450
11451 xp = ref_point.x - w;
11452 yp = ref_point.y;
11453 yp += hilite_offset;
11454
11455 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11456
11457 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11458 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11459
11460 for (wxString line : legend) {
11461 dc.DrawText(line, xp, yp);
11462 yp += hl;
11463 }
11464}
11465
11466void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11467 if (!g_bAllowShipToActive) return;
11468
11469 Route *rt = g_pRouteMan->GetpActiveRoute();
11470 if (!rt) return;
11471
11472 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11473 wxPoint2DDouble pa, pb;
11475 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11476
11477 // set pen
11478 int width =
11479 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11480 if (rt->m_width != wxPENSTYLE_INVALID)
11481 width = rt->m_width; // set route pen style if any
11482 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11483 g_shipToActiveStyle, 5)]; // get setting pen style
11484 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11485 wxColour color =
11486 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11487 : // set setting route pen color
11488 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11489 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11490
11491 dc.SetPen(*mypen);
11492 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11493
11494 if (!Use_Opengl)
11495 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11496 (int)pb.m_y, GetVP(), true);
11497
11498#ifdef ocpnUSE_GL
11499 else {
11500#ifdef USE_ANDROID_GLES2
11501 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11502#else
11503 if (style != wxPENSTYLE_SOLID) {
11504 if (glChartCanvas::dash_map.find(style) !=
11505 glChartCanvas::dash_map.end()) {
11506 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11507 dc.SetPen(*mypen);
11508 }
11509 }
11510 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11511#endif
11512
11513 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11514 (int)pb.m_x, (int)pb.m_y, GetVP());
11515 }
11516#endif
11517 }
11518}
11519
11520void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11521 Route *route = 0;
11522 if (m_routeState >= 2) route = m_pMouseRoute;
11523 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11524 route = m_pMeasureRoute;
11525
11526 if (!route) return;
11527
11528 // Validate route pointer
11529 if (!g_pRouteMan->IsRouteValid(route)) return;
11530
11531 double render_lat = m_cursor_lat;
11532 double render_lon = m_cursor_lon;
11533
11534 int np = route->GetnPoints();
11535 if (np) {
11536 if (g_btouch && (np > 1)) np--;
11537 RoutePoint rp = route->GetPoint(np);
11538 render_lat = rp.m_lat;
11539 render_lon = rp.m_lon;
11540 }
11541
11542 double rhumbBearing, rhumbDist;
11543 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11544 &rhumbBearing, &rhumbDist);
11545 double brg = rhumbBearing;
11546 double dist = rhumbDist;
11547
11548 // Skip GreatCircle rubberbanding on touch devices.
11549 if (!g_btouch) {
11550 double gcBearing, gcBearing2, gcDist;
11551 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11552 m_cursor_lat, &gcDist, &gcBearing,
11553 &gcBearing2);
11554 double gcDistm = gcDist / 1852.0;
11555
11556 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11557 rhumbBearing = 90.;
11558
11559 wxPoint destPoint, lastPoint;
11560
11561 route->m_NextLegGreatCircle = false;
11562 int milesDiff = rhumbDist - gcDistm;
11563 if (milesDiff > 1) {
11564 brg = gcBearing;
11565 dist = gcDistm;
11566 route->m_NextLegGreatCircle = true;
11567 }
11568
11569 // FIXME (MacOS, the first segment is rendered wrong)
11570 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11571 &lastPoint);
11572
11573 if (route->m_NextLegGreatCircle) {
11574 for (int i = 1; i <= milesDiff; i++) {
11575 double p = (double)i * (1.0 / (double)milesDiff);
11576 double pLat, pLon;
11577 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11578 &pLon, &pLat, &gcBearing2);
11579 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11580 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11581 false);
11582 lastPoint = destPoint;
11583 }
11584 } else {
11585 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11586 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11587 false);
11588 if (m_bMeasure_DistCircle) {
11589 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11590 powf((float)(r_rband.y - lastPoint.y), 2));
11591
11592 dc.SetPen(*g_pRouteMan->GetRoutePen());
11593 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11594 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11595 }
11596 }
11597 }
11598 }
11599
11600 wxString routeInfo;
11601 wxArrayString infoArray;
11602 double varBrg = 0;
11603 if (g_bShowTrue)
11604 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11605 0x00B0);
11606
11607 if (g_bShowMag) {
11608 double latAverage = (m_cursor_lat + render_lat) / 2;
11609 double lonAverage = (m_cursor_lon + render_lon) / 2;
11610 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11611
11612 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11613 (int)varBrg, 0x00B0);
11614 }
11615 routeInfo << " " << FormatDistanceAdaptive(dist);
11616 infoArray.Add(routeInfo);
11617 routeInfo.Clear();
11618
11619 // To make it easier to use a route as a bearing on a charted object add for
11620 // the first leg also the reverse bearing.
11621 if (np == 1) {
11622 routeInfo << "Reverse: ";
11623 if (g_bShowTrue)
11624 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11625 (int)(brg + 180.) % 360, 0x00B0);
11626 if (g_bShowMag)
11627 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11628 (int)(varBrg + 180.) % 360, 0x00B0);
11629 infoArray.Add(routeInfo);
11630 routeInfo.Clear();
11631 }
11632
11633 wxString s0;
11634 if (!route->m_bIsInLayer)
11635 s0.Append(_("Route") + ": ");
11636 else
11637 s0.Append(_("Layer Route: "));
11638
11639 double disp_length = route->m_route_length;
11640 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11641 s0 += FormatDistanceAdaptive(disp_length);
11642
11643 infoArray.Add(s0);
11644 routeInfo.Clear();
11645
11646 RouteLegInfo(dc, r_rband, infoArray);
11647
11648 m_brepaint_piano = true;
11649}
11650
11651void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11652 if (!m_bShowVisibleSectors) return;
11653
11654 if (g_bDeferredInitDone) {
11655 // need to re-evaluate sectors?
11656 double rhumbBearing, rhumbDist;
11657 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11658 &rhumbBearing, &rhumbDist);
11659
11660 if (rhumbDist > 0.05) // miles
11661 {
11662 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11663 m_sectorlegsVisible);
11664 m_sector_glat = gLat;
11665 m_sector_glon = gLon;
11666 }
11667 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11668 }
11669}
11670
11671void ChartCanvas::WarpPointerDeferred(int x, int y) {
11672 warp_x = x;
11673 warp_y = y;
11674 warp_flag = true;
11675}
11676
11677int s_msg;
11678
11679void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11680 if (!ps52plib) return;
11681
11682 if (VPoint.b_quilt) { // quilted
11683 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11684
11685 if (m_pQuilt->IsQuiltVector()) {
11686 if (ps52plib->GetStateHash() != m_s52StateHash) {
11687 UpdateS52State();
11688 m_s52StateHash = ps52plib->GetStateHash();
11689 }
11690 }
11691 } else {
11692 if (ps52plib->GetStateHash() != m_s52StateHash) {
11693 UpdateS52State();
11694 m_s52StateHash = ps52plib->GetStateHash();
11695 }
11696 }
11697
11698 // Plugin charts
11699 bool bSendPlibState = true;
11700 if (VPoint.b_quilt) { // quilted
11701 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11702 }
11703
11704 if (bSendPlibState) {
11705 wxJSONValue v;
11706 v["OpenCPN Version Major"] = VERSION_MAJOR;
11707 v["OpenCPN Version Minor"] = VERSION_MINOR;
11708 v["OpenCPN Version Patch"] = VERSION_PATCH;
11709 v["OpenCPN Version Date"] = VERSION_DATE;
11710 v["OpenCPN Version Full"] = VERSION_FULL;
11711
11712 // S52PLIB state
11713 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11714 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11715 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11716 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11717 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11718 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11719 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11720
11721 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11722
11723 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11724 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11725
11726 // Global S52 options
11727
11728 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11729 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11730 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11731 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11732 ps52plib->m_bShowS57ImportantTextOnly;
11733 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11734 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11735 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11736 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11737 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11738
11739 // Some global GUI parameters, for completeness
11740 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11741 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11742 v["OpenCPN Scale Factor Exp"] =
11743 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11744 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11745
11746 wxJSONWriter w;
11747 wxString out;
11748 w.Write(v, out);
11749
11750 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11751 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11752 g_lastS52PLIBPluginMessage = out;
11753 }
11754 }
11755}
11756int spaint;
11757int s_in_update;
11758void ChartCanvas::OnPaint(wxPaintEvent &event) {
11759 wxPaintDC dc(this);
11760
11761 // GetToolbar()->Show( m_bToolbarEnable );
11762
11763 // Paint updates may have been externally disabled (temporarily, to avoid
11764 // Yield() recursion performance loss) It is important that the wxPaintDC is
11765 // built, even if we elect to not process this paint message. Otherwise, the
11766 // paint message may not be removed from the message queue, esp on Windows.
11767 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11768
11769 if (!m_b_paint_enable) {
11770 return;
11771 }
11772
11773 // If necessary, reconfigure the S52 PLIB
11775
11776#ifdef ocpnUSE_GL
11777 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11778
11779 if (m_glcc && g_bopengl) {
11780 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11781 s_in_update++;
11782 m_glcc->Update();
11783 s_in_update--;
11784 }
11785
11786 return;
11787 }
11788#endif
11789
11790 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11791
11792 wxRegion ru = GetUpdateRegion();
11793
11794 int rx, ry, rwidth, rheight;
11795 ru.GetBox(rx, ry, rwidth, rheight);
11796
11797#ifdef ocpnUSE_DIBSECTION
11798 ocpnMemDC temp_dc;
11799#else
11800 wxMemoryDC temp_dc;
11801#endif
11802
11803 long height = GetVP().pix_height;
11804
11805#ifdef __WXMAC__
11806 // On OS X we have to explicitly extend the region for the piano area
11807 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11808 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11809 height += m_Piano->GetHeight();
11810#endif // __WXMAC__
11811 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11812
11813 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11814 if (pthumbwin) {
11815 int thumbx, thumby, thumbsx, thumbsy;
11816 pthumbwin->GetPosition(&thumbx, &thumby);
11817 pthumbwin->GetSize(&thumbsx, &thumbsy);
11818 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11819
11820 if (pthumbwin->IsShown()) {
11821 rgn_chart.Subtract(rgn_thumbwin);
11822 ru.Subtract(rgn_thumbwin);
11823 }
11824 }
11825
11826 // subtract the chart bar if it isn't transparent, and determine if we need to
11827 // paint it
11828 wxRegion rgn_blit = ru;
11829 if (g_bShowChartBar) {
11830 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11831 GetClientSize().x, m_Piano->GetHeight());
11832
11833 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11834 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11835 if (style->chartStatusWindowTransparent)
11836 m_brepaint_piano = true;
11837 else
11838 ru.Subtract(chart_bar_rect);
11839 }
11840 }
11841
11842 if (m_Compass && m_Compass->IsShown()) {
11843 wxRect compassRect = m_Compass->GetRect();
11844 if (ru.Contains(compassRect) != wxOutRegion) {
11845 ru.Subtract(compassRect);
11846 }
11847 }
11848
11849 if (m_notification_button) {
11850 wxRect noteRect = m_notification_button->GetRect();
11851 if (ru.Contains(noteRect) != wxOutRegion) {
11852 ru.Subtract(noteRect);
11853 }
11854 }
11855
11856 // Is this viewpoint the same as the previously painted one?
11857 bool b_newview = true;
11858
11859 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11860 (m_cache_vp.rotation == VPoint.rotation) &&
11861 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11862 m_cache_vp.IsValid()) {
11863 b_newview = false;
11864 }
11865
11866 // If the ViewPort is skewed or rotated, we may be able to use the cached
11867 // rotated bitmap.
11868 bool b_rcache_ok = false;
11869 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11870 b_rcache_ok = !b_newview;
11871
11872 // Make a special VP
11873 if (VPoint.b_MercatorProjectionOverride)
11874 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11875 ViewPort svp = VPoint;
11876
11877 svp.pix_width = svp.rv_rect.width;
11878 svp.pix_height = svp.rv_rect.height;
11879
11880 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11881 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11882 // VPoint.rv_rect.height);
11883
11884 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11885
11886 // If we are going to use the cached rotated image, there is no need to fetch
11887 // any chart data and this will do it...
11888 if (b_rcache_ok) chart_get_region.Clear();
11889
11890 // Blit pan acceleration
11891 if (VPoint.b_quilt) // quilted
11892 {
11893 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11894
11895 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11896
11897 bool busy = false;
11898 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11899 m_cache_vp.rotation != VPoint.rotation)) {
11900 AbstractPlatform::ShowBusySpinner();
11901 busy = true;
11902 }
11903
11904 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11905 (m_working_bm.GetHeight() != svp.pix_height))
11906 m_working_bm.Create(svp.pix_width, svp.pix_height,
11907 -1); // make sure the target is big enoug
11908
11909 if (fabs(VPoint.rotation) < 0.01) {
11910 bool b_save = true;
11911
11912 if (g_SencThreadManager) {
11913 if (g_SencThreadManager->GetJobCount()) {
11914 b_save = false;
11915 m_cache_vp.Invalidate();
11916 }
11917 }
11918
11919 // If the saved wxBitmap from last OnPaint is useable
11920 // calculate the blit parameters
11921
11922 // We can only do screen blit painting if subsequent ViewPorts differ by
11923 // whole pixels So, in small scale bFollow mode, force the full screen
11924 // render. This seems a hack....There may be better logic here.....
11925
11926 // if(m_bFollow)
11927 // b_save = false;
11928
11929 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11930 if (b_newview) {
11931 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11932 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11933
11934 int dy = c_new.y - c_old.y;
11935 int dx = c_new.x - c_old.x;
11936
11937 // printf("In OnPaint Trying Blit dx: %d
11938 // dy:%d\n\n", dx, dy);
11939
11940 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11941 if (dx || dy) {
11942 // Blit the reuseable portion of the cached wxBitmap to a working
11943 // bitmap
11944 temp_dc.SelectObject(m_working_bm);
11945
11946 wxMemoryDC cache_dc;
11947 cache_dc.SelectObject(m_cached_chart_bm);
11948
11949 if (dy > 0) {
11950 if (dx > 0) {
11951 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11952 VPoint.pix_height - dy, &cache_dc, dx, dy);
11953 } else {
11954 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11955 VPoint.pix_height - dy, &cache_dc, 0, dy);
11956 }
11957
11958 } else {
11959 if (dx > 0) {
11960 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11961 VPoint.pix_height + dy, &cache_dc, dx, 0);
11962 } else {
11963 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11964 VPoint.pix_height + dy, &cache_dc, 0, 0);
11965 }
11966 }
11967
11968 OCPNRegion update_region;
11969 if (dy) {
11970 if (dy > 0)
11971 update_region.Union(
11972 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11973 else
11974 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11975 }
11976
11977 if (dx) {
11978 if (dx > 0)
11979 update_region.Union(
11980 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11981 else
11982 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11983 }
11984
11985 // Render the new region
11986 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11987 update_region);
11988 cache_dc.SelectObject(wxNullBitmap);
11989 } else {
11990 // No sensible (dx, dy) change in the view, so use the cached
11991 // member bitmap
11992 temp_dc.SelectObject(m_cached_chart_bm);
11993 b_save = false;
11994 }
11995 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11996
11997 } else // not blitable
11998 {
11999 temp_dc.SelectObject(m_working_bm);
12000 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12001 chart_get_region);
12002 }
12003 } else {
12004 // No change in the view, so use the cached member bitmap2
12005 temp_dc.SelectObject(m_cached_chart_bm);
12006 b_save = false;
12007 }
12008 } else // cached bitmap is not yet valid
12009 {
12010 temp_dc.SelectObject(m_working_bm);
12011 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12012 chart_get_region);
12013 }
12014
12015 // Save the fully rendered quilt image as a wxBitmap member of this class
12016 if (b_save) {
12017 // if((m_cached_chart_bm.GetWidth() !=
12018 // svp.pix_width) ||
12019 // (m_cached_chart_bm.GetHeight() !=
12020 // svp.pix_height))
12021 // m_cached_chart_bm.Create(svp.pix_width,
12022 // svp.pix_height, -1); // target wxBitmap
12023 // is big enough
12024 wxMemoryDC scratch_dc_0;
12025 scratch_dc_0.SelectObject(m_cached_chart_bm);
12026 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12027
12028 scratch_dc_0.SelectObject(wxNullBitmap);
12029
12030 m_bm_cache_vp =
12031 VPoint; // save the ViewPort associated with the cached wxBitmap
12032 }
12033 }
12034
12035 else // quilted, rotated
12036 {
12037 temp_dc.SelectObject(m_working_bm);
12038 OCPNRegion chart_get_all_region(
12039 wxRect(0, 0, svp.pix_width, svp.pix_height));
12040 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12041 chart_get_all_region);
12042 }
12043
12044 AbstractPlatform::HideBusySpinner();
12045
12046 }
12047
12048 else // not quilted
12049 {
12050 if (!m_singleChart) {
12051 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12052 dc.Clear();
12053 return;
12054 }
12055
12056 if (!chart_get_region.IsEmpty()) {
12057 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12058 }
12059 }
12060
12061 if (temp_dc.IsOk()) {
12062 // Arrange to render the World Chart vector data behind the rendered
12063 // current chart so that uncovered canvas areas show at least the world
12064 // chart.
12065 OCPNRegion chartValidRegion;
12066 if (!VPoint.b_quilt) {
12067 // Make a region covering the current chart on the canvas
12068
12069 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12070 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12071 else {
12072 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12073 // require that the viewport passed here have pix_width and pix_height
12074 // set to the actual display, not the virtual (rv_rect) sizes
12075 // (the vector calculations require the virtual sizes in svp)
12076
12077 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12078 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12079 }
12080 } else
12081 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12082
12083 temp_dc.DestroyClippingRegion();
12084
12085 // Copy current chart region
12086 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12087
12088 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12089
12090 if (!backgroundRegion.IsEmpty()) {
12091 // Draw the Background Chart only in the areas NOT covered by the
12092 // current chart view
12093
12094 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12095 clipping regions with more than 1 rectangle so... */
12096 wxColour water = pWorldBackgroundChart->water;
12097 if (water.IsOk()) {
12098 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12099 temp_dc.SetBrush(wxBrush(water));
12100 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12101 while (upd.HaveRects()) {
12102 wxRect rect = upd.GetRect();
12103 temp_dc.DrawRectangle(rect);
12104 upd.NextRect();
12105 }
12106 }
12107 // Associate with temp_dc
12108 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12109 temp_dc.SetDeviceClippingRegion(*clip_region);
12110 delete clip_region;
12111
12112 ocpnDC bgdc(temp_dc);
12113 double r = VPoint.rotation;
12114 SetVPRotation(VPoint.skew);
12115
12116 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12117 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12118
12119 SetVPRotation(r);
12120 }
12121 } // temp_dc.IsOk();
12122
12123 wxMemoryDC *pChartDC = &temp_dc;
12124 wxMemoryDC rotd_dc;
12125
12126 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12127 // Can we use the current rotated image cache?
12128 if (!b_rcache_ok) {
12129#ifdef __WXMSW__
12130 wxMemoryDC tbase_dc;
12131 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12132 tbase_dc.SelectObject(bm_base);
12133 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12134 tbase_dc.SelectObject(wxNullBitmap);
12135#else
12136 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12137#endif
12138
12139 wxImage base_image;
12140 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12141
12142 // Use a local static image rotator to improve wxWidgets code profile
12143 // Especially, on GTK the wxRound and wxRealPoint functions are very
12144 // expensive.....
12145
12146 double angle = GetVP().skew - GetVP().rotation;
12147 wxImage ri;
12148 bool b_rot_ok = false;
12149 if (base_image.IsOk()) {
12150 ViewPort rot_vp = GetVP();
12151
12152 m_b_rot_hidef = false;
12153
12154 ri = Image_Rotate(
12155 base_image, angle,
12156 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12157 m_b_rot_hidef, &m_roffset);
12158
12159 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12160 (rot_vp.rotation == VPoint.rotation) &&
12161 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12162 rot_vp.IsValid() && (ri.IsOk())) {
12163 b_rot_ok = true;
12164 }
12165 }
12166
12167 if (b_rot_ok) {
12168 delete m_prot_bm;
12169 m_prot_bm = new wxBitmap(ri);
12170 }
12171
12172 m_roffset.x += VPoint.rv_rect.x;
12173 m_roffset.y += VPoint.rv_rect.y;
12174 }
12175
12176 if (m_prot_bm && m_prot_bm->IsOk()) {
12177 rotd_dc.SelectObject(*m_prot_bm);
12178 pChartDC = &rotd_dc;
12179 } else {
12180 pChartDC = &temp_dc;
12181 m_roffset = wxPoint(0, 0);
12182 }
12183 } else { // unrotated
12184 pChartDC = &temp_dc;
12185 m_roffset = wxPoint(0, 0);
12186 }
12187
12188 wxPoint offset = m_roffset;
12189
12190 // Save the PixelCache viewpoint for next time
12191 m_cache_vp = VPoint;
12192
12193 // Set up a scratch DC for overlay objects
12194 wxMemoryDC mscratch_dc;
12195 mscratch_dc.SelectObject(*pscratch_bm);
12196
12197 mscratch_dc.ResetBoundingBox();
12198 mscratch_dc.DestroyClippingRegion();
12199 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12200
12201 // Blit the externally invalidated areas of the chart onto the scratch dc
12202 wxRegionIterator upd(rgn_blit); // get the update rect list
12203 while (upd) {
12204 wxRect rect = upd.GetRect();
12205
12206 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12207 rect.x - offset.x, rect.y - offset.y);
12208 upd++;
12209 }
12210
12211 // If multi-canvas, indicate which canvas has keyboard focus
12212 // by drawing a simple blue bar at the top.
12213 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12214 if (this == wxWindow::FindFocus()) {
12215 g_focusCanvas = this;
12216
12217 wxColour colour = GetGlobalColor("BLUE4");
12218 mscratch_dc.SetPen(wxPen(colour));
12219 mscratch_dc.SetBrush(wxBrush(colour));
12220
12221 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12222 mscratch_dc.DrawRectangle(activeRect);
12223 }
12224 }
12225
12226 // Any MBtiles?
12227 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12228 unsigned int im = stackIndexArray.size();
12229 if (VPoint.b_quilt && im > 0) {
12230 std::vector<int> tiles_to_show;
12231 for (unsigned int is = 0; is < im; is++) {
12232 const ChartTableEntry &cte =
12233 ChartData->GetChartTableEntry(stackIndexArray[is]);
12234 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12235 continue;
12236 }
12237 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12238 tiles_to_show.push_back(stackIndexArray[is]);
12239 }
12240 }
12241
12242 if (tiles_to_show.size())
12243 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12244 }
12245
12246 // May get an unexpected OnPaint call while switching display modes
12247 // Guard for that.
12248 if (!g_bopengl) {
12249 ocpnDC scratch_dc(mscratch_dc);
12250 RenderAlertMessage(mscratch_dc, GetVP());
12251 }
12252
12253#if 0
12254 // quiting?
12255 if (g_bquiting) {
12256#ifdef ocpnUSE_DIBSECTION
12257 ocpnMemDC q_dc;
12258#else
12259 wxMemoryDC q_dc;
12260#endif
12261 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12262 q_dc.SelectObject(qbm);
12263
12264 // Get a copy of the screen
12265 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12266
12267 // Draw a rectangle over the screen with a stipple brush
12268 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12269 q_dc.SetBrush(qbr);
12270 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12271
12272 // Blit back into source
12273 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12274 wxCOPY);
12275
12276 q_dc.SelectObject(wxNullBitmap);
12277 }
12278#endif
12279
12280#if 0
12281 // It is possible that this two-step method may be reuired for some platforms.
12282 // So, retain in the code base to aid recovery if necessary
12283
12284 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12285 if( VPoint.b_quilt ) {
12286 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12287 ChartBase *chart = m_pQuilt->GetRefChart();
12288 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12289
12290 // Clear the text Global declutter list
12291 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12292 if(ChPI)
12293 ChPI->ClearPLIBTextList();
12294 else{
12295 if(ps52plib)
12296 ps52plib->ClearTextList();
12297 }
12298
12299 wxMemoryDC t_dc;
12300 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12301
12302 wxColor maskBackground = wxColour(1,0,0);
12303 t_dc.SelectObject( qbm );
12304 t_dc.SetBackground(wxBrush(maskBackground));
12305 t_dc.Clear();
12306
12307 // Copy the scratch DC into the new bitmap
12308 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12309
12310 // Render the text to the new bitmap
12311 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12312 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12313
12314 // Copy the new bitmap back to the scratch dc
12315 wxRegionIterator upd_final( ru );
12316 while( upd_final ) {
12317 wxRect rect = upd_final.GetRect();
12318 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12319 upd_final++;
12320 }
12321
12322 t_dc.SelectObject( wxNullBitmap );
12323 }
12324 }
12325 }
12326#endif
12327 // Direct rendering model...
12328 if (VPoint.b_quilt) {
12329 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12330 ChartBase *chart = m_pQuilt->GetRefChart();
12331 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12332 // Clear the text Global declutter list
12333 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12334 if (ChPI)
12335 ChPI->ClearPLIBTextList();
12336 else {
12337 if (ps52plib) ps52plib->ClearTextList();
12338 }
12339
12340 // Render the text directly to the scratch bitmap
12341 OCPNRegion chart_all_text_region(
12342 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12343
12344 if (g_bShowChartBar && m_Piano) {
12345 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12346 GetVP().pix_width, m_Piano->GetHeight());
12347
12348 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12349 if (!style->chartStatusWindowTransparent)
12350 chart_all_text_region.Subtract(chart_bar_rect);
12351 }
12352
12353 if (m_Compass && m_Compass->IsShown()) {
12354 wxRect compassRect = m_Compass->GetRect();
12355 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12356 chart_all_text_region.Subtract(compassRect);
12357 }
12358 }
12359
12360 mscratch_dc.DestroyClippingRegion();
12361
12362 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12363 chart_all_text_region);
12364 }
12365 }
12366 }
12367
12368 // Now that charts are fully rendered, apply the overlay objects as decals.
12369 ocpnDC scratch_dc(mscratch_dc);
12370 DrawOverlayObjects(scratch_dc, ru);
12371
12372 // And finally, blit the scratch dc onto the physical dc
12373 wxRegionIterator upd_final(rgn_blit);
12374 while (upd_final) {
12375 wxRect rect = upd_final.GetRect();
12376 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12377 rect.y);
12378 upd_final++;
12379 }
12380
12381 // Deselect the chart bitmap from the temp_dc, so that it will not be
12382 // destroyed in the temp_dc dtor
12383 temp_dc.SelectObject(wxNullBitmap);
12384 // And for the scratch bitmap
12385 mscratch_dc.SelectObject(wxNullBitmap);
12386
12387 dc.DestroyClippingRegion();
12388
12389 PaintCleanup();
12390}
12391
12392void ChartCanvas::PaintCleanup() {
12393 // Handle the current graphic window, if present
12394 if (m_inPinch) return;
12395
12396 if (pCwin) {
12397 pCwin->Show();
12398 if (m_bTCupdate) {
12399 pCwin->Refresh();
12400 pCwin->Update();
12401 }
12402 }
12403
12404 // And set flags for next time
12405 m_bTCupdate = false;
12406
12407 // Handle deferred WarpPointer
12408 if (warp_flag) {
12409 WarpPointer(warp_x, warp_y);
12410 warp_flag = false;
12411 }
12412
12413 // Start movement timers, this runs nearly immediately.
12414 // the reason we cannot simply call it directly is the
12415 // refresh events it emits may be blocked from this paint event
12416 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12417 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12418}
12419
12420#if 0
12421wxColour GetErrorGraphicColor(double val)
12422{
12423 /*
12424 double valm = wxMin(val_max, val);
12425
12426 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12427 unsigned char red = (unsigned char)(255 * (valm/val_max));
12428
12429 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12430
12431 hv.saturation = 1.0;
12432 hv.value = 1.0;
12433
12434 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12435 return wxColour(rv.red, rv.green, rv.blue);
12436 */
12437
12438 // HTML colors taken from NOAA WW3 Web representation
12439 wxColour c;
12440 if((val > 0) && (val < 1)) c.Set("#002ad9");
12441 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12442 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12443 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12444 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12445 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12446 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12447 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12448 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12449 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12450 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12451 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12452 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12453 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12454 else if((val >= 30) && (val < 36)) c.Set("#870000");
12455 else if((val >= 36) && (val < 42)) c.Set("#690000");
12456 else if((val >= 42) && (val < 48)) c.Set("#550000");
12457 else if( val >= 48) c.Set("#410000");
12458
12459 return c;
12460}
12461
12462void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12463{
12464 wxImage gr_image(vp->pix_width, vp->pix_height);
12465 gr_image.InitAlpha();
12466
12467 double maxval = -10000;
12468 double minval = 10000;
12469
12470 double rlat, rlon;
12471 double glat, glon;
12472
12473 GetCanvasPixPoint(0, 0, rlat, rlon);
12474
12475 for(int i=1; i < vp->pix_height-1; i++)
12476 {
12477 for(int j=0; j < vp->pix_width; j++)
12478 {
12479 // Reference mercator value
12480// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12481
12482 // Georef value
12483 GetCanvasPixPoint(j, i, glat, glon);
12484
12485 maxval = wxMax(maxval, (glat - rlat));
12486 minval = wxMin(minval, (glat - rlat));
12487
12488 }
12489 rlat = glat;
12490 }
12491
12492 GetCanvasPixPoint(0, 0, rlat, rlon);
12493 for(int i=1; i < vp->pix_height-1; i++)
12494 {
12495 for(int j=0; j < vp->pix_width; j++)
12496 {
12497 // Reference mercator value
12498// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12499
12500 // Georef value
12501 GetCanvasPixPoint(j, i, glat, glon);
12502
12503 double f = ((glat - rlat)-minval)/(maxval - minval);
12504
12505 double dy = (f * 40);
12506
12507 wxColour c = GetErrorGraphicColor(dy);
12508 unsigned char r = c.Red();
12509 unsigned char g = c.Green();
12510 unsigned char b = c.Blue();
12511
12512 gr_image.SetRGB(j, i, r,g,b);
12513 if((glat - rlat )!= 0)
12514 gr_image.SetAlpha(j, i, 128);
12515 else
12516 gr_image.SetAlpha(j, i, 255);
12517
12518 }
12519 rlat = glat;
12520 }
12521
12522 // Create a Bitmap
12523 wxBitmap *pbm = new wxBitmap(gr_image);
12524 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12525 pbm->SetMask(gr_mask);
12526
12527 pmdc->DrawBitmap(*pbm, 0,0);
12528
12529 delete pbm;
12530
12531}
12532
12533#endif
12534
12535void ChartCanvas::CancelMouseRoute() {
12536 m_routeState = 0;
12537 m_pMouseRoute = NULL;
12538 m_bDrawingRoute = false;
12539}
12540
12541int ChartCanvas::GetNextContextMenuId() {
12542 return CanvasMenuHandler::GetNextContextMenuId();
12543}
12544
12545bool ChartCanvas::SetCursor(const wxCursor &c) {
12546#ifdef ocpnUSE_GL
12547 if (g_bopengl && m_glcc)
12548 return m_glcc->SetCursor(c);
12549 else
12550#endif
12551 return wxWindow::SetCursor(c);
12552}
12553
12554void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12555 if (g_bquiting) return;
12556 // Keep the mouse position members up to date
12557 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12558
12559 // Retrigger the route leg popup timer
12560 // This handles the case when the chart is moving in auto-follow mode,
12561 // but no user mouse input is made. The timer handler may Hide() the
12562 // popup if the chart moved enough n.b. We use slightly longer oneshot
12563 // value to allow this method's Refresh() to complete before potentially
12564 // getting another Refresh() in the popup timer handler.
12565 if (!m_RolloverPopupTimer.IsRunning() &&
12566 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12567 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12568 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12569 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12570
12571#ifdef ocpnUSE_GL
12572 if (m_glcc && g_bopengl) {
12573 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12574 // overlay objects.
12575 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12576
12577 m_glcc->Refresh(eraseBackground,
12578 NULL); // We always are going to render the entire screen
12579 // anyway, so make
12580 // sure that the window managers understand the invalid area
12581 // is actually the entire client area.
12582
12583 // We need to selectively Refresh some child windows, if they are visible.
12584 // Note that some children are refreshed elsewhere on timer ticks, so don't
12585 // need attention here.
12586
12587 // Thumbnail chart
12588 if (pthumbwin && pthumbwin->IsShown()) {
12589 pthumbwin->Raise();
12590 pthumbwin->Refresh(false);
12591 }
12592
12593 // ChartInfo window
12594 if (m_pCIWin && m_pCIWin->IsShown()) {
12595 m_pCIWin->Raise();
12596 m_pCIWin->Refresh(false);
12597 }
12598
12599 // if(g_MainToolbar)
12600 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12601
12602 } else
12603#endif
12604 wxWindow::Refresh(eraseBackground, rect);
12605}
12606
12607void ChartCanvas::Update() {
12608 if (m_glcc && g_bopengl) {
12609#ifdef ocpnUSE_GL
12610 m_glcc->Update();
12611#endif
12612 } else
12613 wxWindow::Update();
12614}
12615
12616void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12617 if (!pemboss) return;
12618 int x = pemboss->x, y = pemboss->y;
12619 const double factor = 200;
12620
12621 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12622 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12623 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12624
12625 // Grab a snipped image out of the chart
12626 wxMemoryDC snip_dc;
12627 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12628 snip_dc.SelectObject(snip_bmp);
12629
12630 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12631 snip_dc.SelectObject(wxNullBitmap);
12632
12633 wxImage snip_img = snip_bmp.ConvertToImage();
12634
12635 // Apply Emboss map to the snip image
12636 unsigned char *pdata = snip_img.GetData();
12637 if (pdata) {
12638 for (int y = 0; y < pemboss->height; y++) {
12639 int map_index = (y * pemboss->width);
12640 for (int x = 0; x < pemboss->width; x++) {
12641 double val = (pemboss->pmap[map_index] * factor) / 256.;
12642
12643 int nred = (int)((*pdata) + val);
12644 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12645 *pdata++ = (unsigned char)nred;
12646
12647 int ngreen = (int)((*pdata) + val);
12648 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12649 *pdata++ = (unsigned char)ngreen;
12650
12651 int nblue = (int)((*pdata) + val);
12652 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12653 *pdata++ = (unsigned char)nblue;
12654
12655 map_index++;
12656 }
12657 }
12658 }
12659
12660 // Convert embossed snip to a bitmap
12661 wxBitmap emb_bmp(snip_img);
12662
12663 // Map to another memoryDC
12664 wxMemoryDC result_dc;
12665 result_dc.SelectObject(emb_bmp);
12666
12667 // Blit to target
12668 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12669
12670 result_dc.SelectObject(wxNullBitmap);
12671}
12672
12673emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12674 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12675
12676 if (GetQuiltMode()) {
12677 // disable Overzoom indicator for MBTiles
12678 int refIndex = GetQuiltRefChartdbIndex();
12679 if (refIndex >= 0) {
12680 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12681 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12682 if (current_type == CHART_TYPE_MBTILES) {
12683 ChartBase *pChart = m_pQuilt->GetRefChart();
12684 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12685 if (ptc) {
12686 zoom_factor = ptc->GetZoomFactor();
12687 }
12688 }
12689 }
12690
12691 if (zoom_factor <= 3.9) return NULL;
12692 } else {
12693 if (m_singleChart) {
12694 if (zoom_factor <= 3.9) return NULL;
12695 } else
12696 return NULL;
12697 }
12698
12699 if (m_pEM_OverZoom) {
12700 m_pEM_OverZoom->x = 4;
12701 m_pEM_OverZoom->y = 0;
12702 if (g_MainToolbar && IsPrimaryCanvas()) {
12703 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12704 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12705 }
12706 }
12707 return m_pEM_OverZoom;
12708}
12709
12710void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12711 GridDraw(dc);
12712
12713 // bool pluginOverlayRender = true;
12714 //
12715 // if(g_canvasConfig > 0){ // Multi canvas
12716 // if(IsPrimaryCanvas())
12717 // pluginOverlayRender = false;
12718 // }
12719
12720 g_overlayCanvas = this;
12721
12722 if (g_pi_manager) {
12723 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12724 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12726 }
12727
12728 AISDrawAreaNotices(dc, GetVP(), this);
12729
12730 wxDC *pdc = dc.GetDC();
12731 if (pdc) {
12732 pdc->DestroyClippingRegion();
12733 wxDCClipper(*pdc, ru);
12734 }
12735
12736 if (m_bShowNavobjects) {
12737 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12738 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12739 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12740 DrawAnchorWatchPoints(dc);
12741 } else {
12742 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12743 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12744 }
12745
12746 AISDraw(dc, GetVP(), this);
12747 ShipDraw(dc);
12748 AlertDraw(dc);
12749
12750 RenderVisibleSectorLights(dc);
12751
12752 RenderAllChartOutlines(dc, GetVP());
12753 RenderRouteLegs(dc);
12754 RenderShipToActive(dc, false);
12755 ScaleBarDraw(dc);
12756 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12757 if (g_pi_manager) {
12758 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12760 }
12761
12762 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12763 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12764
12765 if (g_pi_manager) {
12766 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12768 }
12769
12770 if (m_bShowTide) {
12771 RebuildTideSelectList(GetVP().GetBBox());
12772 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12773 }
12774
12775 if (m_bShowCurrent) {
12776 RebuildCurrentSelectList(GetVP().GetBBox());
12777 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12778 }
12779
12780 if (!g_PrintingInProgress) {
12781 if (IsPrimaryCanvas()) {
12782 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12783 }
12784
12785 if (IsPrimaryCanvas()) {
12786 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12787 }
12788
12789 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12790
12791 if (m_pTrackRolloverWin) {
12792 m_pTrackRolloverWin->Draw(dc);
12793 m_brepaint_piano = true;
12794 }
12795
12796 if (m_pRouteRolloverWin) {
12797 m_pRouteRolloverWin->Draw(dc);
12798 m_brepaint_piano = true;
12799 }
12800
12801 if (m_pAISRolloverWin) {
12802 m_pAISRolloverWin->Draw(dc);
12803 m_brepaint_piano = true;
12804 }
12805 if (m_brepaint_piano && g_bShowChartBar) {
12806 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12807 }
12808
12809 if (m_Compass) m_Compass->Paint(dc);
12810
12811 if (!g_CanvasHideNotificationIcon) {
12812 if (IsPrimaryCanvas()) {
12813 auto &noteman = NotificationManager::GetInstance();
12814 if (noteman.GetNotificationCount()) {
12815 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12816 if (m_notification_button->UpdateStatus()) Refresh();
12817 m_notification_button->Show(true);
12818 m_notification_button->Paint(dc);
12819 } else {
12820 m_notification_button->Show(false);
12821 }
12822 }
12823 }
12824 }
12825 if (g_pi_manager) {
12826 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12828 }
12829}
12830
12831emboss_data *ChartCanvas::EmbossDepthScale() {
12832 if (!m_bShowDepthUnits) return NULL;
12833
12834 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12835
12836 if (GetQuiltMode()) {
12837 wxString s = m_pQuilt->GetQuiltDepthUnit();
12838 s.MakeUpper();
12839 if (s == "FEET")
12840 depth_unit_type = DEPTH_UNIT_FEET;
12841 else if (s.StartsWith("FATHOMS"))
12842 depth_unit_type = DEPTH_UNIT_FATHOMS;
12843 else if (s.StartsWith("METERS"))
12844 depth_unit_type = DEPTH_UNIT_METERS;
12845 else if (s.StartsWith("METRES"))
12846 depth_unit_type = DEPTH_UNIT_METERS;
12847 else if (s.StartsWith("METRIC"))
12848 depth_unit_type = DEPTH_UNIT_METERS;
12849 else if (s.StartsWith("METER"))
12850 depth_unit_type = DEPTH_UNIT_METERS;
12851
12852 } else {
12853 if (m_singleChart) {
12854 depth_unit_type = m_singleChart->GetDepthUnitType();
12855 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12856 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12857 }
12858 }
12859
12860 emboss_data *ped = NULL;
12861 switch (depth_unit_type) {
12862 case DEPTH_UNIT_FEET:
12863 ped = m_pEM_Feet;
12864 break;
12865 case DEPTH_UNIT_METERS:
12866 ped = m_pEM_Meters;
12867 break;
12868 case DEPTH_UNIT_FATHOMS:
12869 ped = m_pEM_Fathoms;
12870 break;
12871 default:
12872 return NULL;
12873 }
12874
12875 ped->x = (GetVP().pix_width - ped->width);
12876
12877 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12878 wxRect r = m_Compass->GetRect();
12879 ped->y = r.y + r.height;
12880 } else {
12881 ped->y = 40;
12882 }
12883 return ped;
12884}
12885
12886void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12887 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12888 wxFont font;
12889 if (style->embossFont == wxEmptyString) {
12890 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12891 font = *dFont;
12892 font.SetPointSize(60);
12893 font.SetWeight(wxFONTWEIGHT_BOLD);
12894 } else
12895 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12896 wxFONTWEIGHT_BOLD, false, style->embossFont);
12897
12898 int emboss_width = 500;
12899 int emboss_height = 200;
12900
12901 // Free any existing emboss maps
12902 delete m_pEM_Feet;
12903 delete m_pEM_Meters;
12904 delete m_pEM_Fathoms;
12905
12906 // Create the 3 DepthUnit emboss map structures
12907 m_pEM_Feet =
12908 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12909 m_pEM_Meters =
12910 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12911 m_pEM_Fathoms =
12912 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12913}
12914
12915#define OVERZOOM_TEXT _("OverZoom")
12916
12917void ChartCanvas::SetOverzoomFont() {
12918 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12919 int w, h;
12920
12921 wxFont font;
12922 if (style->embossFont == wxEmptyString) {
12923 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12924 font = *dFont;
12925 font.SetPointSize(40);
12926 font.SetWeight(wxFONTWEIGHT_BOLD);
12927 } else
12928 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12929 wxFONTWEIGHT_BOLD, false, style->embossFont);
12930
12931 wxClientDC dc(this);
12932 dc.SetFont(font);
12933 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12934
12935 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12936 font.SetPointSize(font.GetPointSize() - 1);
12937 dc.SetFont(font);
12938 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12939 }
12940 m_overzoomFont = font;
12941 m_overzoomTextWidth = w;
12942 m_overzoomTextHeight = h;
12943}
12944
12945void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12946 delete m_pEM_OverZoom;
12947
12948 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12949 m_pEM_OverZoom =
12950 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12951 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12952}
12953
12954emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12955 int height, const wxString &str,
12956 ColorScheme cs) {
12957 int *pmap;
12958
12959 // Create a temporary bitmap
12960 wxBitmap bmp(width, height, -1);
12961
12962 // Create a memory DC
12963 wxMemoryDC temp_dc;
12964 temp_dc.SelectObject(bmp);
12965
12966 // Paint on it
12967 temp_dc.SetBackground(*wxWHITE_BRUSH);
12968 temp_dc.SetTextBackground(*wxWHITE);
12969 temp_dc.SetTextForeground(*wxBLACK);
12970
12971 temp_dc.Clear();
12972
12973 temp_dc.SetFont(font);
12974
12975 int str_w, str_h;
12976 temp_dc.GetTextExtent(str, &str_w, &str_h);
12977 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12978 temp_dc.DrawText(str, 1, 1);
12979
12980 // Deselect the bitmap
12981 temp_dc.SelectObject(wxNullBitmap);
12982
12983 // Convert bitmap the wxImage for manipulation
12984 wxImage img = bmp.ConvertToImage();
12985
12986 int image_width = str_w * 105 / 100;
12987 int image_height = str_h * 105 / 100;
12988 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12989 wxMin(image_height, img.GetHeight()));
12990 wxImage imgs = img.GetSubImage(r);
12991
12992 double val_factor;
12993 switch (cs) {
12994 case GLOBAL_COLOR_SCHEME_DAY:
12995 default:
12996 val_factor = 1;
12997 break;
12998 case GLOBAL_COLOR_SCHEME_DUSK:
12999 val_factor = .5;
13000 break;
13001 case GLOBAL_COLOR_SCHEME_NIGHT:
13002 val_factor = .25;
13003 break;
13004 }
13005
13006 int val;
13007 int index;
13008 const int w = imgs.GetWidth();
13009 const int h = imgs.GetHeight();
13010 pmap = (int *)calloc(w * h * sizeof(int), 1);
13011 // Create emboss map by differentiating the emboss image
13012 // and storing integer results in pmap
13013 // n.b. since the image is B/W, it is sufficient to check
13014 // one channel (i.e. red) only
13015 for (int y = 1; y < h - 1; y++) {
13016 for (int x = 1; x < w - 1; x++) {
13017 val =
13018 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13019 val = (int)(val * val_factor);
13020 index = (y * w) + x;
13021 pmap[index] = val;
13022 }
13023 }
13024
13025 emboss_data *pret = new emboss_data;
13026 pret->pmap = pmap;
13027 pret->width = w;
13028 pret->height = h;
13029
13030 return pret;
13031}
13032
13033void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13034 Track *active_track = NULL;
13035 for (Track *pTrackDraw : g_TrackList) {
13036 if (g_pActiveTrack == pTrackDraw) {
13037 active_track = pTrackDraw;
13038 continue;
13039 }
13040
13041 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13042 }
13043
13044 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13045}
13046
13047void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13048 Track *active_track = NULL;
13049 for (Track *pTrackDraw : g_TrackList) {
13050 if (g_pActiveTrack == pTrackDraw) {
13051 active_track = pTrackDraw;
13052 break;
13053 }
13054 }
13055 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13056}
13057
13058void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13059 Route *active_route = NULL;
13060 for (Route *pRouteDraw : *pRouteList) {
13061 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13062 active_route = pRouteDraw;
13063 continue;
13064 }
13065
13066 // if(m_canvasIndex == 1)
13067 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13068 }
13069
13070 // Draw any active or selected route (or track) last, so that is is always on
13071 // top
13072 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13073}
13074
13075void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13076 Route *active_route = NULL;
13077
13078 for (Route *pRouteDraw : *pRouteList) {
13079 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13080 active_route = pRouteDraw;
13081 break;
13082 }
13083 }
13084 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13085}
13086
13087void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13088 if (!pWayPointMan) return;
13089
13090 auto node = pWayPointMan->GetWaypointList()->begin();
13091
13092 while (node != pWayPointMan->GetWaypointList()->end()) {
13093 RoutePoint *pWP = *node;
13094 if (pWP) {
13095 if (pWP->m_bIsInRoute) {
13096 ++node;
13097 continue;
13098 }
13099
13100 /* technically incorrect... waypoint has bounding box */
13101 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13102 RoutePointGui(*pWP).Draw(dc, this, NULL);
13103 else {
13104 // Are Range Rings enabled?
13105 if (pWP->GetShowWaypointRangeRings() &&
13106 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13107 double factor = 1.00;
13108 if (pWP->GetWaypointRangeRingsStepUnits() ==
13109 1) // convert kilometers to NMi
13110 factor = 1 / 1.852;
13111
13112 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13113 pWP->GetWaypointRangeRingsStep() / 60.;
13114 radius *= 2; // Fudge factor
13115
13116 LLBBox radar_box;
13117 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13118 pWP->m_lat + radius, pWP->m_lon + radius);
13119 if (!BltBBox.IntersectOut(radar_box)) {
13120 RoutePointGui(*pWP).Draw(dc, this, NULL);
13121 }
13122 }
13123 }
13124 }
13125
13126 ++node;
13127 }
13128}
13129
13130void ChartCanvas::DrawBlinkObjects() {
13131 // All RoutePoints
13132 wxRect update_rect;
13133
13134 if (!pWayPointMan) return;
13135
13136 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13137 if (pWP) {
13138 if (pWP->m_bBlink) {
13139 update_rect.Union(pWP->CurrentRect_in_DC);
13140 }
13141 }
13142 }
13143 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13144}
13145
13146void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13147 // draw anchor watch rings, if activated
13148
13150 wxPoint r1, r2;
13151 wxPoint lAnchorPoint1, lAnchorPoint2;
13152 double lpp1 = 0.0;
13153 double lpp2 = 0.0;
13154 if (pAnchorWatchPoint1) {
13155 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13157 &lAnchorPoint1);
13158 }
13159 if (pAnchorWatchPoint2) {
13160 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13162 &lAnchorPoint2);
13163 }
13164
13165 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13166 wxPen ppPenr(GetGlobalColor("URED"), 2);
13167
13168 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13169 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13170 dc.SetBrush(*ppBrush);
13171
13172 if (lpp1 > 0) {
13173 dc.SetPen(ppPeng);
13174 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13175 }
13176
13177 if (lpp2 > 0) {
13178 dc.SetPen(ppPeng);
13179 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13180 }
13181
13182 if (lpp1 < 0) {
13183 dc.SetPen(ppPenr);
13184 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13185 }
13186
13187 if (lpp2 < 0) {
13188 dc.SetPen(ppPenr);
13189 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13190 }
13191 }
13192}
13193
13194double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13195 double lpp = 0.;
13196 wxPoint r1;
13197 wxPoint lAnchorPoint;
13198 double d1 = 0.0;
13199 double dabs;
13200 double tlat1, tlon1;
13201
13202 if (pAnchorWatchPoint) {
13203 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13204 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13205 dabs = fabs(d1 / 1852.);
13206 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13207 &tlat1, &tlon1);
13208 GetCanvasPointPix(tlat1, tlon1, &r1);
13209 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13210 &lAnchorPoint);
13211 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13212 pow((double)(lAnchorPoint.y - r1.y), 2));
13213
13214 // This is an entry watch
13215 if (d1 < 0) lpp = -lpp;
13216 }
13217 return lpp;
13218}
13219
13220//------------------------------------------------------------------------------------------
13221// Tides Support
13222//------------------------------------------------------------------------------------------
13223void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13224 if (!ptcmgr) return;
13225
13226 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13227
13228 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13229 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13230 double lon = pIDX->IDX_lon;
13231 double lat = pIDX->IDX_lat;
13232
13233 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13234 if ((type == 't') || (type == 'T')) {
13235 if (BBox.Contains(lat, lon)) {
13236 // Manage the point selection list
13237 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13238 }
13239 }
13240 }
13241}
13242
13243void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13244 if (!ptcmgr) return;
13245
13246 wxDateTime this_now = gTimeSource;
13247 bool cur_time = !gTimeSource.IsValid();
13248 if (cur_time) this_now = wxDateTime::Now();
13249 time_t t_this_now = this_now.GetTicks();
13250
13251 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13252 wxPENSTYLE_SOLID);
13253 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13254 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13255 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13256 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13257
13258 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13259 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13260 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13261 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13262 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13263 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13264
13265 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13266 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13267 int font_size = wxMax(10, dFont->GetPointSize());
13268 font_size /= g_Platform->GetDisplayDIPMult(this);
13269 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13270 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13271 false, dFont->GetFaceName());
13272
13273 dc.SetPen(*pblack_pen);
13274 dc.SetBrush(*pgreen_brush);
13275
13276 wxBitmap bm;
13277 switch (m_cs) {
13278 case GLOBAL_COLOR_SCHEME_DAY:
13279 bm = m_bmTideDay;
13280 break;
13281 case GLOBAL_COLOR_SCHEME_DUSK:
13282 bm = m_bmTideDusk;
13283 break;
13284 case GLOBAL_COLOR_SCHEME_NIGHT:
13285 bm = m_bmTideNight;
13286 break;
13287 default:
13288 bm = m_bmTideDay;
13289 break;
13290 }
13291
13292 int bmw = bm.GetWidth();
13293 int bmh = bm.GetHeight();
13294
13295 float scale_factor = 1.0;
13296
13297 // Set the onscreen size of the symbol
13298 // Compensate for various display resolutions
13299 float icon_pixelRefDim = 45;
13300
13301 // Tidal report graphic is scaled by the text size of the label in use
13302 wxScreenDC sdc;
13303 int height;
13304 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13305 height *= g_Platform->GetDisplayDIPMult(this);
13306 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13307
13308 scale_factor *= pix_factor;
13309
13310 float user_scale_factor = g_ChartScaleFactorExp;
13311 if (g_ChartScaleFactorExp > 1.0)
13312 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13313 1.2; // soften the scale factor a bit
13314
13315 scale_factor *= user_scale_factor;
13316 scale_factor *= GetContentScaleFactor();
13317
13318 {
13319 double marge = 0.05;
13320 std::vector<LLBBox> drawn_boxes;
13321 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13322 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13323
13324 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13325 if ((type == 't') || (type == 'T')) // only Tides
13326 {
13327 double lon = pIDX->IDX_lon;
13328 double lat = pIDX->IDX_lat;
13329
13330 if (BBox.ContainsMarge(lat, lon, marge)) {
13331 // Avoid drawing detailed graphic for duplicate tide stations
13332 if (GetVP().chart_scale < 500000) {
13333 bool bdrawn = false;
13334 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13335 if (drawn_boxes[i].Contains(lat, lon)) {
13336 bdrawn = true;
13337 break;
13338 }
13339 }
13340 if (bdrawn) continue; // the station loop
13341
13342 LLBBox this_box;
13343 this_box.Set(lat, lon, lat, lon);
13344 this_box.EnLarge(.005);
13345 drawn_boxes.push_back(this_box);
13346 }
13347
13348 wxPoint r;
13349 GetCanvasPointPix(lat, lon, &r);
13350 // draw standard icons
13351 if (GetVP().chart_scale > 500000) {
13352 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13353 }
13354 // draw "extended" icons
13355 else {
13356 dc.SetFont(*plabelFont);
13357 {
13358 {
13359 float val, nowlev;
13360 float ltleve = 0.;
13361 float htleve = 0.;
13362 time_t tctime;
13363 time_t lttime = 0;
13364 time_t httime = 0;
13365 bool wt;
13366 // define if flood or ebb in the last ten minutes and verify if
13367 // data are useable
13368 if (ptcmgr->GetTideFlowSens(
13369 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13370 pIDX->IDX_rec_num, nowlev, val, wt)) {
13371 // search forward the first HW or LW near "now" ( starting at
13372 // "now" - ten minutes )
13373 ptcmgr->GetHightOrLowTide(
13374 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13375 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13376 wt, pIDX->IDX_rec_num, val, tctime);
13377 if (wt) {
13378 httime = tctime;
13379 htleve = val;
13380 } else {
13381 lttime = tctime;
13382 ltleve = val;
13383 }
13384 wt = !wt;
13385
13386 // then search opposite tide near "now"
13387 if (tctime > t_this_now) // search backward
13388 ptcmgr->GetHightOrLowTide(
13389 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13390 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13391 pIDX->IDX_rec_num, val, tctime);
13392 else
13393 // or search forward
13394 ptcmgr->GetHightOrLowTide(
13395 t_this_now, FORWARD_TEN_MINUTES_STEP,
13396 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13397 val, tctime);
13398 if (wt) {
13399 httime = tctime;
13400 htleve = val;
13401 } else {
13402 lttime = tctime;
13403 ltleve = val;
13404 }
13405
13406 // draw the tide rectangle:
13407
13408 // tide icon rectangle has default pre-scaled width = 12 ,
13409 // height = 45
13410 int width = (int)(12 * scale_factor + 0.5);
13411 int height = (int)(45 * scale_factor + 0.5);
13412 int linew = wxMax(1, (int)(scale_factor));
13413 int xDraw = r.x - (width / 2);
13414 int yDraw = r.y - (height / 2);
13415
13416 // process tide state ( %height and flow sens )
13417 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13418 int hs = (httime > lttime) ? -4 : 4;
13419 hs *= (int)(scale_factor + 0.5);
13420 if (ts > 0.995 || ts < 0.005) hs = 0;
13421 int ht_y = (int)(height * ts);
13422
13423 // draw yellow tide rectangle outlined in black
13424 pblack_pen->SetWidth(linew);
13425 dc.SetPen(*pblack_pen);
13426 dc.SetBrush(*pyelo_brush);
13427 dc.DrawRectangle(xDraw, yDraw, width, height);
13428
13429 // draw blue rectangle as water height, smaller in width than
13430 // yellow rectangle
13431 dc.SetPen(*pblue_pen);
13432 dc.SetBrush(*pblue_brush);
13433 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13434 (width - (4 * linew)), height - ht_y);
13435
13436 // draw sens arrows (ensure they are not "under-drawn" by top
13437 // line of blue rectangle )
13438 int hl;
13439 wxPoint arrow[3];
13440 arrow[0].x = xDraw + 2 * linew;
13441 arrow[1].x = xDraw + width / 2;
13442 arrow[2].x = xDraw + width - 2 * linew;
13443 pyelo_pen->SetWidth(linew);
13444 pblue_pen->SetWidth(linew);
13445 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13446 {
13447 hl = (int)(height * 0.25) + yDraw;
13448 arrow[0].y = hl;
13449 arrow[1].y = hl + hs;
13450 arrow[2].y = hl;
13451 if (ts < 0.15)
13452 dc.SetPen(*pyelo_pen);
13453 else
13454 dc.SetPen(*pblue_pen);
13455 dc.DrawLines(3, arrow);
13456 }
13457 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13458 {
13459 hl = (int)(height * 0.5) + yDraw;
13460 arrow[0].y = hl;
13461 arrow[1].y = hl + hs;
13462 arrow[2].y = hl;
13463 if (ts < 0.40)
13464 dc.SetPen(*pyelo_pen);
13465 else
13466 dc.SetPen(*pblue_pen);
13467 dc.DrawLines(3, arrow);
13468 }
13469 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13470 {
13471 hl = (int)(height * 0.75) + yDraw;
13472 arrow[0].y = hl;
13473 arrow[1].y = hl + hs;
13474 arrow[2].y = hl;
13475 if (ts < 0.65)
13476 dc.SetPen(*pyelo_pen);
13477 else
13478 dc.SetPen(*pblue_pen);
13479 dc.DrawLines(3, arrow);
13480 }
13481 // draw tide level text
13482 wxString s;
13483 s.Printf("%3.1f", nowlev);
13484 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13485 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13486 int wx1;
13487 dc.GetTextExtent(s, &wx1, NULL);
13488 wx1 *= g_Platform->GetDisplayDIPMult(this);
13489 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13490 }
13491 }
13492 }
13493 }
13494 }
13495 }
13496 }
13497 }
13498}
13499
13500//------------------------------------------------------------------------------------------
13501// Currents Support
13502//------------------------------------------------------------------------------------------
13503
13504void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13505 if (!ptcmgr) return;
13506
13507 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13508
13509 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13510 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13511 double lon = pIDX->IDX_lon;
13512 double lat = pIDX->IDX_lat;
13513
13514 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13515 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13516 if ((BBox.Contains(lat, lon))) {
13517 // Manage the point selection list
13518 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13519 }
13520 }
13521 }
13522}
13523
13524void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13525 if (!ptcmgr) return;
13526
13527 float tcvalue, dir;
13528 bool bnew_val;
13529 char sbuf[20];
13530 wxFont *pTCFont;
13531 double lon_last = 0.;
13532 double lat_last = 0.;
13533 // arrow size for Raz Blanchard : 12 knots north
13534 double marge = 0.2;
13535 bool cur_time = !gTimeSource.IsValid();
13536
13537 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13538 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13539
13540 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13541 wxPENSTYLE_SOLID);
13542 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13543 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13544 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13545 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13546 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13547 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13548 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13549 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13550
13551 double skew_angle = GetVPRotation();
13552
13553 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13554 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13555 int font_size = wxMax(10, dFont->GetPointSize());
13556 font_size /= g_Platform->GetDisplayDIPMult(this);
13557 pTCFont = FontMgr::Get().FindOrCreateFont(
13558 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13559 false, dFont->GetFaceName());
13560
13561 float scale_factor = 1.0;
13562
13563 // Set the onscreen size of the symbol
13564 // Current report graphic is scaled by the text size of the label in use
13565 wxScreenDC sdc;
13566 int height;
13567 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13568 height *= g_Platform->GetDisplayDIPMult(this);
13569 float nominal_icon_size_pixels = 15;
13570 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13571
13572 scale_factor *= pix_factor;
13573
13574 float user_scale_factor = g_ChartScaleFactorExp;
13575 if (g_ChartScaleFactorExp > 1.0)
13576 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13577 1.2; // soften the scale factor a bit
13578
13579 scale_factor *= user_scale_factor;
13580
13581 scale_factor *= GetContentScaleFactor();
13582
13583 {
13584 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13585 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13586 double lon = pIDX->IDX_lon;
13587 double lat = pIDX->IDX_lat;
13588
13589 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13590 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13591 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13592 wxPoint r;
13593 GetCanvasPointPix(lat, lon, &r);
13594
13595 wxPoint d[4]; // points of a diamond at the current station location
13596 int dd = (int)(5.0 * scale_factor + 0.5);
13597 d[0].x = r.x;
13598 d[0].y = r.y + dd;
13599 d[1].x = r.x + dd;
13600 d[1].y = r.y;
13601 d[2].x = r.x;
13602 d[2].y = r.y - dd;
13603 d[3].x = r.x - dd;
13604 d[3].y = r.y;
13605
13606 if (1) {
13607 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13608 dc.SetPen(*pblack_pen);
13609 dc.SetBrush(*porange_brush);
13610 dc.DrawPolygon(4, d);
13611
13612 if (type == 'C') {
13613 dc.SetBrush(*pblack_brush);
13614 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13615 }
13616
13617 if (GetVP().chart_scale < 1000000) {
13618 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13619 continue;
13620 } else
13621 continue;
13622
13623 if (1 /*type == 'c'*/) {
13624 {
13625 // Get the display pixel location of the current station
13626 int pixxc, pixyc;
13627 pixxc = r.x;
13628 pixyc = r.y;
13629
13630 // Adjust drawing size using logarithmic scale. tcvalue is
13631 // current in knots
13632 double a1 = fabs(tcvalue) * 10.;
13633 // Current values <= 0.1 knot will have no arrow
13634 a1 = wxMax(1.0, a1);
13635 double a2 = log10(a1);
13636
13637 float cscale = scale_factor * a2 * 0.3;
13638
13639 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13640 dc.SetPen(*porange_pen);
13641 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13642 cscale);
13643 // Draw text, if enabled
13644
13645 if (bDrawCurrentValues) {
13646 dc.SetFont(*pTCFont);
13647 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13648 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13649 }
13650 }
13651 } // scale
13652 }
13653 /* This is useful for debugging the TC database
13654 else
13655 {
13656 dc.SetPen ( *porange_pen );
13657 dc.SetBrush ( *pgray_brush );
13658 dc.DrawPolygon ( 4, d );
13659 }
13660 */
13661 }
13662 lon_last = lon;
13663 lat_last = lat;
13664 }
13665 }
13666 }
13667}
13668
13669void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13670 ShowSingleTideDialog(x, y, pvIDX);
13671}
13672
13673void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13674 if (!pvIDX) return; // Validate input
13675
13676 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13677
13678 // Check if a tide dialog is already open and visible
13679 if (pCwin && pCwin->IsShown()) {
13680 // Same tide station: bring existing dialog to front (preserves user
13681 // context)
13682 if (pCwin->GetCurrentIDX() == pNewIDX) {
13683 pCwin->Raise();
13684 pCwin->SetFocus();
13685
13686 // Provide subtle visual feedback that dialog is already open
13687 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13688 return;
13689 }
13690
13691 // Different tide station: close current dialog before opening new one
13692 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13693 }
13694
13695 if (pCwin) {
13696 // This shouldn't happen but ensures clean state
13697 pCwin->Destroy();
13698 pCwin = NULL;
13699 }
13700
13701 // Create and display new tide dialog
13702 pCwin = new TCWin(this, x, y, pvIDX);
13703
13704 // Ensure the dialog is properly shown and focused
13705 if (pCwin) {
13706 pCwin->Show();
13707 pCwin->Raise();
13708 pCwin->SetFocus();
13709 }
13710}
13711
13712bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13713
13715 if (pCwin) {
13716 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13717 }
13718}
13719
13720#define NUM_CURRENT_ARROW_POINTS 9
13721static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13722 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13723 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13724 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13725
13726void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13727 double scale) {
13728 if (scale > 1e-2) {
13729 float sin_rot = sin(rot_angle * PI / 180.);
13730 float cos_rot = cos(rot_angle * PI / 180.);
13731
13732 // Move to the first point
13733
13734 float xt = CurrentArrowArray[0].x;
13735 float yt = CurrentArrowArray[0].y;
13736
13737 float xp = (xt * cos_rot) - (yt * sin_rot);
13738 float yp = (xt * sin_rot) + (yt * cos_rot);
13739 int x1 = (int)(xp * scale);
13740 int y1 = (int)(yp * scale);
13741
13742 // Walk thru the point list
13743 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13744 xt = CurrentArrowArray[ip].x;
13745 yt = CurrentArrowArray[ip].y;
13746
13747 float xp = (xt * cos_rot) - (yt * sin_rot);
13748 float yp = (xt * sin_rot) + (yt * cos_rot);
13749 int x2 = (int)(xp * scale);
13750 int y2 = (int)(yp * scale);
13751
13752 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13753
13754 x1 = x2;
13755 y1 = y2;
13756 }
13757 }
13758}
13759
13760wxString ChartCanvas::FindValidUploadPort() {
13761 wxString port;
13762 // Try to use the saved persistent upload port first
13763 if (!g_uploadConnection.IsEmpty() &&
13764 g_uploadConnection.StartsWith("Serial")) {
13765 port = g_uploadConnection;
13766 }
13767
13768 else {
13769 // If there is no persistent upload port recorded (yet)
13770 // then use the first available serial connection which has output defined.
13771 for (auto *cp : TheConnectionParams()) {
13772 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13773 port << "Serial:" << cp->Port;
13774 }
13775 }
13776 return port;
13777}
13778
13779void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13780 if (!win) return;
13781
13782 if (NULL == g_pais_query_dialog_active) {
13783 int pos_x = g_ais_query_dialog_x;
13784 int pos_y = g_ais_query_dialog_y;
13785
13786 if (g_pais_query_dialog_active) {
13787 g_pais_query_dialog_active->Destroy();
13788 g_pais_query_dialog_active = new AISTargetQueryDialog();
13789 } else {
13790 g_pais_query_dialog_active = new AISTargetQueryDialog();
13791 }
13792
13793 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13794 wxPoint(pos_x, pos_y));
13795
13796 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13797 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13798 g_pais_query_dialog_active->SetMMSI(mmsi);
13799 g_pais_query_dialog_active->UpdateText();
13800 wxSize sz = g_pais_query_dialog_active->GetSize();
13801
13802 bool b_reset_pos = false;
13803#ifdef __WXMSW__
13804 // Support MultiMonitor setups which an allow negative window positions.
13805 // If the requested window title bar does not intersect any installed
13806 // monitor, then default to simple primary monitor positioning.
13807 RECT frame_title_rect;
13808 frame_title_rect.left = pos_x;
13809 frame_title_rect.top = pos_y;
13810 frame_title_rect.right = pos_x + sz.x;
13811 frame_title_rect.bottom = pos_y + 30;
13812
13813 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13814 b_reset_pos = true;
13815#else
13816
13817 // Make sure drag bar (title bar) of window intersects wxClient Area of
13818 // screen, with a little slop...
13819 wxRect window_title_rect; // conservative estimate
13820 window_title_rect.x = pos_x;
13821 window_title_rect.y = pos_y;
13822 window_title_rect.width = sz.x;
13823 window_title_rect.height = 30;
13824
13825 wxRect ClientRect = wxGetClientDisplayRect();
13826 ClientRect.Deflate(
13827 60, 60); // Prevent the new window from being too close to the edge
13828 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13829
13830#endif
13831
13832 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13833
13834 } else {
13835 g_pais_query_dialog_active->SetMMSI(mmsi);
13836 g_pais_query_dialog_active->UpdateText();
13837 }
13838
13839 g_pais_query_dialog_active->Show();
13840}
13841
13842void ChartCanvas::ToggleCanvasQuiltMode() {
13843 bool cur_mode = GetQuiltMode();
13844
13845 if (!GetQuiltMode())
13846 SetQuiltMode(true);
13847 else if (GetQuiltMode()) {
13848 SetQuiltMode(false);
13849 g_sticky_chart = GetQuiltReferenceChartIndex();
13850 }
13851
13852 if (cur_mode != GetQuiltMode()) {
13853 SetupCanvasQuiltMode();
13854 DoCanvasUpdate();
13855 InvalidateGL();
13856 Refresh();
13857 }
13858 // TODO What to do about this?
13859 // g_bQuiltEnable = GetQuiltMode();
13860
13861 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13862 if (ps52plib) ps52plib->GenerateStateHash();
13863
13864 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13865 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13866}
13867
13868void ChartCanvas::DoCanvasStackDelta(int direction) {
13869 if (!GetQuiltMode()) {
13870 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13871 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13872 if ((current_stack_index + direction) < 0) return;
13873
13874 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13875 int new_dbIndex =
13876 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13877
13878 if (IsChartQuiltableRef(new_dbIndex)) {
13879 ToggleCanvasQuiltMode();
13880 SelectQuiltRefdbChart(new_dbIndex);
13881 m_bpersistent_quilt = false;
13882 }
13883 } else {
13884 SelectChartFromStack(current_stack_index + direction);
13885 }
13886 } else {
13887 std::vector<int> piano_chart_index_array =
13888 GetQuiltExtendedStackdbIndexArray();
13889 int refdb = GetQuiltRefChartdbIndex();
13890
13891 // Find the ref chart in the stack
13892 int current_index = -1;
13893 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13894 if (refdb == piano_chart_index_array[i]) {
13895 current_index = i;
13896 break;
13897 }
13898 }
13899 if (current_index == -1) return;
13900
13901 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13902 int target_family = ctet.GetChartFamily();
13903
13904 int new_index = -1;
13905 int check_index = current_index + direction;
13906 bool found = false;
13907 int check_dbIndex = -1;
13908 int new_dbIndex = -1;
13909
13910 // When quilted. switch within the same chart family
13911 while (!found &&
13912 (unsigned int)check_index < piano_chart_index_array.size() &&
13913 (check_index >= 0)) {
13914 check_dbIndex = piano_chart_index_array[check_index];
13915 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13916 if (target_family == cte.GetChartFamily()) {
13917 found = true;
13918 new_index = check_index;
13919 new_dbIndex = check_dbIndex;
13920 break;
13921 }
13922
13923 check_index += direction;
13924 }
13925
13926 if (!found) return;
13927
13928 if (!IsChartQuiltableRef(new_dbIndex)) {
13929 ToggleCanvasQuiltMode();
13930 SelectdbChart(new_dbIndex);
13931 m_bpersistent_quilt = true;
13932 } else {
13933 SelectQuiltRefChart(new_index);
13934 }
13935 }
13936
13937 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13938 // (checkmarks etc)
13939 SetQuiltChartHiLiteIndex(-1);
13940
13941 ReloadVP();
13942}
13943
13944//--------------------------------------------------------------------------------------------------------
13945//
13946// Toolbar support
13947//
13948//--------------------------------------------------------------------------------------------------------
13949
13950void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13951 // Handle the per-canvas toolbar clicks here
13952
13953 switch (event.GetId()) {
13954 case ID_ZOOMIN: {
13955 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13956 break;
13957 }
13958
13959 case ID_ZOOMOUT: {
13960 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13961 break;
13962 }
13963
13964 case ID_STKUP:
13965 DoCanvasStackDelta(1);
13966 DoCanvasUpdate();
13967 break;
13968
13969 case ID_STKDN:
13970 DoCanvasStackDelta(-1);
13971 DoCanvasUpdate();
13972 break;
13973
13974 case ID_FOLLOW: {
13975 TogglebFollow();
13976 break;
13977 }
13978
13979 case ID_CURRENT: {
13980 ShowCurrents(!GetbShowCurrent());
13981 ReloadVP();
13982 Refresh(false);
13983 break;
13984 }
13985
13986 case ID_TIDE: {
13987 ShowTides(!GetbShowTide());
13988 ReloadVP();
13989 Refresh(false);
13990 break;
13991 }
13992
13993 case ID_ROUTE: {
13994 if (0 == m_routeState) {
13995 StartRoute();
13996 } else {
13997 FinishRoute();
13998 }
13999
14000#ifdef __ANDROID__
14001 androidSetRouteAnnunciator(m_routeState == 1);
14002#endif
14003 break;
14004 }
14005
14006 case ID_AIS: {
14007 SetAISCanvasDisplayStyle(-1);
14008 break;
14009 }
14010
14011 default:
14012 break;
14013 }
14014
14015 // And then let gFrame handle the rest....
14016 event.Skip();
14017}
14018
14019void ChartCanvas::SetShowAIS(bool show) {
14020 m_bShowAIS = show;
14021 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14022 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14023}
14024
14025void ChartCanvas::SetAttenAIS(bool show) {
14026 m_bShowAISScaled = show;
14027 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14028 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14029}
14030
14031void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14032 // make some arrays to hold the dfferences between cycle steps
14033 // show all, scaled, hide all
14034 bool bShowAIS_Array[3] = {true, true, false};
14035 bool bShowScaled_Array[3] = {false, true, true};
14036 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14037 _("Attenuate less critical AIS targets"),
14038 _("Hide AIS Targets")};
14039 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14040 int ArraySize = 3;
14041 int AIS_Toolbar_Switch = 0;
14042 if (StyleIndx == -1) { // -1 means coming from toolbar button
14043 // find current state of switch
14044 for (int i = 1; i < ArraySize; i++) {
14045 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14046 (bShowScaled_Array[i] == m_bShowAISScaled))
14047 AIS_Toolbar_Switch = i;
14048 }
14049 AIS_Toolbar_Switch++; // we did click so continu with next item
14050 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14051 AIS_Toolbar_Switch++;
14052
14053 } else { // coming from menu bar.
14054 AIS_Toolbar_Switch = StyleIndx;
14055 }
14056 // make sure we are not above array
14057 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14058
14059 int AIS_Toolbar_Switch_Next =
14060 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14061 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14062 AIS_Toolbar_Switch_Next++;
14063 if (AIS_Toolbar_Switch_Next >= ArraySize)
14064 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14065
14066 // Set found values to global and member variables
14067 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14068 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14069}
14070
14071void ChartCanvas::TouchAISToolActive() {}
14072
14073void ChartCanvas::UpdateAISTBTool() {}
14074
14075//---------------------------------------------------------------------------------
14076//
14077// Compass/GPS status icon support
14078//
14079//---------------------------------------------------------------------------------
14080
14081void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14082 // Look for change in overlap or positions
14083 bool b_update = false;
14084 int cc1_edge_comp = 2;
14085 wxRect rect = m_Compass->GetRect();
14086 wxSize parent_size = GetSize();
14087
14088 parent_size *= m_displayScale;
14089
14090 // check to see if it would overlap if it was in its home position (upper
14091 // right)
14092 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14093 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14094 wxRect compass_rect(compass_pt, rect.GetSize());
14095
14096 m_Compass->Move(compass_pt);
14097
14098 if (m_Compass && m_Compass->IsShown())
14099 m_Compass->UpdateStatus(b_force_new | b_update);
14100
14101 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14102 scaler = wxMax(scaler, 1.0);
14103 wxPoint note_point = wxPoint(
14104 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14105 if (m_notification_button) {
14106 m_notification_button->Move(note_point);
14107 m_notification_button->UpdateStatus();
14108 }
14109
14110 if (b_force_new | b_update) Refresh();
14111}
14112
14113void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14114 ChartTypeEnum New_Type,
14115 ChartFamilyEnum New_Family) {
14116 if (!GetpCurrentStack()) return;
14117 if (!ChartData) return;
14118
14119 if (index < GetpCurrentStack()->nEntry) {
14120 // Open the new chart
14121 ChartBase *pTentative_Chart;
14122 pTentative_Chart = ChartData->OpenStackChartConditional(
14123 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14124
14125 if (pTentative_Chart) {
14126 if (m_singleChart) m_singleChart->Deactivate();
14127
14128 m_singleChart = pTentative_Chart;
14129 m_singleChart->Activate();
14130
14131 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14132 GetpCurrentStack(), m_singleChart->GetFullPath());
14133 }
14134
14135 // Setup the view
14136 double zLat, zLon;
14137 if (m_bFollow) {
14138 zLat = gLat;
14139 zLon = gLon;
14140 } else {
14141 zLat = m_vLat;
14142 zLon = m_vLon;
14143 }
14144
14145 double best_scale_ppm = GetBestVPScale(m_singleChart);
14146 double rotation = GetVPRotation();
14147 double oldskew = GetVPSkew();
14148 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14149
14150 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14151 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14152 if (fabs(newskew) > 0.0001) rotation = newskew;
14153 }
14154
14155 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14156
14157 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14158 }
14159
14160 // refresh Piano
14161 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14162 if (idx < 0) return;
14163
14164 std::vector<int> piano_active_chart_index_array;
14165 piano_active_chart_index_array.push_back(
14166 GetpCurrentStack()->GetCurrentEntrydbIndex());
14167 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14168}
14169
14170void ChartCanvas::SelectdbChart(int dbindex) {
14171 if (!GetpCurrentStack()) return;
14172 if (!ChartData) return;
14173
14174 if (dbindex >= 0) {
14175 // Open the new chart
14176 ChartBase *pTentative_Chart;
14177 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14178
14179 if (pTentative_Chart) {
14180 if (m_singleChart) m_singleChart->Deactivate();
14181
14182 m_singleChart = pTentative_Chart;
14183 m_singleChart->Activate();
14184
14185 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14186 GetpCurrentStack(), m_singleChart->GetFullPath());
14187 }
14188
14189 // Setup the view
14190 double zLat, zLon;
14191 if (m_bFollow) {
14192 zLat = gLat;
14193 zLon = gLon;
14194 } else {
14195 zLat = m_vLat;
14196 zLon = m_vLon;
14197 }
14198
14199 double best_scale_ppm = GetBestVPScale(m_singleChart);
14200
14201 if (m_singleChart)
14202 SetViewPoint(zLat, zLon, best_scale_ppm,
14203 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14204
14205 // SetChartUpdatePeriod( );
14206
14207 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14208 }
14209
14210 // TODO refresh_Piano();
14211}
14212
14213void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14214 double target_scale = GetVP().view_scale_ppm;
14215
14216 if (!GetQuiltMode()) {
14217 if (GetpCurrentStack()) {
14218 int stack_index = -1;
14219 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14220 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14221 if (check_dbIndex < 0) continue;
14222 const ChartTableEntry &cte =
14223 ChartData->GetChartTableEntry(check_dbIndex);
14224 if (type == cte.GetChartType()) {
14225 stack_index = i;
14226 break;
14227 } else if (family == cte.GetChartFamily()) {
14228 stack_index = i;
14229 break;
14230 }
14231 }
14232
14233 if (stack_index >= 0) {
14234 SelectChartFromStack(stack_index);
14235 }
14236 }
14237 } else {
14238 int sel_dbIndex = -1;
14239 std::vector<int> piano_chart_index_array =
14240 GetQuiltExtendedStackdbIndexArray();
14241 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14242 int check_dbIndex = piano_chart_index_array[i];
14243 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14244 if (type == cte.GetChartType()) {
14245 if (IsChartQuiltableRef(check_dbIndex)) {
14246 sel_dbIndex = check_dbIndex;
14247 break;
14248 }
14249 } else if (family == cte.GetChartFamily()) {
14250 if (IsChartQuiltableRef(check_dbIndex)) {
14251 sel_dbIndex = check_dbIndex;
14252 break;
14253 }
14254 }
14255 }
14256
14257 if (sel_dbIndex >= 0) {
14258 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14259 // Re-qualify the quilt reference chart selection
14260 AdjustQuiltRefChart();
14261 }
14262
14263 // Now reset the scale to the target...
14264 SetVPScale(target_scale);
14265 }
14266
14267 SetQuiltChartHiLiteIndex(-1);
14268
14269 ReloadVP();
14270}
14271
14272bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14273 return std::find(m_tile_yesshow_index_array.begin(),
14274 m_tile_yesshow_index_array.end(),
14275 index) != m_tile_yesshow_index_array.end();
14276}
14277
14278bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14279 return std::find(m_tile_noshow_index_array.begin(),
14280 m_tile_noshow_index_array.end(),
14281 index) != m_tile_noshow_index_array.end();
14282}
14283
14284void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14285 if (std::find(m_tile_noshow_index_array.begin(),
14286 m_tile_noshow_index_array.end(),
14287 index) == m_tile_noshow_index_array.end()) {
14288 m_tile_noshow_index_array.push_back(index);
14289 }
14290}
14291
14292//-------------------------------------------------------------------------------------------------------
14293//
14294// Piano support
14295//
14296//-------------------------------------------------------------------------------------------------------
14297
14298void ChartCanvas::HandlePianoClick(
14299 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14300 if (g_options && g_options->IsShown())
14301 return; // Piano might be invalid due to chartset updates.
14302 if (!m_pCurrentStack) return;
14303 if (!ChartData) return;
14304
14305 // stop movement or on slow computer we may get something like :
14306 // zoom out with the wheel (timer is set)
14307 // quickly click and display a chart, which may zoom in
14308 // but the delayed timer fires first and it zooms out again!
14309 StopMovement();
14310
14311 // When switching by piano key click, we may appoint the new target chart to
14312 // be any chart in the composite array.
14313 // As an improvement to UX, find the chart that is "closest" to the current
14314 // vp,
14315 // and select that chart. This will cause a jump to the centroid of that
14316 // chart
14317
14318 double distance = 25000; // RTW
14319 int closest_index = -1;
14320 for (int chart_index : selected_dbIndex_array) {
14321 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14322 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14323 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14324
14325 // measure distance as Manhattan style
14326 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14327 if (test_distance < distance) {
14328 distance = test_distance;
14329 closest_index = chart_index;
14330 }
14331 }
14332
14333 int selected_dbIndex = selected_dbIndex_array[0];
14334 if (closest_index >= 0) selected_dbIndex = closest_index;
14335
14336 if (!GetQuiltMode()) {
14337 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14338 if (IsChartQuiltableRef(selected_dbIndex)) {
14339 ToggleCanvasQuiltMode();
14340 SelectQuiltRefdbChart(selected_dbIndex);
14341 m_bpersistent_quilt = false;
14342 } else {
14343 SelectChartFromStack(selected_index);
14344 }
14345 } else {
14346 SelectChartFromStack(selected_index);
14347 g_sticky_chart = selected_dbIndex;
14348 }
14349
14350 if (m_singleChart)
14351 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14352 } else {
14353 // Handle MBTiles overlays first
14354 // Left click simply toggles the noshow array index entry
14355 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14356 bool bfound = false;
14357 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14358 if (m_tile_noshow_index_array[i] ==
14359 selected_dbIndex) { // chart is in the noshow list
14360 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14361 i); // erase it
14362 bfound = true;
14363 break;
14364 }
14365 }
14366 if (!bfound) {
14367 m_tile_noshow_index_array.push_back(selected_dbIndex);
14368 }
14369
14370 // If not already present, add this tileset to the "yes_show" array.
14371 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14372 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14373 }
14374
14375 else {
14376 if (IsChartQuiltableRef(selected_dbIndex)) {
14377 // if( ChartData ) ChartData->PurgeCache();
14378
14379 // If the chart is a vector chart, and of very large scale,
14380 // then we had better set the new scale directly to avoid excessive
14381 // underzoom on, eg, Inland ENCs
14382 bool set_scale = false;
14383 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14384 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14385 set_scale = true;
14386 }
14387 }
14388
14389 if (!set_scale) {
14390 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14391 } else {
14392 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14393
14394 // Adjust scale so that the selected chart is underzoomed/overzoomed
14395 // by a controlled amount
14396 ChartBase *pc =
14397 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14398 if (pc) {
14399 double proposed_scale_onscreen =
14401
14402 if (g_bPreserveScaleOnX) {
14403 proposed_scale_onscreen =
14404 wxMin(proposed_scale_onscreen,
14405 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14406 GetCanvasWidth()));
14407 } else {
14408 proposed_scale_onscreen =
14409 wxMin(proposed_scale_onscreen,
14410 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14411 GetCanvasWidth()));
14412
14413 proposed_scale_onscreen =
14414 wxMax(proposed_scale_onscreen,
14415 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14417 }
14418
14419 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14420 }
14421 }
14422 } else {
14423 ToggleCanvasQuiltMode();
14424 SelectdbChart(selected_dbIndex);
14425 m_bpersistent_quilt = true;
14426 }
14427 }
14428 }
14429
14430 SetQuiltChartHiLiteIndex(-1);
14431 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14432 // (checkmarks etc)
14433 HideChartInfoWindow();
14434 DoCanvasUpdate();
14435 ReloadVP(); // Pick up the new selections
14436}
14437
14438void ChartCanvas::HandlePianoRClick(
14439 int x, int y, int selected_index,
14440 const std::vector<int> &selected_dbIndex_array) {
14441 if (g_options && g_options->IsShown())
14442 return; // Piano might be invalid due to chartset updates.
14443 if (!GetpCurrentStack()) return;
14444
14445 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14446 UpdateCanvasControlBar();
14447
14448 SetQuiltChartHiLiteIndex(-1);
14449}
14450
14451void ChartCanvas::HandlePianoRollover(
14452 int selected_index, const std::vector<int> &selected_dbIndex_array,
14453 int n_charts, int scale) {
14454 if (g_options && g_options->IsShown())
14455 return; // Piano might be invalid due to chartset updates.
14456 if (!GetpCurrentStack()) return;
14457 if (!ChartData) return;
14458
14459 if (ChartData->IsBusy()) return;
14460
14461 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14462
14463 if (!GetQuiltMode()) {
14464 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14465 } else {
14466 // Select the correct vector
14467 std::vector<int> piano_chart_index_array;
14468 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14469 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14470 if ((GetpCurrentStack()->nEntry > 1) ||
14471 (piano_chart_index_array.size() >= 1)) {
14472 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14473
14474 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14475 ReloadVP(false); // no VP adjustment allowed
14476 } else if (GetpCurrentStack()->nEntry == 1) {
14477 const ChartTableEntry &cte =
14478 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14479 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14480 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14481 ReloadVP(false);
14482 } else if ((-1 == selected_index) &&
14483 (0 == selected_dbIndex_array.size())) {
14484 ShowChartInfoWindow(key_location.x, -1);
14485 }
14486 }
14487 } else {
14488 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14489
14490 if ((GetpCurrentStack()->nEntry > 1) ||
14491 (piano_chart_index_array.size() >= 1)) {
14492 if (n_charts > 1)
14493 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14494 selected_dbIndex_array);
14495 else if (n_charts == 1)
14496 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14497
14498 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14499 ReloadVP(false); // no VP adjustment allowed
14500 }
14501 }
14502 }
14503}
14504
14505void ChartCanvas::ClearPianoRollover() {
14506 ClearQuiltChartHiLiteIndexArray();
14507 ShowChartInfoWindow(0, -1);
14508 std::vector<int> vec;
14509 ShowCompositeInfoWindow(0, 0, 0, vec);
14510 ReloadVP(false);
14511}
14512
14513void ChartCanvas::UpdateCanvasControlBar() {
14514 if (m_pianoFrozen) return;
14515
14516 if (!GetpCurrentStack()) return;
14517 if (!ChartData) return;
14518 if (!g_bShowChartBar) return;
14519
14520 int sel_type = -1;
14521 int sel_family = -1;
14522
14523 std::vector<int> piano_chart_index_array;
14524 std::vector<int> empty_piano_chart_index_array;
14525
14526 wxString old_hash = m_Piano->GetStoredHash();
14527
14528 if (GetQuiltMode()) {
14529 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14530 GetQuiltFullScreendbIndexArray());
14531
14532 std::vector<int> piano_active_chart_index_array =
14533 GetQuiltCandidatedbIndexArray();
14534 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14535
14536 std::vector<int> piano_eclipsed_chart_index_array =
14537 GetQuiltEclipsedStackdbIndexArray();
14538 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14539
14540 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14541 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14542
14543 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14544 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14545 } else {
14546 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14547 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14548 // TODO refresh_Piano();
14549
14550 if (m_singleChart) {
14551 sel_type = m_singleChart->GetChartType();
14552 sel_family = m_singleChart->GetChartFamily();
14553 }
14554 }
14555
14556 // Set up the TMerc and Skew arrays
14557 std::vector<int> piano_skew_chart_index_array;
14558 std::vector<int> piano_tmerc_chart_index_array;
14559 std::vector<int> piano_poly_chart_index_array;
14560
14561 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14562 const ChartTableEntry &ctei =
14563 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14564 double skew_norm = ctei.GetChartSkew();
14565 if (skew_norm > 180.) skew_norm -= 360.;
14566
14567 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14568 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14569
14570 // Polyconic skewed charts should show as skewed
14571 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14572 if (fabs(skew_norm) > 1.)
14573 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14574 else
14575 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14576 } else if (fabs(skew_norm) > 1.)
14577 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14578 }
14579 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14580 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14581 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14582
14583 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14584 if (new_hash != old_hash) {
14585 m_Piano->FormatKeys();
14586 HideChartInfoWindow();
14587 m_Piano->ResetRollover();
14588 SetQuiltChartHiLiteIndex(-1);
14589 m_brepaint_piano = true;
14590 }
14591
14592 // Create a bitmask int that describes what Family/Type of charts are shown in
14593 // the bar, and notify the platform.
14594 int mask = 0;
14595 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14596 const ChartTableEntry &ctei =
14597 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14598 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14599 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14600 if (e == CHART_FAMILY_RASTER) mask |= 1;
14601 if (e == CHART_FAMILY_VECTOR) {
14602 if (t == CHART_TYPE_CM93COMP)
14603 mask |= 4;
14604 else
14605 mask |= 2;
14606 }
14607 }
14608
14609 wxString s_indicated;
14610 if (sel_type == CHART_TYPE_CM93COMP)
14611 s_indicated = "cm93";
14612 else {
14613 if (sel_family == CHART_FAMILY_RASTER)
14614 s_indicated = "raster";
14615 else if (sel_family == CHART_FAMILY_VECTOR)
14616 s_indicated = "vector";
14617 }
14618
14619 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14620}
14621
14622void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14623
14624void ChartCanvas::PianoPopupMenu(
14625 int x, int y, int selected_index,
14626 const std::vector<int> &selected_dbIndex_array) {
14627 if (!GetpCurrentStack()) return;
14628
14629 // No context menu if quilting is disabled
14630 if (!GetQuiltMode()) return;
14631
14632 m_piano_ctx_menu = new wxMenu();
14633
14634 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14635 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14636 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14637 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14638 } else {
14639 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14640 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14641 // wxEVT_COMMAND_MENU_SELECTED,
14642 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14643
14644 menu_selected_dbIndex = selected_dbIndex_array[0];
14645 menu_selected_index = selected_index;
14646
14647 // Search the no-show array
14648 bool b_is_in_noshow = false;
14649 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14650 if (m_quilt_noshow_index_array[i] ==
14651 menu_selected_dbIndex) // chart is in the noshow list
14652 {
14653 b_is_in_noshow = true;
14654 break;
14655 }
14656 }
14657
14658 if (b_is_in_noshow) {
14659 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14660 _("Show This Chart"));
14661 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14662 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14663 } else if (GetpCurrentStack()->nEntry > 1) {
14664 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14665 _("Hide This Chart"));
14666 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14667 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14668 }
14669 }
14670
14671 wxPoint pos = wxPoint(x, y - 30);
14672
14673 // Invoke the drop-down menu
14674 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14675 PopupMenu(m_piano_ctx_menu, pos);
14676
14677 delete m_piano_ctx_menu;
14678 m_piano_ctx_menu = NULL;
14679
14680 HideChartInfoWindow();
14681 m_Piano->ResetRollover();
14682
14683 SetQuiltChartHiLiteIndex(-1);
14684 ClearQuiltChartHiLiteIndexArray();
14685
14686 ReloadVP();
14687}
14688
14689void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14690 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14691 if (m_quilt_noshow_index_array[i] ==
14692 menu_selected_dbIndex) // chart is in the noshow list
14693 {
14694 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14695 break;
14696 }
14697 }
14698}
14699
14700void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14701 if (!GetpCurrentStack()) return;
14702 if (!ChartData) return;
14703
14704 RemoveChartFromQuilt(menu_selected_dbIndex);
14705
14706 // It could happen that the chart being disabled is the reference
14707 // chart....
14708 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14709 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14710
14711 int i = menu_selected_index + 1; // select next smaller scale chart
14712 bool b_success = false;
14713 while (i < GetpCurrentStack()->nEntry - 1) {
14714 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14715 if (type == ChartData->GetDBChartType(dbIndex)) {
14716 SelectQuiltRefChart(i);
14717 b_success = true;
14718 break;
14719 }
14720 i++;
14721 }
14722
14723 // If that did not work, try to select the next larger scale compatible
14724 // chart
14725 if (!b_success) {
14726 i = menu_selected_index - 1;
14727 while (i > 0) {
14728 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14729 if (type == ChartData->GetDBChartType(dbIndex)) {
14730 SelectQuiltRefChart(i);
14731 b_success = true;
14732 break;
14733 }
14734 i--;
14735 }
14736 }
14737 }
14738}
14739
14740void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14741 // Remove the item from the list (if it appears) to avoid multiple addition
14742 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14743 if (m_quilt_noshow_index_array[i] ==
14744 dbIndex) // chart is already in the noshow list
14745 {
14746 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14747 break;
14748 }
14749 }
14750
14751 m_quilt_noshow_index_array.push_back(dbIndex);
14752}
14753
14754bool ChartCanvas::UpdateS52State() {
14755 bool retval = false;
14756
14757 if (ps52plib) {
14758 ps52plib->SetShowS57Text(m_encShowText);
14759 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14760 ps52plib->m_bShowSoundg = m_encShowDepth;
14761 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14762 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14763
14764 // Lights
14765 if (!m_encShowLights) // On, going off
14766 ps52plib->AddObjNoshow("LIGHTS");
14767 else // Off, going on
14768 ps52plib->RemoveObjNoshow("LIGHTS");
14769 ps52plib->SetLightsOff(!m_encShowLights);
14770 ps52plib->m_bExtendLightSectors = true;
14771
14772 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14773 ps52plib->SetAnchorOn(m_encShowAnchor);
14774 ps52plib->SetQualityOfData(m_encShowDataQual);
14775 }
14776
14777 return retval;
14778}
14779
14780void ChartCanvas::SetShowENCDataQual(bool show) {
14781 m_encShowDataQual = show;
14782 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14783 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14784
14785 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14786}
14787
14788void ChartCanvas::SetShowENCText(bool show) {
14789 m_encShowText = show;
14790 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14791 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14792
14793 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14794}
14795
14796void ChartCanvas::SetENCDisplayCategory(int category) {
14797 m_encDisplayCategory = category;
14798 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14799}
14800
14801void ChartCanvas::SetShowENCDepth(bool show) {
14802 m_encShowDepth = show;
14803 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14804 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14805
14806 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14807}
14808
14809void ChartCanvas::SetShowENCLightDesc(bool show) {
14810 m_encShowLightDesc = show;
14811 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14812 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14813
14814 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14815}
14816
14817void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14818 m_encShowBuoyLabels = show;
14819 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14820}
14821
14822void ChartCanvas::SetShowENCLights(bool show) {
14823 m_encShowLights = show;
14824 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14825 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14826
14827 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14828}
14829
14830void ChartCanvas::SetShowENCAnchor(bool show) {
14831 m_encShowAnchor = show;
14832 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14833 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14834
14835 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14836}
14837
14838wxRect ChartCanvas::GetMUIBarRect() {
14839 wxRect rv;
14840 if (m_muiBar) {
14841 rv = m_muiBar->GetRect();
14842 }
14843
14844 return rv;
14845}
14846
14847void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14848 if (!GetAlertString().IsEmpty()) {
14849 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14850 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14851
14852 dc.SetFont(*pfont);
14853 dc.SetPen(*wxTRANSPARENT_PEN);
14854
14855 dc.SetBrush(wxColour(243, 229, 47));
14856 int w, h;
14857 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14858 h += 2;
14859 // int yp = vp.pix_height - 20 - h;
14860
14861 wxRect sbr = GetScaleBarRect();
14862 int xp = sbr.x + sbr.width + 10;
14863 int yp = (sbr.y + sbr.height) - h;
14864
14865 int wdraw = w + 10;
14866 dc.DrawRectangle(xp, yp, wdraw, h);
14867 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14868 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14869 }
14870}
14871
14872//--------------------------------------------------------------------------------------------------------
14873// Screen Brightness Control Support Routines
14874//
14875//--------------------------------------------------------------------------------------------------------
14876
14877#ifdef __UNIX__
14878#define BRIGHT_XCALIB
14879#define __OPCPN_USEICC__
14880#endif
14881
14882#ifdef __OPCPN_USEICC__
14883int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14884 double co_green, double co_blue);
14885
14886wxString temp_file_name;
14887#endif
14888
14889#if 0
14890class ocpnCurtain: public wxDialog
14891{
14892 DECLARE_CLASS( ocpnCurtain )
14893 DECLARE_EVENT_TABLE()
14894
14895public:
14896 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14897 ~ocpnCurtain( );
14898 bool ProcessEvent(wxEvent& event);
14899
14900};
14901
14902IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14903
14904BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14905END_EVENT_TABLE()
14906
14907ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14908{
14909 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14910}
14911
14912ocpnCurtain::~ocpnCurtain()
14913{
14914}
14915
14916bool ocpnCurtain::ProcessEvent(wxEvent& event)
14917{
14918 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14919 return GetParent()->GetEventHandler()->ProcessEvent(event);
14920}
14921#endif
14922
14923#ifdef _WIN32
14924#include <windows.h>
14925
14926HMODULE hGDI32DLL;
14927typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14928typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14929SetDeviceGammaRamp_ptr_type
14930 g_pSetDeviceGammaRamp; // the API entry points in the dll
14931GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14932
14933WORD *g_pSavedGammaMap;
14934
14935#endif
14936
14937int InitScreenBrightness() {
14938#ifdef _WIN32
14939#ifdef ocpnUSE_GL
14940 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14941 HDC hDC;
14942 BOOL bbr;
14943
14944 if (NULL == hGDI32DLL) {
14945 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14946
14947 if (NULL != hGDI32DLL) {
14948 // Get the entry points of the required functions
14949 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14950 hGDI32DLL, "SetDeviceGammaRamp");
14951 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14952 hGDI32DLL, "GetDeviceGammaRamp");
14953
14954 // If the functions are not found, unload the DLL and return false
14955 if ((NULL == g_pSetDeviceGammaRamp) ||
14956 (NULL == g_pGetDeviceGammaRamp)) {
14957 FreeLibrary(hGDI32DLL);
14958 hGDI32DLL = NULL;
14959 return 0;
14960 }
14961 }
14962 }
14963
14964 // Interface is ready, so....
14965 // Get some storage
14966 if (!g_pSavedGammaMap) {
14967 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14968
14969 hDC = GetDC(NULL); // Get the full screen DC
14970 bbr = g_pGetDeviceGammaRamp(
14971 hDC, g_pSavedGammaMap); // Get the existing ramp table
14972 ReleaseDC(NULL, hDC); // Release the DC
14973 }
14974
14975 // On Windows hosts, try to adjust the registry to allow full range
14976 // setting of Gamma table This is an undocumented Windows hack.....
14977 wxRegKey *pRegKey = new wxRegKey(
14978 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14979 "NT\\CurrentVersion\\ICM");
14980 if (!pRegKey->Exists()) pRegKey->Create();
14981 pRegKey->SetValue("GdiIcmGammaRange", 256);
14982
14983 g_brightness_init = true;
14984 return 1;
14985 }
14986#endif
14987
14988 {
14989 if (NULL == g_pcurtain) {
14990 if (gFrame->CanSetTransparent()) {
14991 // Build the curtain window
14992 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14993 wxPoint(0, 0), ::wxGetDisplaySize(),
14994 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14995 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14996
14997 // g_pcurtain = new ocpnCurtain(gFrame,
14998 // wxPoint(0,0),::wxGetDisplaySize(),
14999 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15000 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15001
15002 g_pcurtain->Hide();
15003
15004 HWND hWnd = GetHwndOf(g_pcurtain);
15005 SetWindowLong(hWnd, GWL_EXSTYLE,
15006 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15007 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15008 g_pcurtain->SetTransparent(0);
15009
15010 g_pcurtain->Maximize();
15011 g_pcurtain->Show();
15012
15013 // All of this is obtuse, but necessary for Windows...
15014 g_pcurtain->Enable();
15015 g_pcurtain->Disable();
15016
15017 gFrame->Disable();
15018 gFrame->Enable();
15019 // SetFocus();
15020 }
15021 }
15022 g_brightness_init = true;
15023
15024 return 1;
15025 }
15026#else
15027 // Look for "xcalib" application
15028 wxString cmd("xcalib -version");
15029
15030 wxArrayString output;
15031 long r = wxExecute(cmd, output);
15032 if (0 != r)
15033 wxLogMessage(
15034 " External application \"xcalib\" not found. Screen brightness "
15035 "not changed.");
15036
15037 g_brightness_init = true;
15038 return 0;
15039#endif
15040}
15041
15042int RestoreScreenBrightness() {
15043#ifdef _WIN32
15044
15045 if (g_pSavedGammaMap) {
15046 HDC hDC = GetDC(NULL); // Get the full screen DC
15047 g_pSetDeviceGammaRamp(hDC,
15048 g_pSavedGammaMap); // Restore the saved ramp table
15049 ReleaseDC(NULL, hDC); // Release the DC
15050
15051 free(g_pSavedGammaMap);
15052 g_pSavedGammaMap = NULL;
15053 }
15054
15055 if (g_pcurtain) {
15056 g_pcurtain->Close();
15057 g_pcurtain->Destroy();
15058 g_pcurtain = NULL;
15059 }
15060
15061 g_brightness_init = false;
15062 return 1;
15063
15064#endif
15065
15066#ifdef BRIGHT_XCALIB
15067 if (g_brightness_init) {
15068 wxString cmd;
15069 cmd = "xcalib -clear";
15070 wxExecute(cmd, wxEXEC_ASYNC);
15071 g_brightness_init = false;
15072 }
15073
15074 return 1;
15075#endif
15076
15077 return 0;
15078}
15079
15080// Set brightness. [0..100]
15081int SetScreenBrightness(int brightness) {
15082#ifdef _WIN32
15083
15084 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15085 // some (most modern?) versions of gdi32.dll Load the required library dll,
15086 // if not already in place
15087#ifdef ocpnUSE_GL
15088 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15089 if (g_pcurtain) {
15090 g_pcurtain->Close();
15091 g_pcurtain->Destroy();
15092 g_pcurtain = NULL;
15093 }
15094
15095 InitScreenBrightness();
15096
15097 if (NULL == hGDI32DLL) {
15098 // Unicode stuff.....
15099 wchar_t wdll_name[80];
15100 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15101 LPCWSTR cstr = wdll_name;
15102
15103 hGDI32DLL = LoadLibrary(cstr);
15104
15105 if (NULL != hGDI32DLL) {
15106 // Get the entry points of the required functions
15107 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15108 hGDI32DLL, "SetDeviceGammaRamp");
15109 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15110 hGDI32DLL, "GetDeviceGammaRamp");
15111
15112 // If the functions are not found, unload the DLL and return false
15113 if ((NULL == g_pSetDeviceGammaRamp) ||
15114 (NULL == g_pGetDeviceGammaRamp)) {
15115 FreeLibrary(hGDI32DLL);
15116 hGDI32DLL = NULL;
15117 return 0;
15118 }
15119 }
15120 }
15121
15122 HDC hDC = GetDC(NULL); // Get the full screen DC
15123
15124 /*
15125 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15126 if (cmcap != CM_GAMMA_RAMP)
15127 {
15128 wxLogMessage(" Video hardware does not support brightness control by
15129 gamma ramp adjustment."); return false;
15130 }
15131 */
15132
15133 int increment = brightness * 256 / 100;
15134
15135 // Build the Gamma Ramp table
15136 WORD GammaTable[3][256];
15137
15138 int table_val = 0;
15139 for (int i = 0; i < 256; i++) {
15140 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15141 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15142 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15143
15144 table_val += increment;
15145
15146 if (table_val > 65535) table_val = 65535;
15147 }
15148
15149 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15150 ReleaseDC(NULL, hDC); // Release the DC
15151
15152 return 1;
15153 }
15154#endif
15155
15156 {
15157 if (g_pSavedGammaMap) {
15158 HDC hDC = GetDC(NULL); // Get the full screen DC
15159 g_pSetDeviceGammaRamp(hDC,
15160 g_pSavedGammaMap); // Restore the saved ramp table
15161 ReleaseDC(NULL, hDC); // Release the DC
15162 }
15163
15164 if (brightness < 100) {
15165 if (NULL == g_pcurtain) InitScreenBrightness();
15166
15167 if (g_pcurtain) {
15168 int sbrite = wxMax(1, brightness);
15169 sbrite = wxMin(100, sbrite);
15170
15171 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15172 }
15173 } else {
15174 if (g_pcurtain) {
15175 g_pcurtain->Close();
15176 g_pcurtain->Destroy();
15177 g_pcurtain = NULL;
15178 }
15179 }
15180
15181 return 1;
15182 }
15183
15184#endif
15185
15186#ifdef BRIGHT_XCALIB
15187
15188 if (!g_brightness_init) {
15189 last_brightness = 100;
15190 g_brightness_init = true;
15191 temp_file_name = wxFileName::CreateTempFileName("");
15192 InitScreenBrightness();
15193 }
15194
15195#ifdef __OPCPN_USEICC__
15196 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15197 // desired, and then activate this temporary profile using xcalib <filename>
15198 if (!CreateSimpleICCProfileFile(
15199 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15200 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15201 wxString cmd("xcalib ");
15202 cmd += temp_file_name;
15203
15204 wxExecute(cmd, wxEXEC_ASYNC);
15205 }
15206
15207#else
15208 // Or, use "xcalib -co" to set overall contrast value
15209 // This is not as nice, since the -co parameter wants to be a fraction of
15210 // the current contrast, and values greater than 100 are not allowed. As a
15211 // result, increases of contrast must do a "-clear" step first, which
15212 // produces objectionable flashing.
15213 if (brightness > last_brightness) {
15214 wxString cmd;
15215 cmd = "xcalib -clear";
15216 wxExecute(cmd, wxEXEC_ASYNC);
15217
15218 ::wxMilliSleep(10);
15219
15220 int brite_adj = wxMax(1, brightness);
15221 cmd.Printf("xcalib -co %2d -a", brite_adj);
15222 wxExecute(cmd, wxEXEC_ASYNC);
15223 } else {
15224 int brite_adj = wxMax(1, brightness);
15225 int factor = (brite_adj * 100) / last_brightness;
15226 factor = wxMax(1, factor);
15227 wxString cmd;
15228 cmd.Printf("xcalib -co %2d -a", factor);
15229 wxExecute(cmd, wxEXEC_ASYNC);
15230 }
15231
15232#endif
15233
15234 last_brightness = brightness;
15235
15236#endif
15237
15238 return 0;
15239}
15240
15241#ifdef __OPCPN_USEICC__
15242
15243#define MLUT_TAG 0x6d4c5554L
15244#define VCGT_TAG 0x76636774L
15245
15246int GetIntEndian(unsigned char *s) {
15247 int ret;
15248 unsigned char *p;
15249 int i;
15250
15251 p = (unsigned char *)&ret;
15252
15253 if (1)
15254 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15255 else
15256 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15257
15258 return ret;
15259}
15260
15261unsigned short GetShortEndian(unsigned char *s) {
15262 unsigned short ret;
15263 unsigned char *p;
15264 int i;
15265
15266 p = (unsigned char *)&ret;
15267
15268 if (1)
15269 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15270 else
15271 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15272
15273 return ret;
15274}
15275
15276// Create a very simple Gamma correction file readable by xcalib
15277int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15278 double co_green, double co_blue) {
15279 FILE *fp;
15280
15281 if (file_name) {
15282 fp = fopen(file_name, "wb");
15283 if (!fp) return -1; /* file can not be created */
15284 } else
15285 return -1; /* filename char pointer not valid */
15286
15287 // Write header
15288 char header[128];
15289 for (int i = 0; i < 128; i++) header[i] = 0;
15290
15291 fwrite(header, 128, 1, fp);
15292
15293 // Num tags
15294 int numTags0 = 1;
15295 int numTags = GetIntEndian((unsigned char *)&numTags0);
15296 fwrite(&numTags, 1, 4, fp);
15297
15298 int tagName0 = VCGT_TAG;
15299 int tagName = GetIntEndian((unsigned char *)&tagName0);
15300 fwrite(&tagName, 1, 4, fp);
15301
15302 int tagOffset0 = 128 + 4 * sizeof(int);
15303 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15304 fwrite(&tagOffset, 1, 4, fp);
15305
15306 int tagSize0 = 1;
15307 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15308 fwrite(&tagSize, 1, 4, fp);
15309
15310 fwrite(&tagName, 1, 4, fp); // another copy of tag
15311
15312 fwrite(&tagName, 1, 4, fp); // dummy
15313
15314 // Table type
15315
15316 /* VideoCardGammaTable (The simplest type) */
15317 int gammatype0 = 0;
15318 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15319 fwrite(&gammatype, 1, 4, fp);
15320
15321 int numChannels0 = 3;
15322 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15323 fwrite(&numChannels, 1, 2, fp);
15324
15325 int numEntries0 = 256;
15326 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15327 fwrite(&numEntries, 1, 2, fp);
15328
15329 int entrySize0 = 1;
15330 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15331 fwrite(&entrySize, 1, 2, fp);
15332
15333 unsigned char ramp[256];
15334
15335 // Red ramp
15336 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15337 fwrite(ramp, 256, 1, fp);
15338
15339 // Green ramp
15340 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15341 fwrite(ramp, 256, 1, fp);
15342
15343 // Blue ramp
15344 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15345 fwrite(ramp, 256, 1, fp);
15346
15347 fclose(fp);
15348
15349 return 0;
15350}
15351#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:741
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:72
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:56
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1314
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:174
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1313
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1314
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1313
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:49
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:125
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:157
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13714
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:4541
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11758
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4537
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3635
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13673
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:4487
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:782
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:485
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:516
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2342
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7972
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7781
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5069
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:473
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:873
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4618
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5349
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:766
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4562
bool IsTideDialogOpen() const
Definition chcanv.cpp:13712
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:4624
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13669
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4482
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5368
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10165
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:468
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
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:60
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:201
Main application frame.
Definition ocpn_frame.h:137
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:59
PluginLoader is a backend module without any direct GUI functionality.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1779
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h:44
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:72
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:58
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
Definition routeman.cpp:998
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:77
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
MySQL based storage for routes, tracks, etc.
Utility functions.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
OpenCPN top window.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:183
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:65
OpenCPN Toolbar.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.