OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/ocpn_utils.h"
58#include "model/own_ship.h"
59#include "model/plugin_comm.h"
60#include "model/route.h"
61#include "model/routeman.h"
62#include "model/select.h"
63#include "model/select_item.h"
64#include "model/track.h"
65#include "model/ocpn_utils.h"
66
67#include "ais.h"
70#include "canvas_config.h"
71#include "canvas_menu.h"
72#include "canvas_options.h"
73#include "chartdb.h"
74#include "chartimg.h"
75#include "chcanv.h"
76#include "ch_info_win.h"
77#include "cm93.h" // for chart outline draw
78#include "compass.h"
79#include "concanv.h"
80#include "detail_slider.h"
81#include "displays.h"
82#include "hotkeys_dlg.h"
83#include "font_mgr.h"
84#include "gl_texture_descr.h"
85#include "go_to_position_dlg.h"
86#include "gshhs.h"
87#include "ienc_toolbar.h"
88#include "kml.h"
89#include "line_clip.h"
90#include "mark_info.h"
91#include "mbtiles.h"
92#include "mui_bar.h"
93#include "navutil.h"
94#include "ocpn_aui_manager.h"
95#include "ocpndc.h"
96#include "ocpn_pixel.h"
97#include "ocpn_region.h"
98#include "options.h"
99#include "piano.h"
100#include "pluginmanager.h"
101#include "quilt.h"
102#include "route_gui.h"
103#include "routemanagerdialog.h"
104#include "route_point_gui.h"
105#include "route_prop_dlg_impl.h"
106#include "s52plib.h"
107#include "s52utils.h"
108#include "s57_query_dlg.h"
109#include "s57chart.h" // for ArrayOfS57Obj
110#include "senc_manager.h"
111#include "shapefile_basemap.h"
112#include "styles.h"
113#include "tcmgr.h"
114#include "tc_win.h"
115#include "thumbwin.h"
116#include "tide_time.h"
117#include "timers.h"
118#include "toolbar.h"
119#include "top_frame.h"
120#include "track_gui.h"
121#include "track_prop_dlg.h"
122#include "undo.h"
123#include "user_colors.h"
124
125#include "s57_ocpn_utils.h"
126
127#ifdef __ANDROID__
128#include "androidUTIL.h"
129#endif
130
131#ifdef ocpnUSE_GL
132#include "gl_chart_canvas.h"
135#endif
136
137#ifdef __VISUALC__
138#include <wx/msw/msvcrt.h>
139#endif
140
141#ifndef __WXMSW__
142#include <signal.h>
143#include <setjmp.h>
144#endif
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173arrayofCanvasPtr g_canvasArray;
175static bool g_bSmoothRecenter = true;
176static bool bDrawCurrentValues;
186static int mouse_x;
196static int mouse_y;
197static bool mouse_leftisdown;
198static bool g_brouteCreating;
199static int r_gamma_mult;
200static int g_gamma_mult;
201static int b_gamma_mult;
202static int gamma_state;
203static bool g_brightness_init;
204static int last_brightness;
205static wxGLContext *g_pGLcontext; // shared common context
206
207// "Curtain" mode parameters
208static wxDialog *g_pcurtain;
209
210static wxString g_lastS52PLIBPluginMessage;
211
212#define MIN_BRIGHT 10
213#define MAX_BRIGHT 100
214
215//------------------------------------------------------------------------------
216// ChartCanvas Implementation
217//------------------------------------------------------------------------------
218BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
219EVT_PAINT(ChartCanvas::OnPaint)
220EVT_ACTIVATE(ChartCanvas::OnActivate)
221EVT_SIZE(ChartCanvas::OnSize)
222#ifndef HAVE_WX_GESTURE_EVENTS
223EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
224#endif
225EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
226EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
227EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
228EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
229EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
230EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
231EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
232EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
233EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
234EVT_KEY_UP(ChartCanvas::OnKeyUp)
235EVT_CHAR(ChartCanvas::OnKeyChar)
236EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
237EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
238EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
239EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
240EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
241EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
242EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
243EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
244EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
245EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
246
247END_EVENT_TABLE()
248
249// Define a constructor for my canvas
250ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
251 : AbstractChartCanvas(frame, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
252 m_nmea_log(nmea_log) {
253 parent_frame = frame; // save a pointer to parent
254 m_canvasIndex = canvasIndex;
255
256 pscratch_bm = NULL;
257
258 SetBackgroundColour(wxColour(0, 0, 0));
259 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
260 // color scheme change
261
262 m_groupIndex = 0;
263 m_bDrawingRoute = false;
264 m_bRouteEditing = false;
265 m_bMarkEditing = false;
266 m_bRoutePoinDragging = false;
267 m_bIsInRadius = false;
268 m_bMayToggleMenuBar = true;
269
270 m_bFollow = false;
271 m_bShowNavobjects = true;
272 m_bTCupdate = false;
273 m_bAppendingRoute = false; // was true in MSW, why??
274 pThumbDIBShow = NULL;
275 m_bShowCurrent = false;
276 m_bShowTide = false;
277 bShowingCurrent = false;
278 pCwin = NULL;
279 warp_flag = false;
280 m_bzooming = false;
281 m_b_paint_enable = true;
282 m_routeState = 0;
283
284 pss_overlay_bmp = NULL;
285 pss_overlay_mask = NULL;
286 m_bChartDragging = false;
287 m_bMeasure_Active = false;
288 m_bMeasure_DistCircle = false;
289 m_pMeasureRoute = NULL;
290 m_pTrackRolloverWin = NULL;
291 m_pRouteRolloverWin = NULL;
292 m_pAISRolloverWin = NULL;
293 m_bedge_pan = false;
294 m_disable_edge_pan = false;
295 m_dragoffsetSet = false;
296 m_bautofind = false;
297 m_bFirstAuto = true;
298 m_groupIndex = 0;
299 m_singleChart = NULL;
300 m_upMode = NORTH_UP_MODE;
301 m_bShowAIS = true;
302 m_bShowAISScaled = false;
303 m_timed_move_vp_active = false;
304 m_inPinch = false;
305 m_disable_adjust_on_zoom = false;
306
307 m_vLat = 0.;
308 m_vLon = 0.;
309
310 m_pCIWin = NULL;
311
312 m_pSelectedRoute = NULL;
313 m_pSelectedTrack = NULL;
314 m_pRoutePointEditTarget = NULL;
315 m_pFoundPoint = NULL;
316 m_pMouseRoute = NULL;
317 m_prev_pMousePoint = NULL;
318 m_pEditRouteArray = NULL;
319 m_pFoundRoutePoint = NULL;
320 m_FinishRouteOnKillFocus = true;
321
322 m_pRolloverRouteSeg = NULL;
323 m_pRolloverTrackSeg = NULL;
324 m_bsectors_shown = false;
325
326 m_bbrightdir = false;
327 r_gamma_mult = 1;
328 g_gamma_mult = 1;
329 b_gamma_mult = 1;
330
331 m_pos_image_user_day = NULL;
332 m_pos_image_user_dusk = NULL;
333 m_pos_image_user_night = NULL;
334 m_pos_image_user_grey_day = NULL;
335 m_pos_image_user_grey_dusk = NULL;
336 m_pos_image_user_grey_night = NULL;
337
338 m_zoom_factor = 1;
339 m_rotation_speed = 0;
340 m_mustmove = 0;
341
342 m_OSoffsetx = 0.;
343 m_OSoffsety = 0.;
344
345 m_pos_image_user_yellow_day = NULL;
346 m_pos_image_user_yellow_dusk = NULL;
347 m_pos_image_user_yellow_night = NULL;
348
349 SetOwnShipState(SHIP_INVALID);
350
351 undo = new Undo(this);
352
353 VPoint.Invalidate();
354
355 m_glcc = NULL;
356
357 m_focus_indicator_pix = 1;
358
359 m_pCurrentStack = NULL;
360 m_bpersistent_quilt = false;
361 m_piano_ctx_menu = NULL;
362 m_Compass = NULL;
363 m_NotificationsList = NULL;
364 m_notification_button = NULL;
365
366 g_ChartNotRenderScaleFactor = 2.0;
367 m_bShowScaleInStatusBar = true;
368
369 m_muiBar = NULL;
370 m_bShowScaleInStatusBar = false;
371 m_show_focus_bar = true;
372
373 m_bShowOutlines = false;
374 m_bDisplayGrid = false;
375 m_bShowDepthUnits = true;
376 m_encDisplayCategory = (int)STANDARD;
377
378 m_encShowLights = true;
379 m_encShowAnchor = true;
380 m_encShowDataQual = false;
381 m_bShowGPS = true;
382 m_pQuilt = new Quilt(this);
383 SetQuiltMode(true);
384 SetAlertString("");
385 m_sector_glat = 0;
386 m_sector_glon = 0;
387 g_PrintingInProgress = false;
388
389#ifdef HAVE_WX_GESTURE_EVENTS
390 m_oldVPSScale = -1.0;
391 m_popupWanted = false;
392 m_leftdown = false;
393#endif /* HAVE_WX_GESTURE_EVENTS */
394 m_inLongPress = false;
395 m_sw_down_time = 0;
396 m_sw_up_time = 0;
397 m_sw_left_down.Start();
398 m_sw_left_up.Start();
399
400 SetupGlCanvas();
401
402 singleClickEventIsValid = false;
403
404 // Build the cursors
405
406 pCursorLeft = NULL;
407 pCursorRight = NULL;
408 pCursorUp = NULL;
409 pCursorDown = NULL;
410 pCursorArrow = NULL;
411 pCursorPencil = NULL;
412 pCursorCross = NULL;
413
414 RebuildCursors();
415
416 SetCursor(*pCursorArrow);
417
418 pPanTimer = new wxTimer(this, m_MouseDragging);
419 pPanTimer->Stop();
420
421 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
422 pMovementTimer->Stop();
423
424 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
425 pMovementStopTimer->Stop();
426
427 pRotDefTimer = new wxTimer(this, ROT_TIMER);
428 pRotDefTimer->Stop();
429
430 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
431 m_DoubleClickTimer->Stop();
432
433 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
434 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
435 m_chart_drag_inertia_active = false;
436
437 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
438 m_animationActive = false;
439 m_menuTimer.SetOwner(this, MENU_TIMER);
440 m_tap_timer.SetOwner(this, TAP_TIMER);
441
442 m_panx = m_pany = 0;
443 m_panspeed = 0;
444 m_panx_target_final = m_pany_target_final = 0;
445 m_panx_target_now = m_pany_target_now = 0;
446 m_DragTrigger = -1;
447
448 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
449 pCurTrackTimer->Stop();
450 m_curtrack_timer_msec = 10;
451
452 m_wheelzoom_stop_oneshot = 0;
453 m_last_wheel_dir = 0;
454
455 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
456
457 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
458
459 m_rollover_popup_timer_msec = 20;
460
461 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
462
463 m_b_rot_hidef = true;
464
465 proute_bm = NULL;
466 m_prot_bm = NULL;
467
468 m_upMode = NORTH_UP_MODE;
469 m_bLookAhead = false;
470
471 // Set some benign initial values
472
473 m_cs = GLOBAL_COLOR_SCHEME_DAY;
474 VPoint.clat = 0;
475 VPoint.clon = 0;
476 VPoint.view_scale_ppm = 1;
477 VPoint.Invalidate();
478 m_nMeasureState = 0;
479
480 m_canvas_scale_factor = 1.;
481
482 m_canvas_width = 1000;
483
484 m_overzoomTextWidth = 0;
485 m_overzoomTextHeight = 0;
486
487 // Create the default world chart
488 pWorldBackgroundChart = new GSHHSChart;
489 gShapeBasemap.Reset();
490
491 // Create the default depth unit emboss maps
492 m_pEM_Feet = NULL;
493 m_pEM_Meters = NULL;
494 m_pEM_Fathoms = NULL;
495
496 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
497
498 m_pEM_OverZoom = NULL;
499 SetOverzoomFont();
500 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
501
502 // Build icons for tide/current points
503 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
504 m_bmTideDay =
505 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
506
507 // Dusk
508 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
509
510 // Night
511 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
512
513 // Build Dusk/Night ownship icons
514 double factor_dusk = 0.5;
515 double factor_night = 0.25;
516
517 // Red
518 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
519
520 int rimg_width = m_os_image_red_day.GetWidth();
521 int rimg_height = m_os_image_red_day.GetHeight();
522
523 m_os_image_red_dusk = m_os_image_red_day.Copy();
524 m_os_image_red_night = m_os_image_red_day.Copy();
525
526 for (int iy = 0; iy < rimg_height; iy++) {
527 for (int ix = 0; ix < rimg_width; ix++) {
528 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
529 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
530 m_os_image_red_day.GetGreen(ix, iy),
531 m_os_image_red_day.GetBlue(ix, iy));
532 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
533 hsv.value = hsv.value * factor_dusk;
534 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
535 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
536
537 hsv = wxImage::RGBtoHSV(rgb);
538 hsv.value = hsv.value * factor_night;
539 nrgb = wxImage::HSVtoRGB(hsv);
540 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
541 }
542 }
543 }
544
545 // Grey
546 m_os_image_grey_day =
547 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
548
549 int gimg_width = m_os_image_grey_day.GetWidth();
550 int gimg_height = m_os_image_grey_day.GetHeight();
551
552 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
553 m_os_image_grey_night = m_os_image_grey_day.Copy();
554
555 for (int iy = 0; iy < gimg_height; iy++) {
556 for (int ix = 0; ix < gimg_width; ix++) {
557 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
558 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
559 m_os_image_grey_day.GetGreen(ix, iy),
560 m_os_image_grey_day.GetBlue(ix, iy));
561 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
562 hsv.value = hsv.value * factor_dusk;
563 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
564 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
565
566 hsv = wxImage::RGBtoHSV(rgb);
567 hsv.value = hsv.value * factor_night;
568 nrgb = wxImage::HSVtoRGB(hsv);
569 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
570 }
571 }
572 }
573
574 // Yellow
575 m_os_image_yellow_day = m_os_image_red_day.Copy();
576
577 gimg_width = m_os_image_yellow_day.GetWidth();
578 gimg_height = m_os_image_yellow_day.GetHeight();
579
580 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
581 m_os_image_yellow_night = m_os_image_red_day.Copy();
582
583 for (int iy = 0; iy < gimg_height; iy++) {
584 for (int ix = 0; ix < gimg_width; ix++) {
585 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
586 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
587 m_os_image_yellow_day.GetGreen(ix, iy),
588 m_os_image_yellow_day.GetBlue(ix, iy));
589 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
590 hsv.hue += 60. / 360.; // shift to yellow
591 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
592 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
593
594 hsv = wxImage::RGBtoHSV(rgb);
595 hsv.value = hsv.value * factor_dusk;
596 hsv.hue += 60. / 360.; // shift to yellow
597 nrgb = wxImage::HSVtoRGB(hsv);
598 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
599
600 hsv = wxImage::RGBtoHSV(rgb);
601 hsv.hue += 60. / 360.; // shift to yellow
602 hsv.value = hsv.value * factor_night;
603 nrgb = wxImage::HSVtoRGB(hsv);
604 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
605 }
606 }
607 }
608
609 // Set initial pointers to ownship images
610 m_pos_image_red = &m_os_image_red_day;
611 m_pos_image_yellow = &m_os_image_yellow_day;
612 m_pos_image_grey = &m_os_image_grey_day;
613
614 SetUserOwnship();
615
616 m_pBrightPopup = NULL;
617
618#ifdef ocpnUSE_GL
619 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
620#endif
621
622 SetupGridFont();
623
624 m_Piano = new Piano(this);
625
626 m_bShowCompassWin = true;
627 m_Compass = new ocpnCompass(this);
628 m_Compass->SetScaleFactor(g_compass_scalefactor);
629 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
630
631 if (IsPrimaryCanvas() && !g_disableNotifications) {
632 m_notification_button = new NotificationButton(this);
633 m_notification_button->SetScaleFactor(g_compass_scalefactor);
634 m_notification_button->Show(true);
635 }
636
637 m_pianoFrozen = false;
638
639 SetMinSize(wxSize(200, 200));
640
641 m_displayScale = 1.0;
642#if defined(__WXOSX__) || defined(__WXGTK3__)
643 // Support scaled HDPI displays.
644 m_displayScale = GetContentScaleFactor();
645#endif
646 VPoint.SetPixelScale(m_displayScale);
647
648#ifdef HAVE_WX_GESTURE_EVENTS
649 // if (!m_glcc)
650 {
651 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
652 wxLogError("Failed to enable touch events");
653 }
654
655 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
656
657 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
658 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
659
660 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
661 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
662
663 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
664 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
665
666 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
667 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
668 }
669#endif
670
671 // Listen for notification events
672 auto &noteman = NotificationManager::GetInstance();
673
674 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
675 evt_notificationlist_change_listener.Listen(
676 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
677 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
678 if (m_NotificationsList && m_NotificationsList->IsShown()) {
679 m_NotificationsList->ReloadNotificationList();
680 }
681 Refresh();
682 });
683}
684
685ChartCanvas::~ChartCanvas() {
686 delete pThumbDIBShow;
687
688 // Delete Cursors
689 delete pCursorLeft;
690 delete pCursorRight;
691 delete pCursorUp;
692 delete pCursorDown;
693 delete pCursorArrow;
694 delete pCursorPencil;
695 delete pCursorCross;
696
697 delete pPanTimer;
698 delete pMovementTimer;
699 delete pMovementStopTimer;
700 delete pCurTrackTimer;
701 delete pRotDefTimer;
702 delete m_DoubleClickTimer;
703
704 delete m_pTrackRolloverWin;
705 delete m_pRouteRolloverWin;
706 delete m_pAISRolloverWin;
707 delete m_pBrightPopup;
708
709 delete m_pCIWin;
710
711 delete pscratch_bm;
712
713 m_dc_route.SelectObject(wxNullBitmap);
714 delete proute_bm;
715
716 delete pWorldBackgroundChart;
717 delete pss_overlay_bmp;
718
719 delete m_pEM_Feet;
720 delete m_pEM_Meters;
721 delete m_pEM_Fathoms;
722
723 delete m_pEM_OverZoom;
724 // delete m_pEM_CM93Offset;
725
726 delete m_prot_bm;
727
728 delete m_pos_image_user_day;
729 delete m_pos_image_user_dusk;
730 delete m_pos_image_user_night;
731 delete m_pos_image_user_grey_day;
732 delete m_pos_image_user_grey_dusk;
733 delete m_pos_image_user_grey_night;
734 delete m_pos_image_user_yellow_day;
735 delete m_pos_image_user_yellow_dusk;
736 delete m_pos_image_user_yellow_night;
737
738 delete undo;
739#ifdef ocpnUSE_GL
740 if (!g_bdisable_opengl) {
741 delete m_glcc;
742
743#if wxCHECK_VERSION(2, 9, 0)
744 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
745#endif
746 }
747#endif
748
749 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
750 // wx tries to deliver events to this canvas during destroy.
751 MUIBar *muiBar = m_muiBar;
752 m_muiBar = 0;
753 delete muiBar;
754 delete m_pQuilt;
755 delete m_pCurrentStack;
756 delete m_Compass;
757 delete m_Piano;
758 delete m_notification_button;
759}
760
761void ChartCanvas::SetupGridFont() {
762 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
763 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
764 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
765 m_pgridFont = FontMgr::Get().FindOrCreateFont(
766 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
767 FALSE, wxString("Arial"));
768}
769
770void ChartCanvas::RebuildCursors() {
771 delete pCursorLeft;
772 delete pCursorRight;
773 delete pCursorUp;
774 delete pCursorDown;
775 delete pCursorArrow;
776 delete pCursorPencil;
777 delete pCursorCross;
778
779 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
780 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
781
782 double pencilScale =
783 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
784
785 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
786 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
787 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
788 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
789 wxImage ICursorPencil =
790 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
791 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
792
793#if !defined(__WXMSW__) && !defined(__WXQT__)
794 ICursorLeft.ConvertAlphaToMask(128);
795 ICursorRight.ConvertAlphaToMask(128);
796 ICursorUp.ConvertAlphaToMask(128);
797 ICursorDown.ConvertAlphaToMask(128);
798 ICursorPencil.ConvertAlphaToMask(10);
799 ICursorCross.ConvertAlphaToMask(10);
800#endif
801
802 if (ICursorLeft.Ok()) {
803 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
805 pCursorLeft = new wxCursor(ICursorLeft);
806 } else
807 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
808
809 if (ICursorRight.Ok()) {
810 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
812 pCursorRight = new wxCursor(ICursorRight);
813 } else
814 pCursorRight = new wxCursor(wxCURSOR_ARROW);
815
816 if (ICursorUp.Ok()) {
817 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
819 pCursorUp = new wxCursor(ICursorUp);
820 } else
821 pCursorUp = new wxCursor(wxCURSOR_ARROW);
822
823 if (ICursorDown.Ok()) {
824 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
826 pCursorDown = new wxCursor(ICursorDown);
827 } else
828 pCursorDown = new wxCursor(wxCURSOR_ARROW);
829
830 if (ICursorPencil.Ok()) {
831 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
833 pCursorPencil = new wxCursor(ICursorPencil);
834 } else
835 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
836
837 if (ICursorCross.Ok()) {
838 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
840 pCursorCross = new wxCursor(ICursorCross);
841 } else
842 pCursorCross = new wxCursor(wxCURSOR_ARROW);
843
844 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
845 pPlugIn_Cursor = NULL;
846}
847
848void ChartCanvas::CanvasApplyLocale() {
849 CreateDepthUnitEmbossMaps(m_cs);
850 CreateOZEmbossMapData(m_cs);
851}
852
853void ChartCanvas::SetupGlCanvas() {
854#ifndef __ANDROID__
855#ifdef ocpnUSE_GL
856 if (!g_bdisable_opengl) {
857 if (g_bopengl) {
858 wxLogMessage("Creating glChartCanvas");
859 m_glcc = new glChartCanvas(this);
860
861 // We use one context for all GL windows, so that textures etc will be
862 // automatically shared
863 if (IsPrimaryCanvas()) {
864 // qDebug() << "Creating Primary Context";
865
866 // wxGLContextAttrs ctxAttr;
867 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
868 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
869 // NULL, &ctxAttr);
870 wxGLContext *pctx = new wxGLContext(m_glcc);
871 m_glcc->SetContext(pctx);
872 g_pGLcontext = pctx; // Save a copy of the common context
873 } else {
874#ifdef __WXOSX__
875 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
876#else
877 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
878 // saved common context
879#endif
880 }
881 }
882 }
883#endif
884#endif
885
886#ifdef __ANDROID__ // ocpnUSE_GL
887 if (!g_bdisable_opengl) {
888 if (g_bopengl) {
889 // qDebug() << "SetupGlCanvas";
890 wxLogMessage("Creating glChartCanvas");
891
892 // We use one context for all GL windows, so that textures etc will be
893 // automatically shared
894 if (IsPrimaryCanvas()) {
895 qDebug() << "Creating Primary glChartCanvas";
896
897 // wxGLContextAttrs ctxAttr;
898 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
899 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
900 // NULL, &ctxAttr);
901 m_glcc = new glChartCanvas(this);
902
903 wxGLContext *pctx = new wxGLContext(m_glcc);
904 m_glcc->SetContext(pctx);
905 g_pGLcontext = pctx; // Save a copy of the common context
906 m_glcc->m_pParentCanvas = this;
907 // m_glcc->Reparent(this);
908 } else {
909 qDebug() << "Creating Secondary glChartCanvas";
910 // QGLContext *pctx =
911 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
912 // << "pctx: " << pctx;
913
914 m_glcc =
915 new glChartCanvas(wxTheApp->GetTopWindow(),
916 top_frame::Get()->GetWxGlCanvas()); // 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 top_frame::Get()->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 top_frame::Get()->LoadHarmonics();
1263
1264 if (ptcmgr->IsReady()) {
1265 SetbShowTide(bShow);
1266
1267 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1268 } else {
1269 wxLogMessage("Chart1::Event...TCMgr Not Available");
1270 SetbShowTide(false);
1271 top_frame::Get()->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 top_frame::Get()->LoadHarmonics();
1288
1289 if (ptcmgr->IsReady()) {
1290 SetbShowCurrent(bShow);
1291 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1292 } else {
1293 wxLogMessage("Chart1::Event...TCMgr Not Available");
1294 SetbShowCurrent(false);
1295 top_frame::Get()->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 auto &cte = ChartData->GetChartTableEntry(ic);
1424 wxString chart_full_path(cte.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 (!GetVP().IsValid()) return false;
1529 if (bDBUpdateInProgress) return false;
1530 if (!ChartData) return false;
1531
1532 if (ChartData->IsBusy()) return false;
1533 // Do not disturb any existing animations
1534 if (m_chart_drag_inertia_active) return false;
1535 if (m_animationActive) return false;
1536
1537 // Startup case:
1538 // Quilting is enabled, but the last chart seen was not quiltable
1539 // In this case, drop to single chart mode, set persistence flag,
1540 // And open the specified chart
1541 // TODO implement this
1542 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1543 // if( GetQuiltMode() ) {
1544 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1545 // gFrame->ToggleQuiltMode();
1546 // m_bpersistent_quilt = true;
1547 // m_singleChart = NULL;
1548 // }
1549 // }
1550 // }
1551
1552 // If in auto-follow mode, use the current glat,glon to build chart
1553 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1554 // other means
1555
1556 if (m_bFollow) {
1557 tLat = gLat;
1558 tLon = gLon;
1559
1560 // Set the ViewPort center based on the OWNSHIP offset
1561 double dx = m_OSoffsetx;
1562 double dy = m_OSoffsety;
1563 double d_east = dx / GetVP().view_scale_ppm;
1564 double d_north = dy / GetVP().view_scale_ppm;
1565
1566 if (GetUpMode() == NORTH_UP_MODE) {
1567 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1568 } else {
1569 double offset_angle = atan2(d_north, d_east);
1570 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1571 double chart_angle = GetVPRotation();
1572 double target_angle = chart_angle + offset_angle;
1573 double d_east_mod = offset_distance * cos(target_angle);
1574 double d_north_mod = offset_distance * sin(target_angle);
1575 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1576 }
1577
1578 // on lookahead mode, adjust the vp center point
1579 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1580 double cog_to_use = gCog;
1581 if (g_btenhertz &&
1582 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1583 cog_to_use = gCog_gt;
1584 blong_jump = true;
1585 }
1586 if (!g_btenhertz) cog_to_use = g_COGAvg;
1587
1588 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1589
1590 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1591 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1592
1593 double pixel_delta_tent =
1594 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1595
1596 double pixel_delta = 0;
1597
1598 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1599 // avoid jumping of the vp center point during slow maneuvering, or at
1600 // anchor....
1601 if (!std::isnan(gSog)) {
1602 if (gSog < 2.0)
1603 pixel_delta = 0.;
1604 else
1605 pixel_delta = pixel_delta_tent;
1606 }
1607
1608 meters_to_shift = 0;
1609 dir_to_shift = 0;
1610 if (!std::isnan(gCog)) {
1611 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1612 dir_to_shift = cog_to_use;
1613 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1614 &vpLon);
1615 } else {
1616 vpLat = gLat;
1617 vpLon = gLon;
1618 }
1619 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1620 m_OSoffsetx = 0; // center ownship on loss of GPS
1621 m_OSoffsety = 0;
1622 vpLat = gLat;
1623 vpLon = gLon;
1624 }
1625
1626 } else {
1627 tLat = m_vLat;
1628 tLon = m_vLon;
1629 vpLat = m_vLat;
1630 vpLon = m_vLon;
1631 }
1632
1633 if (GetQuiltMode()) {
1634 int current_db_index = -1;
1635 if (m_pCurrentStack)
1636 current_db_index =
1637 m_pCurrentStack
1638 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1639 // chart dbIndex
1640 else
1641 m_pCurrentStack = new ChartStack;
1642
1643 // This logic added to enable opening a chart when there is no
1644 // previous chart indication, either from inital startup, or from adding
1645 // new chart directory
1646 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1647 m_pCurrentStack) {
1648 if (m_pCurrentStack->nEntry) {
1649 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1650 1); // smallest scale
1651 SelectQuiltRefdbChart(new_dbIndex, true);
1652 m_bautofind = false;
1653 }
1654 }
1655
1656 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1657 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1658
1659 if (m_bFirstAuto) {
1660 // Allow the chart database to pre-calculate the MBTile inclusion test
1661 // boolean...
1662 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1663
1664 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1665 // physical pixels. On standard DPI displays where logical = physical
1666 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1667 // logical pixels, this ratio would be 0.5.
1668 double proposed_scale_onscreen =
1669 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1670
1671 int initial_db_index = m_restore_dbindex;
1672 if (initial_db_index < 0) {
1673 if (m_pCurrentStack->nEntry) {
1674 initial_db_index =
1675 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1676 } else
1677 m_bautofind = true; // initial_db_index = 0;
1678 }
1679
1680 if (m_pCurrentStack->nEntry) {
1681 int initial_type = ChartData->GetDBChartType(initial_db_index);
1682
1683 // Check to see if the target new chart is quiltable as a reference
1684 // chart
1685
1686 if (!IsChartQuiltableRef(initial_db_index)) {
1687 // If it is not quiltable, then walk the stack up looking for a
1688 // satisfactory chart i.e. one that is quiltable and of the same type
1689 // XXX if there's none?
1690 int stack_index = 0;
1691
1692 if (stack_index >= 0) {
1693 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1694 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1695 if (IsChartQuiltableRef(test_db_index) &&
1696 (initial_type ==
1697 ChartData->GetDBChartType(initial_db_index))) {
1698 initial_db_index = test_db_index;
1699 break;
1700 }
1701 stack_index++;
1702 }
1703 }
1704 }
1705
1706 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1707 if (pc) {
1708 SetQuiltRefChart(initial_db_index);
1709 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1710 }
1711 }
1712 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1713 // just GetVPScale(), so I'm not sure why it's necessary to define the
1714 // proposed_scale_onscreen variable.
1715 bNewView |= SetViewPoint(vpLat, vpLon,
1716 GetCanvasScaleFactor() / proposed_scale_onscreen,
1717 0, GetVPRotation());
1718 }
1719 // Measure rough jump distance if in bfollow mode
1720 // No good reason to do smooth pan for
1721 // jump distance more than one screen width at scale.
1722 bool super_jump = false;
1723 if (m_bFollow) {
1724 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1725 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1726 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1727 }
1728 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1729 goto update_finish;
1730 }
1731
1732 // Single Chart Mode from here....
1733 pLast_Ch = m_singleChart;
1734 ChartTypeEnum new_open_type;
1735 ChartFamilyEnum new_open_family;
1736 if (pLast_Ch) {
1737 new_open_type = pLast_Ch->GetChartType();
1738 new_open_family = pLast_Ch->GetChartFamily();
1739 } else {
1740 new_open_type = CHART_TYPE_KAP;
1741 new_open_family = CHART_FAMILY_RASTER;
1742 }
1743
1744 bOpenSpecified = m_bFirstAuto;
1745
1746 // Make sure the target stack is valid
1747 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1748
1749 // Build a chart stack based on tLat, tLon
1750 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1751 m_groupIndex)) { // Bogus Lat, Lon?
1752 if (NULL == pDummyChart) {
1753 pDummyChart = new ChartDummy;
1754 bNewChart = true;
1755 }
1756
1757 if (m_singleChart)
1758 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1759
1760 m_singleChart = pDummyChart;
1761
1762 // If the current viewpoint is invalid, set the default scale to
1763 // something reasonable.
1764 double set_scale = GetVPScale();
1765 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1766
1767 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1768
1769 // If the chart stack has just changed, there is new status
1770 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1771 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1772 bNewPiano = true;
1773 bNewChart = true;
1774 }
1775 }
1776
1777 // Copy the new (by definition empty) stack into the target stack
1778 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1779
1780 goto update_finish;
1781 }
1782
1783 // Check to see if Chart Stack has changed
1784 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1785 // New chart stack, so...
1786 bNewPiano = true;
1787
1788 // Save a copy of the current stack
1789 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1790
1791 // Copy the new stack into the target stack
1792 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1793
1794 // Is Current Chart in new stack?
1795
1796 int tEntry = -1;
1797 if (NULL != m_singleChart) // this handles startup case
1798 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1799 m_singleChart->GetFullPath());
1800
1801 if (tEntry != -1) { // m_singleChart is in the new stack
1802 m_pCurrentStack->CurrentStackEntry = tEntry;
1803 bNewChart = false;
1804 }
1805
1806 else // m_singleChart is NOT in new stack, or m_singlechart not yet set
1807 { // So, need to open a new chart
1808 // Find the largest scale raster chart that opens OK
1809
1810 ChartBase *pProposed = NULL;
1811
1812 if (bCanvasChartAutoOpen) {
1813 bool search_direction =
1814 false; // default is to search from lowest to highest
1815 int start_index = 0;
1816
1817 // A special case: If panning at high scale, open largest scale
1818 // chart first
1819 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1820 (LastStack.nEntry == 0)) {
1821 search_direction = true;
1822 start_index = m_pCurrentStack->nEntry - 1;
1823 }
1824
1825 // Another special case, open specified db index on program start
1826 if (bOpenSpecified) {
1827 if (m_restore_dbindex >= 0) {
1828 pProposed =
1829 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
1830 std::vector<int> one_array;
1831 one_array.push_back(m_restore_dbindex);
1832 m_Piano->SetActiveKeyArray(one_array);
1833 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
1834 m_restore_dbindex = -1; // Mark as used...
1835 }
1836
1837 if (!pProposed) {
1838 search_direction = false;
1839 start_index = m_restore_dbindex;
1840 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1841 start_index = 0;
1842
1843 new_open_type = CHART_TYPE_DONTCARE;
1844 }
1845 }
1846
1847 if (!pProposed) {
1848 pProposed = ChartData->OpenStackChartConditional(
1849 m_pCurrentStack, start_index, search_direction, new_open_type,
1850 new_open_family);
1851
1852 // Try to open other types/families of chart in some priority
1853 if (NULL == pProposed)
1854 pProposed = ChartData->OpenStackChartConditional(
1855 m_pCurrentStack, start_index, search_direction,
1856 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1857
1858 if (NULL == pProposed)
1859 pProposed = ChartData->OpenStackChartConditional(
1860 m_pCurrentStack, start_index, search_direction,
1861 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1862
1863 bNewChart = true;
1864 }
1865 } // bCanvasChartAutoOpen
1866
1867 else
1868 pProposed = NULL;
1869
1870 // If no go, then
1871 // Open a Dummy Chart
1872 if (NULL == pProposed) {
1873 if (NULL == pDummyChart) {
1874 pDummyChart = new ChartDummy;
1875 bNewChart = true;
1876 }
1877
1878 if (pLast_Ch)
1879 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1880
1881 pProposed = pDummyChart;
1882 }
1883
1884 // Arriving here, pProposed points to an opened chart, or NULL.
1885 if (m_singleChart) m_singleChart->Deactivate();
1886 m_singleChart = pProposed;
1887
1888 if (m_singleChart) {
1889 m_singleChart->Activate();
1890 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1891 m_pCurrentStack, m_singleChart->GetFullPath());
1892 }
1893 } // need new chart
1894
1895 // Arriving here, m_singleChart is opened and OK, or NULL
1896 if (NULL != m_singleChart) {
1897 // Setup the view using the current scale
1898 double set_scale = GetVPScale();
1899
1900 double proposed_scale_onscreen;
1901
1902 if (m_bFollow) { // autoset the scale only if in autofollow
1903 double new_scale_ppm =
1904 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1905 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1906 } else
1907 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1908
1909 // This logic will bring a new chart onscreen at roughly twice the true
1910 // paper scale equivalent. Note that first chart opened on application
1911 // startup (bOpenSpecified = true) will open at the config saved scale
1912 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1913 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1914 double equivalent_vp_scale =
1915 GetCanvasScaleFactor() / proposed_scale_onscreen;
1916 double new_scale_ppm =
1917 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1918 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1919 }
1920
1921 if (m_bFollow) { // bounds-check the scale only if in autofollow
1922 proposed_scale_onscreen =
1923 wxMin(proposed_scale_onscreen,
1924 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1925 GetCanvasWidth()));
1926 proposed_scale_onscreen =
1927 wxMax(proposed_scale_onscreen,
1928 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1930 }
1931
1932 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1933
1934 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 double s = GetVPScale();
1943 if ((m_bFollow) && m_singleChart)
1944 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1945 m_singleChart->GetChartSkew() * PI / 180.,
1946 GetVPRotation());
1947 }
1948
1949update_finish:
1950
1951 // TODO
1952 // if( bNewPiano ) UpdateControlBar();
1953
1954 m_bFirstAuto = false; // Auto open on program start
1955
1956 // If we need a Refresh(), do it here...
1957 // But don't duplicate a Refresh() done by SetViewPoint()
1958 if (bNewChart && !bNewView) Refresh(false);
1959
1960#ifdef ocpnUSE_GL
1961 // If a new chart, need to invalidate gl viewport for refresh
1962 // so the fbo gets flushed
1963 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1964#endif
1965
1966 return bNewChart | bNewView;
1967}
1968
1969void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1970 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1971
1972 SetQuiltRefChart(db_index);
1973 if (ChartData) {
1974 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1975 if (pc) {
1976 if (b_autoscale) {
1977 double best_scale_ppm = GetBestVPScale(pc);
1978 SetVPScale(best_scale_ppm);
1979 }
1980 } else
1981 SetQuiltRefChart(-1);
1982 } else
1983 SetQuiltRefChart(-1);
1984}
1985
1986void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1987 std::vector<int> piano_chart_index_array =
1988 GetQuiltExtendedStackdbIndexArray();
1989 int current_db_index = piano_chart_index_array[selected_index];
1990
1991 SelectQuiltRefdbChart(current_db_index);
1992}
1993
1994double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1995 if (pchart) {
1996 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1997
1998 if ((g_bPreserveScaleOnX) ||
1999 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2000 double new_scale_ppm = GetVPScale();
2001 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2002 } else {
2003 // This logic will bring the new chart onscreen at roughly twice the true
2004 // paper scale equivalent.
2005 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2006 double equivalent_vp_scale =
2007 GetCanvasScaleFactor() / proposed_scale_onscreen;
2008 double new_scale_ppm =
2009 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2010 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2011 }
2012
2013 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2014 // set. Otherwise, we get severe performance problems on all platforms
2015
2016 double max_underzoom_multiplier = 2.0;
2017 if (GetVP().b_quilt) {
2018 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2019 pchart->GetChartType(),
2020 pchart->GetChartFamily());
2021 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2022 }
2023
2024 proposed_scale_onscreen = wxMin(
2025 proposed_scale_onscreen,
2026 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2027 max_underzoom_multiplier);
2028
2029 // And, do not allow excessive overzoom either
2030 proposed_scale_onscreen =
2031 wxMax(proposed_scale_onscreen,
2032 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2033
2034 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2035 } else
2036 return 1.0;
2037}
2038
2039void ChartCanvas::SetupCanvasQuiltMode() {
2040 if (GetQuiltMode()) // going to quilt mode
2041 {
2042 ChartData->LockCache();
2043
2044 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2045
2046 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2047
2048 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2049 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2050 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2051 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2052
2053 m_Piano->SetRoundedRectangles(true);
2054
2055 // Select the proper Ref chart
2056 int target_new_dbindex = -1;
2057 if (m_pCurrentStack) {
2058 target_new_dbindex =
2059 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2060
2061 if (-1 != target_new_dbindex) {
2062 if (!IsChartQuiltableRef(target_new_dbindex)) {
2063 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2064 int type = ChartData->GetDBChartType(target_new_dbindex);
2065
2066 // walk the stack up looking for a satisfactory chart
2067 int stack_index = m_pCurrentStack->CurrentStackEntry;
2068
2069 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2070 (stack_index >= 0)) {
2071 int proj_tent = ChartData->GetDBChartProj(
2072 m_pCurrentStack->GetDBIndex(stack_index));
2073 int type_tent = ChartData->GetDBChartType(
2074 m_pCurrentStack->GetDBIndex(stack_index));
2075
2076 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2077 if ((proj == proj_tent) && (type_tent == type)) {
2078 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2079 break;
2080 }
2081 }
2082 stack_index++;
2083 }
2084 }
2085 }
2086 }
2087
2088 if (IsChartQuiltableRef(target_new_dbindex))
2089 SelectQuiltRefdbChart(target_new_dbindex,
2090 false); // Try not to allow a scale change
2091 else { // fall back to last selected no-quilt chart as new reference
2092 int stack_index = m_pCurrentStack->CurrentStackEntry;
2093 SelectQuiltRefdbChart(m_pCurrentStack->GetDBIndex(stack_index), false);
2094 }
2095
2096 m_singleChart = NULL; // Bye....
2097
2098 // Re-qualify the quilt reference chart selection
2099 AdjustQuiltRefChart();
2100
2101 // Restore projection type saved on last quilt mode toggle
2102 // TODO
2103 // if(g_sticky_projection != -1)
2104 // GetVP().SetProjectionType(g_sticky_projection);
2105 // else
2106 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2107 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2108
2109 } else // going to SC Mode
2110 {
2111 std::vector<int> empty_array;
2112 m_Piano->SetActiveKeyArray(empty_array);
2113 m_Piano->SetNoshowIndexArray(empty_array);
2114 m_Piano->SetEclipsedIndexArray(empty_array);
2115
2116 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2117 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2118 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2119 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2120 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2121
2122 m_Piano->SetRoundedRectangles(false);
2123 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2124 }
2125
2126 // When shifting from quilt to single chart mode, select the "best" single
2127 // chart to show
2128 if (!GetQuiltMode()) {
2129 if (ChartData && ChartData->IsValid()) {
2130 UnlockQuilt();
2131
2132 double tLat, tLon;
2133 if (m_bFollow == true) {
2134 tLat = gLat;
2135 tLon = gLon;
2136 } else {
2137 tLat = m_vLat;
2138 tLon = m_vLon;
2139 }
2140
2141 if (!m_singleChart) {
2142 // First choice is to adopt the outgoing quilt reference chart
2143 if (GetQuiltReferenceChartIndex() >= 0) {
2144 m_singleChart = ChartData->OpenChartFromDB(
2145 GetQuiltReferenceChartIndex(), FULL_INIT);
2146 }
2147 // Second choice is to use any "no-quilt restore index", if available
2148 else if (m_restore_dbindex >= 0) {
2149 m_singleChart =
2150 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
2151 }
2152 // Final choice it to pick a suitable chart based on current stack.
2153 else {
2154 // Build a temporary chart stack based on tLat, tLon
2155 ChartStack TempStack;
2156 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2157 m_groupIndex);
2158
2159 // Iterate over the quilt charts actually shown, looking for the
2160 // largest scale chart that will be in the new chartstack.... This
2161 // will (almost?) always be the reference chart....
2162
2163 ChartBase *Candidate_Chart = NULL;
2164 int cur_max_scale = (int)1e8;
2165
2166 ChartBase *pChart = GetFirstQuiltChart();
2167 while (pChart) {
2168 // Is this pChart in new stack?
2169 int tEntry =
2170 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2171 if (tEntry != -1) {
2172 if (pChart->GetNativeScale() < cur_max_scale) {
2173 Candidate_Chart = pChart;
2174 cur_max_scale = pChart->GetNativeScale();
2175 }
2176 }
2177 pChart = GetNextQuiltChart();
2178 }
2179
2180 m_singleChart = Candidate_Chart;
2181
2182 // If the quilt is empty, there is no "best" chart.
2183 // So, open the smallest scale chart in the current stack
2184 if (NULL == m_singleChart) {
2185 m_singleChart = ChartData->OpenStackChartConditional(
2186 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2187 CHART_FAMILY_DONTCARE);
2188 }
2189 }
2190 }
2191 // Invalidate all the charts in the quilt,
2192 // as any cached data may be region based and not have fullscreen coverage
2193 InvalidateAllQuiltPatchs();
2194
2195 if (m_singleChart) {
2196 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2197 std::vector<int> one_array;
2198 one_array.push_back(dbi);
2199 m_Piano->SetActiveKeyArray(one_array);
2200 }
2201
2202 if (m_singleChart) {
2203 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2204 }
2205 }
2206 }
2207 // Invalidate the current stack so that it will be rebuilt on next tick
2208 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2209 SetVPScale(GetVPScale() * 1.0001);
2210}
2211
2212bool ChartCanvas::IsTempMenuBarEnabled() {
2213#ifdef __WXMSW__
2214 int major;
2215 wxGetOsVersion(&major);
2216 return (major >
2217 5); // For Windows, function is only available on Vista and above
2218#else
2219 return true;
2220#endif
2221}
2222
2223double ChartCanvas::GetCanvasRangeMeters() {
2224 int width, height;
2225 GetSize(&width, &height);
2226 int minDimension = wxMin(width, height);
2227
2228 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2229 range *= cos(GetVP().clat * PI / 180.);
2230 return range;
2231}
2232
2233void ChartCanvas::SetCanvasRangeMeters(double range) {
2234 int width, height;
2235 GetSize(&width, &height);
2236 int minDimension = wxMin(width, height);
2237
2238 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2239 SetVPScale(scale_ppm / 2);
2240}
2241
2242bool ChartCanvas::SetUserOwnship() {
2243 // Look for user defined ownship image
2244 // This may be found in the shared data location along with other user
2245 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2246 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2247 double factor_dusk = 0.5;
2248 double factor_night = 0.25;
2249
2250 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2251 m_pos_image_user_day = new wxImage;
2252 *m_pos_image_user_day = pbmp->ConvertToImage();
2253 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2254
2255 int gimg_width = m_pos_image_user_day->GetWidth();
2256 int gimg_height = m_pos_image_user_day->GetHeight();
2257
2258 // Make dusk and night images
2259 m_pos_image_user_dusk = new wxImage;
2260 m_pos_image_user_night = new wxImage;
2261
2262 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2263 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2264
2265 for (int iy = 0; iy < gimg_height; iy++) {
2266 for (int ix = 0; ix < gimg_width; ix++) {
2267 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2268 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2269 m_pos_image_user_day->GetGreen(ix, iy),
2270 m_pos_image_user_day->GetBlue(ix, iy));
2271 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2272 hsv.value = hsv.value * factor_dusk;
2273 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2274 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2275 nrgb.blue);
2276
2277 hsv = wxImage::RGBtoHSV(rgb);
2278 hsv.value = hsv.value * factor_night;
2279 nrgb = wxImage::HSVtoRGB(hsv);
2280 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2281 nrgb.blue);
2282 }
2283 }
2284 }
2285
2286 // Make some alternate greyed out day/dusk/night images
2287 m_pos_image_user_grey_day = new wxImage;
2288 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2289
2290 m_pos_image_user_grey_dusk = new wxImage;
2291 m_pos_image_user_grey_night = new wxImage;
2292
2293 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2294 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2295
2296 for (int iy = 0; iy < gimg_height; iy++) {
2297 for (int ix = 0; ix < gimg_width; ix++) {
2298 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2299 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2300 m_pos_image_user_grey_day->GetGreen(ix, iy),
2301 m_pos_image_user_grey_day->GetBlue(ix, iy));
2302 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2303 hsv.value = hsv.value * factor_dusk;
2304 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2305 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2306 nrgb.blue);
2307
2308 hsv = wxImage::RGBtoHSV(rgb);
2309 hsv.value = hsv.value * factor_night;
2310 nrgb = wxImage::HSVtoRGB(hsv);
2311 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2312 nrgb.blue);
2313 }
2314 }
2315 }
2316
2317 // Make a yellow image for rendering under low accuracy chart conditions
2318 m_pos_image_user_yellow_day = new wxImage;
2319 m_pos_image_user_yellow_dusk = new wxImage;
2320 m_pos_image_user_yellow_night = new wxImage;
2321
2322 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2323 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2324 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2325
2326 for (int iy = 0; iy < gimg_height; iy++) {
2327 for (int ix = 0; ix < gimg_width; ix++) {
2328 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2329 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2330 m_pos_image_user_grey_day->GetGreen(ix, iy),
2331 m_pos_image_user_grey_day->GetBlue(ix, iy));
2332
2333 // Simply remove all "blue" from the greyscaled image...
2334 // so, what is not black becomes yellow.
2335 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2336 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2337 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2338
2339 hsv = wxImage::RGBtoHSV(rgb);
2340 hsv.value = hsv.value * factor_dusk;
2341 nrgb = wxImage::HSVtoRGB(hsv);
2342 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2343
2344 hsv = wxImage::RGBtoHSV(rgb);
2345 hsv.value = hsv.value * factor_night;
2346 nrgb = wxImage::HSVtoRGB(hsv);
2347 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2348 0);
2349 }
2350 }
2351 }
2352
2353 return true;
2354 } else
2355 return false;
2356}
2357
2359 m_display_size_mm = size;
2360
2361 // int sx, sy;
2362 // wxDisplaySize( &sx, &sy );
2363
2364 // Calculate logical pixels per mm for later reference.
2365 wxSize sd = g_Platform->getDisplaySize();
2366 double horizontal = sd.x;
2367 // Set DPI (Win) scale factor
2368 g_scaler = g_Platform->GetDisplayDIPMult(this);
2369
2370 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2371 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2372
2373 if (ps52plib) {
2374 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2375 ps52plib->SetPPMM(m_pix_per_mm);
2376 }
2377
2378 wxString msg;
2379 msg.Printf(
2380 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2381 "%d:%d ",
2382 m_display_size_mm, sd.x, sd.y);
2383 wxLogDebug(msg);
2384
2385 int ssx, ssy;
2386 ssx = g_monitor_info[g_current_monitor].width;
2387 ssy = g_monitor_info[g_current_monitor].height;
2388 msg.Printf("monitor size: %d %d", ssx, ssy);
2389 wxLogDebug(msg);
2390
2391 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2392}
2393#if 0
2394void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2395{
2396 wxString msg(event.m_string.c_str(), wxConvUTF8);
2397 // if cpus are removed between runs
2398 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2399 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2400 }
2401
2402 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2403 {
2404 compress_msg_array.RemoveAt(event.thread);
2405 compress_msg_array.Insert( msg, event.thread);
2406 }
2407 else
2408 compress_msg_array.Add(msg);
2409
2410
2411 wxString combined_msg;
2412 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2413 combined_msg += compress_msg_array[i];
2414 combined_msg += "\n";
2415 }
2416
2417 bool skip = false;
2418 pprog->Update(pprog_count, combined_msg, &skip );
2419 pprog->SetSize(pprog_size);
2420 if(skip)
2421 b_skipout = skip;
2422}
2423#endif
2424void ChartCanvas::InvalidateGL() {
2425 if (!m_glcc) return;
2426#ifdef ocpnUSE_GL
2427 if (g_bopengl) m_glcc->Invalidate();
2428#endif
2429 if (m_Compass) m_Compass->UpdateStatus(true);
2430}
2431
2432int ChartCanvas::GetCanvasChartNativeScale() {
2433 int ret = 1;
2434 if (!VPoint.b_quilt) {
2435 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2436 } else
2437 ret = (int)m_pQuilt->GetRefNativeScale();
2438
2439 return ret;
2440}
2441
2442ChartBase *ChartCanvas::GetChartAtCursor() {
2443 ChartBase *target_chart;
2444 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2445 target_chart = m_singleChart;
2446 else if (VPoint.b_quilt)
2447 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2448 else
2449 target_chart = NULL;
2450 return target_chart;
2451}
2452
2453ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2454 ChartBase *target_chart;
2455 if (VPoint.b_quilt)
2456 target_chart =
2457 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2458 else
2459 target_chart = NULL;
2460 return target_chart;
2461}
2462
2463int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2464 int new_dbIndex = -1;
2465 if (!VPoint.b_quilt) {
2466 if (m_pCurrentStack) {
2467 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2468 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2469 if (sc >= scale) {
2470 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2471 break;
2472 }
2473 }
2474 }
2475 } else {
2476 // Using the current quilt, select a useable reference chart
2477 // Said chart will be in the extended (possibly full-screen) stack,
2478 // And will have a scale equal to or just greater than the stipulated
2479 // value, and will belong to the same chart family (RNC/ENC)
2480 // If no family match is found, then family requirement is ignored.
2481 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2482 if (im > 0) {
2483 // Find closest using scale and family
2484 for (unsigned int is = 0; is < im; is++) {
2485 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2486 m_pQuilt->GetExtendedStackIndexArray()[is]);
2487 if ((m.Scale_ge(scale)) &&
2488 (m_pQuilt->GetRefFamily() == m.GetChartFamily())) {
2489 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2490 break;
2491 }
2492 }
2493 // if not found, likely due to Family requirement
2494 // That is, there is no matching family chart in the StackIndexArray.
2495 // Try again, without consideration of family.
2496 if (new_dbIndex < 0) {
2497 for (unsigned int is = 0; is < im; is++) {
2498 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2499 m_pQuilt->GetExtendedStackIndexArray()[is]);
2500 if (m.Scale_ge(scale)) {
2501 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2502 break;
2503 }
2504 }
2505 }
2506 }
2507 }
2508
2509 return new_dbIndex;
2510}
2511
2512void ChartCanvas::EnablePaint(bool b_enable) {
2513 m_b_paint_enable = b_enable;
2514#ifdef ocpnUSE_GL
2515 if (m_glcc) m_glcc->EnablePaint(b_enable);
2516#endif
2517}
2518
2519bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2520
2521void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2522
2523std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2524 return m_pQuilt->GetQuiltIndexArray();
2525 ;
2526}
2527
2528void ChartCanvas::SetQuiltMode(bool b_quilt) {
2529 VPoint.b_quilt = b_quilt;
2530 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2531}
2532
2533bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2534
2535int ChartCanvas::GetQuiltReferenceChartIndex() {
2536 return m_pQuilt->GetRefChartdbIndex();
2537}
2538
2539void ChartCanvas::InvalidateAllQuiltPatchs() {
2540 m_pQuilt->InvalidateAllQuiltPatchs();
2541}
2542
2543ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2544 return m_pQuilt->GetLargestScaleChart();
2545}
2546
2547ChartBase *ChartCanvas::GetFirstQuiltChart() {
2548 return m_pQuilt->GetFirstChart();
2549}
2550
2551ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2552
2553int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2554
2555void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2556 m_pQuilt->SetHiliteIndex(dbIndex);
2557}
2558
2559void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2560 m_pQuilt->SetHiliteIndexArray(hilite_array);
2561}
2562
2563void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2564 m_pQuilt->ClearHiliteIndexArray();
2565}
2566
2567std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2568 bool flag2) {
2569 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2570}
2571
2572int ChartCanvas::GetQuiltRefChartdbIndex() {
2573 return m_pQuilt->GetRefChartdbIndex();
2574}
2575
2576std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2577 return m_pQuilt->GetExtendedStackIndexArray();
2578}
2579
2580std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2581 return m_pQuilt->GetFullscreenIndexArray();
2582}
2583
2584std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2585 return m_pQuilt->GetEclipsedStackIndexArray();
2586}
2587
2588void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2589
2590double ChartCanvas::GetQuiltMaxErrorFactor() {
2591 return m_pQuilt->GetMaxErrorFactor();
2592}
2593
2594bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2595 return m_pQuilt->IsChartQuiltableRef(db_index);
2596}
2597
2598bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2599 double chartMaxScale =
2600 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2601 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2602}
2603
2604void ChartCanvas::StartMeasureRoute() {
2605 if (!m_routeState) { // no measure tool if currently creating route
2606 if (m_bMeasure_Active) {
2607 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2608 m_pMeasureRoute = NULL;
2609 }
2610
2611 m_bMeasure_Active = true;
2612 m_nMeasureState = 1;
2613 m_bDrawingRoute = false;
2614
2615 SetCursor(*pCursorPencil);
2616 Refresh();
2617 }
2618}
2619
2620void ChartCanvas::CancelMeasureRoute() {
2621 m_bMeasure_Active = false;
2622 m_nMeasureState = 0;
2623 m_bDrawingRoute = false;
2624
2625 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2626 m_pMeasureRoute = NULL;
2627
2628 SetCursor(*pCursorArrow);
2629}
2630
2631ViewPort &ChartCanvas::GetVP() { return VPoint; }
2632
2633void ChartCanvas::SetVP(ViewPort &vp) {
2634 VPoint = vp;
2635 VPoint.SetPixelScale(m_displayScale);
2636}
2637
2638// void ChartCanvas::SetFocus()
2639// {
2640// printf("set %d\n", m_canvasIndex);
2641// //wxWindow:SetFocus();
2642// }
2643
2644void ChartCanvas::TriggerDeferredFocus() {
2645 // #if defined(__WXGTK__) || defined(__WXOSX__)
2646
2647 m_deferredFocusTimer.Start(20, true);
2648
2649#if defined(__WXGTK__) || defined(__WXOSX__)
2650 top_frame::Get()->Raise();
2651#endif
2652
2653 // top_frame::Get()->Raise();
2654 // #else
2655 // SetFocus();
2656 // Refresh(true);
2657 // #endif
2658}
2659
2660void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2661 SetFocus();
2662 Refresh(true);
2663}
2664
2665void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2666 if (SendKeyEventToPlugins(event))
2667 return; // PlugIn did something, and does not want the canvas to do
2668 // anything else
2669
2670 int key_char = event.GetKeyCode();
2671 switch (key_char) {
2672 case '?':
2673 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2674 break;
2675 case '+':
2676 ZoomCanvas(g_plus_minus_zoom_factor, false);
2677 break;
2678 case '-':
2679 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2680 break;
2681 default:
2682 break;
2683 }
2684 if (g_benable_rotate) {
2685 switch (key_char) {
2686 case ']':
2687 RotateCanvas(1);
2688 Refresh();
2689 break;
2690
2691 case '[':
2692 RotateCanvas(-1);
2693 Refresh();
2694 break;
2695
2696 case '\\':
2697 DoRotateCanvas(0);
2698 break;
2699 }
2700 }
2701
2702 event.Skip();
2703}
2704
2705void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2706 if (SendKeyEventToPlugins(event))
2707 return; // PlugIn did something, and does not want the canvas to do
2708 // anything else
2709
2710 bool b_handled = false;
2711
2712 m_modkeys = event.GetModifiers();
2713
2714 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2715
2716#ifdef OCPN_ALT_MENUBAR
2717#ifndef __WXOSX__
2718 // If the permanent menubar is disabled, we show it temporarily when Alt is
2719 // pressed or when Alt + a letter is presssed (for the top-menu-level
2720 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2721 // some special cases.
2722 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2723 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2724 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2725 if (!g_bTempShowMenuBar) {
2726 g_bTempShowMenuBar = true;
2727 top_frame::Get()->ApplyGlobalSettings(false);
2728 }
2729 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2730 event.Skip();
2731 return;
2732 }
2733 // If another key is pressed while Alt is down, do NOT toggle the menus when
2734 // Alt is released
2735 if (event.GetKeyCode() != WXK_ALT) {
2736 m_bMayToggleMenuBar = false;
2737 }
2738 }
2739#endif
2740#endif
2741
2742 // HOTKEYS
2743 switch (event.GetKeyCode()) {
2744 case WXK_TAB:
2745 // parent_frame->SwitchKBFocus( this );
2746 break;
2747
2748 case WXK_MENU:
2749 int x, y;
2750 event.GetPosition(&x, &y);
2751 m_FinishRouteOnKillFocus = false;
2752 CallPopupMenu(x, y);
2753 m_FinishRouteOnKillFocus = true;
2754 break;
2755
2756 case WXK_ALT:
2757 m_modkeys |= wxMOD_ALT;
2758 break;
2759
2760 case WXK_CONTROL:
2761 m_modkeys |= wxMOD_CONTROL;
2762 break;
2763
2764#ifdef __WXOSX__
2765 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2766 case WXK_RAW_CONTROL:
2767 m_modkeys |= wxMOD_RAW_CONTROL;
2768 break;
2769#endif
2770
2771 case WXK_LEFT:
2772 if (m_modkeys == wxMOD_CONTROL)
2773 top_frame::Get()->DoStackDown(this);
2774 else if (g_bsmoothpanzoom) {
2775 StartTimedMovement();
2776 m_panx = -1;
2777 } else {
2778 PanCanvas(-panspeed, 0);
2779 }
2780 b_handled = true;
2781 break;
2782
2783 case WXK_UP:
2784 if (g_bsmoothpanzoom) {
2785 StartTimedMovement();
2786 m_pany = -1;
2787 } else
2788 PanCanvas(0, -panspeed);
2789 b_handled = true;
2790 break;
2791
2792 case WXK_RIGHT:
2793 if (m_modkeys == wxMOD_CONTROL)
2794 top_frame::Get()->DoStackUp(this);
2795 else if (g_bsmoothpanzoom) {
2796 StartTimedMovement();
2797 m_panx = 1;
2798 } else
2799 PanCanvas(panspeed, 0);
2800 b_handled = true;
2801
2802 break;
2803
2804 case WXK_DOWN:
2805 if (g_bsmoothpanzoom) {
2806 StartTimedMovement();
2807 m_pany = 1;
2808 } else
2809 PanCanvas(0, panspeed);
2810 b_handled = true;
2811 break;
2812
2813 case WXK_F2: {
2814 if (event.ShiftDown()) {
2815 double scale = GetVP().view_scale_ppm;
2816 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2817
2818 std::shared_ptr<HostApi> host_api;
2819 host_api = GetHostApi();
2820 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2821
2822 if (api_121)
2823 api_121->SelectChartFamily(m_canvasIndex,
2824 (ChartFamilyEnumPI)target_family);
2825 } else
2826 TogglebFollow();
2827 break;
2828 }
2829 case WXK_F3: {
2830 if (event.ShiftDown()) {
2831 double scale = GetVP().view_scale_ppm;
2832 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2833
2834 std::shared_ptr<HostApi> host_api;
2835 host_api = GetHostApi();
2836 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2837
2838 if (api_121)
2839 api_121->SelectChartFamily(m_canvasIndex,
2840 (ChartFamilyEnumPI)target_family);
2841 } else {
2842 SetShowENCText(!GetShowENCText());
2843 Refresh(true);
2844 InvalidateGL();
2845 }
2846 break;
2847 }
2848 case WXK_F4:
2849 if (!m_bMeasure_Active) {
2850 if (event.ShiftDown())
2851 m_bMeasure_DistCircle = true;
2852 else
2853 m_bMeasure_DistCircle = false;
2854
2855 StartMeasureRoute();
2856 } else {
2857 CancelMeasureRoute();
2858
2859 SetCursor(*pCursorArrow);
2860
2861 // SurfaceToolbar();
2862 InvalidateGL();
2863 Refresh(false);
2864 }
2865
2866 break;
2867
2868 case WXK_F5:
2869 top_frame::Get()->ToggleColorScheme();
2870 top_frame::Get()->Raise();
2871 TriggerDeferredFocus();
2872 break;
2873
2874 case WXK_F6: {
2875 int mod = m_modkeys & wxMOD_SHIFT;
2876 if (mod != m_brightmod) {
2877 m_brightmod = mod;
2878 m_bbrightdir = !m_bbrightdir;
2879 }
2880
2881 if (!m_bbrightdir) {
2882 g_nbrightness -= 10;
2883 if (g_nbrightness <= MIN_BRIGHT) {
2884 g_nbrightness = MIN_BRIGHT;
2885 m_bbrightdir = true;
2886 }
2887 } else {
2888 g_nbrightness += 10;
2889 if (g_nbrightness >= MAX_BRIGHT) {
2890 g_nbrightness = MAX_BRIGHT;
2891 m_bbrightdir = false;
2892 }
2893 }
2894
2895 SetScreenBrightness(g_nbrightness);
2896 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2897
2898 SetFocus(); // just in case the external program steals it....
2899 top_frame::Get()->Raise(); // And reactivate the application main
2900
2901 break;
2902 }
2903
2904 case WXK_F7:
2905 top_frame::Get()->DoStackDown(this);
2906 break;
2907
2908 case WXK_F8:
2909 top_frame::Get()->DoStackUp(this);
2910 break;
2911
2912#ifndef __WXOSX__
2913 case WXK_F9: {
2914 ToggleCanvasQuiltMode();
2915 break;
2916 }
2917#endif
2918
2919 case WXK_F11:
2920 top_frame::Get()->ToggleFullScreen();
2921 b_handled = true;
2922 break;
2923
2924 case WXK_F12: {
2925 if (m_modkeys == wxMOD_ALT) {
2926 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2927 } else {
2928 ToggleChartOutlines();
2929 }
2930 break;
2931 }
2932
2933 case WXK_PAUSE: // Drop MOB
2934 top_frame::Get()->ActivateMOB();
2935 break;
2936
2937 // NUMERIC PAD
2938 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2939 case WXK_PAGEUP: {
2940 ZoomCanvas(g_plus_minus_zoom_factor, false);
2941 break;
2942 }
2943 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2944 case WXK_PAGEDOWN: {
2945 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2946 break;
2947 }
2948 case WXK_DELETE:
2949 case WXK_BACK:
2950 if (m_bMeasure_Active) {
2951 if (m_nMeasureState > 2) {
2952 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2953 m_pMeasureRoute->m_lastMousePointIndex =
2954 m_pMeasureRoute->GetnPoints();
2955 m_nMeasureState--;
2956 top_frame::Get()->RefreshAllCanvas();
2957 } else {
2958 CancelMeasureRoute();
2959 StartMeasureRoute();
2960 }
2961 }
2962 break;
2963 default:
2964 break;
2965 }
2966
2967 if (event.GetKeyCode() < 128) // ascii
2968 {
2969 int key_char = event.GetKeyCode();
2970
2971 // Handle both QWERTY and AZERTY keyboard separately for a few control
2972 // codes
2973 if (!g_b_assume_azerty) {
2974#ifdef __WXMAC__
2975 if (g_benable_rotate) {
2976 switch (key_char) {
2977 // On other platforms these are handled in OnKeyChar, which
2978 // (apparently) works better in some locales. On OS X it is better
2979 // to handle them here, since pressing Alt (which should change the
2980 // rotation speed) changes the key char and so prevents the keys
2981 // from working.
2982 case ']':
2983 RotateCanvas(1);
2984 b_handled = true;
2985 break;
2986
2987 case '[':
2988 RotateCanvas(-1);
2989 b_handled = true;
2990 break;
2991
2992 case '\\':
2993 DoRotateCanvas(0);
2994 b_handled = true;
2995 break;
2996 }
2997 }
2998#endif
2999 } else { // AZERTY
3000 switch (key_char) {
3001 case 43:
3002 ZoomCanvas(g_plus_minus_zoom_factor, false);
3003 break;
3004
3005 case 54: // '-' alpha/num pad
3006 // case 56: // '_' alpha/num pad
3007 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3008 break;
3009 }
3010 }
3011
3012#ifdef __WXOSX__
3013 // Ctrl+Cmd+F toggles fullscreen on macOS
3014 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3015 m_modkeys & wxMOD_RAW_CONTROL) {
3016 top_frame::Get()->ToggleFullScreen();
3017 return;
3018 }
3019#endif
3020
3021 if (event.ControlDown()) key_char -= 64;
3022
3023 if (key_char >= '0' && key_char <= '9')
3024 SetGroupIndex(key_char - '0');
3025 else
3026
3027 switch (key_char) {
3028 case 'A':
3029 SetShowENCAnchor(!GetShowENCAnchor());
3030 ReloadVP();
3031
3032 break;
3033
3034 case 'C':
3035 top_frame::Get()->ToggleColorScheme();
3036 break;
3037
3038 case 'D': {
3039 int x, y;
3040 event.GetPosition(&x, &y);
3041 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3042 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3043 // First find out what kind of chart is being used
3044 if (!pPopupDetailSlider) {
3045 if (VPoint.b_quilt) {
3046 if (m_pQuilt) {
3047 if (m_pQuilt->GetChartAtPix(
3048 VPoint,
3049 wxPoint(
3050 x, y))) // = null if no chart loaded for this point
3051 {
3052 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3053 ->GetChartType();
3054 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3055 ->GetChartFamily();
3056 }
3057 }
3058 } else {
3059 if (m_singleChart) {
3060 ChartType = m_singleChart->GetChartType();
3061 ChartFam = m_singleChart->GetChartFamily();
3062 }
3063 }
3064 // If a charttype is found show the popupslider
3065 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3066 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3068 this, -1, ChartType, ChartFam,
3069 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3070 wxDefaultSize, wxSIMPLE_BORDER, "");
3072 }
3073 } else //( !pPopupDetailSlider ) close popupslider
3074 {
3076 pPopupDetailSlider = NULL;
3077 }
3078 break;
3079 }
3080
3081 case 'E':
3082 m_nmea_log->Show();
3083 m_nmea_log->Raise();
3084 break;
3085
3086 case 'L':
3087 SetShowENCLights(!GetShowENCLights());
3088 ReloadVP();
3089
3090 break;
3091
3092 case 'M':
3093 if (event.ShiftDown())
3094 m_bMeasure_DistCircle = true;
3095 else
3096 m_bMeasure_DistCircle = false;
3097
3098 StartMeasureRoute();
3099 break;
3100
3101 case 'N':
3102 if (g_bInlandEcdis && ps52plib) {
3103 SetENCDisplayCategory((_DisCat)STANDARD);
3104 }
3105 break;
3106
3107 case 'O':
3108 ToggleChartOutlines();
3109 break;
3110
3111 case 'Q':
3112 ToggleCanvasQuiltMode();
3113 break;
3114
3115 case 'P':
3116 top_frame::Get()->ToggleTestPause();
3117 break;
3118 case 'R':
3119 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3120 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3121 g_iNavAidRadarRingsNumberVisible = 1;
3122 else if (!g_bNavAidRadarRingsShown &&
3123 g_iNavAidRadarRingsNumberVisible == 1)
3124 g_iNavAidRadarRingsNumberVisible = 0;
3125 break;
3126 case 'S':
3127 SetShowENCDepth(!m_encShowDepth);
3128 ReloadVP();
3129 break;
3130
3131 case 'T':
3132 SetShowENCText(!GetShowENCText());
3133 ReloadVP();
3134 break;
3135
3136 case 'U':
3137 SetShowENCDataQual(!GetShowENCDataQual());
3138 ReloadVP();
3139 break;
3140
3141 case 'V':
3142 m_bShowNavobjects = !m_bShowNavobjects;
3143 Refresh(true);
3144 break;
3145
3146 case 'W': // W Toggle CPA alarm
3147 ToggleCPAWarn();
3148
3149 break;
3150
3151 case 1: // Ctrl A
3152 TogglebFollow();
3153
3154 break;
3155
3156 case 2: // Ctrl B
3157 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3158 break;
3159
3160 case 13: // Ctrl M // Drop Marker at cursor
3161 {
3162 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3163 break;
3164 }
3165
3166 case 14: // Ctrl N - Activate next waypoint in a route
3167 {
3168 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3169 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3170 if ((indexActive + 1) <= r->GetnPoints()) {
3172 InvalidateGL();
3173 Refresh(false);
3174 }
3175 }
3176 break;
3177 }
3178
3179 case 15: // Ctrl O - Drop Marker at boat's position
3180 {
3181 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3182 break;
3183 }
3184
3185 case 32: // Special needs use space bar
3186 {
3187 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3188 break;
3189 }
3190
3191 case -32: // Ctrl Space // Drop MOB
3192 {
3193 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3194
3195 break;
3196 }
3197
3198 case -20: // Ctrl ,
3199 {
3200 top_frame::Get()->DoSettings();
3201 break;
3202 }
3203 case 17: // Ctrl Q
3204 parent_frame->Close();
3205 return;
3206
3207 case 18: // Ctrl R
3208 StartRoute();
3209 return;
3210
3211 case 20: // Ctrl T
3212 if (NULL == pGoToPositionDialog) // There is one global instance of
3213 // the Go To Position Dialog
3215 pGoToPositionDialog->SetCanvas(this);
3216 pGoToPositionDialog->Show();
3217 break;
3218
3219 case 25: // Ctrl Y
3220 if (undo->AnythingToRedo()) {
3221 undo->RedoNextAction();
3222 InvalidateGL();
3223 Refresh(false);
3224 }
3225 break;
3226
3227 case 26:
3228 if (event.ShiftDown()) { // Shift-Ctrl-Z
3229 if (undo->AnythingToRedo()) {
3230 undo->RedoNextAction();
3231 InvalidateGL();
3232 Refresh(false);
3233 }
3234 } else { // Ctrl Z
3235 if (undo->AnythingToUndo()) {
3236 undo->UndoLastAction();
3237 InvalidateGL();
3238 Refresh(false);
3239 }
3240 }
3241 break;
3242
3243 case 27:
3244 // Generic break
3245 if (m_bMeasure_Active) {
3246 CancelMeasureRoute();
3247
3248 SetCursor(*pCursorArrow);
3249
3250 // SurfaceToolbar();
3251 top_frame::Get()->RefreshAllCanvas();
3252 }
3253
3254 if (m_routeState) // creating route?
3255 {
3256 FinishRoute();
3257 // SurfaceToolbar();
3258 InvalidateGL();
3259 Refresh(false);
3260 }
3261
3262 break;
3263
3264 case 7: // Ctrl G
3265 switch (gamma_state) {
3266 case (0):
3267 r_gamma_mult = 0;
3268 g_gamma_mult = 1;
3269 b_gamma_mult = 0;
3270 gamma_state = 1;
3271 break;
3272 case (1):
3273 r_gamma_mult = 1;
3274 g_gamma_mult = 0;
3275 b_gamma_mult = 0;
3276 gamma_state = 2;
3277 break;
3278 case (2):
3279 r_gamma_mult = 1;
3280 g_gamma_mult = 1;
3281 b_gamma_mult = 1;
3282 gamma_state = 0;
3283 break;
3284 }
3285 SetScreenBrightness(g_nbrightness);
3286
3287 break;
3288
3289 case 9: // Ctrl I
3290 if (event.ControlDown()) {
3291 m_bShowCompassWin = !m_bShowCompassWin;
3292 SetShowGPSCompassWindow(m_bShowCompassWin);
3293 Refresh(false);
3294 }
3295 break;
3296
3297 default:
3298 break;
3299
3300 } // switch
3301 }
3302
3303 // Allow OnKeyChar to catch the key events too.
3304 if (!b_handled) {
3305 event.Skip();
3306 }
3307}
3308
3309void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3310 if (SendKeyEventToPlugins(event))
3311 return; // PlugIn did something, and does not want the canvas to do
3312 // anything else
3313
3314 switch (event.GetKeyCode()) {
3315 case WXK_TAB:
3316 top_frame::Get()->SwitchKBFocus(this);
3317 break;
3318
3319 case WXK_LEFT:
3320 case WXK_RIGHT:
3321 m_panx = 0;
3322 if (!m_pany) m_panspeed = 0;
3323 break;
3324
3325 case WXK_UP:
3326 case WXK_DOWN:
3327 m_pany = 0;
3328 if (!m_panx) m_panspeed = 0;
3329 break;
3330
3331 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3332 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3333 case WXK_PAGEUP:
3334 case WXK_PAGEDOWN:
3335 if (m_mustmove) DoMovement(m_mustmove);
3336
3337 m_zoom_factor = 1;
3338 break;
3339
3340 case WXK_ALT:
3341 m_modkeys &= ~wxMOD_ALT;
3342#ifdef OCPN_ALT_MENUBAR
3343#ifndef __WXOSX__
3344 // If the permanent menu bar is disabled, and we are not in the middle of
3345 // another key combo, then show the menu bar temporarily when Alt is
3346 // released (or hide it if already visible).
3347 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3348 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3349 top_frame::Get()->ApplyGlobalSettings(false);
3350 }
3351 m_bMayToggleMenuBar = true;
3352#endif
3353#endif
3354 break;
3355
3356 case WXK_CONTROL:
3357 m_modkeys &= ~wxMOD_CONTROL;
3358 break;
3359 }
3360
3361 if (event.GetKeyCode() < 128) // ascii
3362 {
3363 int key_char = event.GetKeyCode();
3364
3365 // Handle both QWERTY and AZERTY keyboard separately for a few control
3366 // codes
3367 if (!g_b_assume_azerty) {
3368 switch (key_char) {
3369 case '+':
3370 case '=':
3371 case '-':
3372 case '_':
3373 case 54:
3374 case 56: // '_' alpha/num pad
3375 DoMovement(m_mustmove);
3376
3377 // m_zoom_factor = 1;
3378 break;
3379 case '[':
3380 case ']':
3381 DoMovement(m_mustmove);
3382 m_rotation_speed = 0;
3383 break;
3384 }
3385 } else {
3386 switch (key_char) {
3387 case 43:
3388 case 54: // '-' alpha/num pad
3389 case 56: // '_' alpha/num pad
3390 DoMovement(m_mustmove);
3391
3392 m_zoom_factor = 1;
3393 break;
3394 }
3395 }
3396 }
3397 event.Skip();
3398}
3399
3400void ChartCanvas::ToggleChartOutlines() {
3401 m_bShowOutlines = !m_bShowOutlines;
3402
3403 Refresh(false);
3404
3405#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3406 // needs a full refresh
3407 if (g_bopengl) InvalidateGL();
3408#endif
3409}
3410
3411void ChartCanvas::ToggleLookahead() {
3412 m_bLookAhead = !m_bLookAhead;
3413 m_OSoffsetx = 0; // center ownship
3414 m_OSoffsety = 0;
3415}
3416
3417void ChartCanvas::SetUpMode(int mode) {
3418 m_upMode = mode;
3419
3420 if (mode != NORTH_UP_MODE) {
3421 // Stuff the COGAvg table in case COGUp is selected
3422 double stuff = 0;
3423 if (!std::isnan(gCog)) stuff = gCog;
3424
3425 if (g_COGAvgSec > 0) {
3426 auto cog_table = top_frame::Get()->GetCOGTable();
3427 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3428 }
3429 g_COGAvg = stuff;
3430 top_frame::Get()->StartCogTimer();
3431 } else {
3432 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3433 SetVPRotation(GetVPSkew());
3434 else
3435 SetVPRotation(0); /* reset to north up */
3436 }
3437
3438 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3439 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3440
3441 UpdateGPSCompassStatusBox(true);
3442 top_frame::Get()->DoChartUpdate();
3443}
3444
3445bool ChartCanvas::DoCanvasCOGSet() {
3446 if (GetUpMode() == NORTH_UP_MODE) return false;
3447 double cog_use = g_COGAvg;
3448 if (g_btenhertz) cog_use = gCog;
3449
3450 double rotation = 0;
3451 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3452 rotation = -gHdt * PI / 180.;
3453 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3454 rotation = -cog_use * PI / 180.;
3455
3456 SetVPRotation(rotation);
3457 return true;
3458}
3459
3460double easeOutCubic(double t) {
3461 // Starts quickly and slows down toward the end
3462 return 1.0 - pow(1.0 - t, 3.0);
3463}
3464
3465void ChartCanvas::StartChartDragInertia() {
3466 m_bChartDragging = false;
3467
3468 // Set some parameters
3469 m_chart_drag_inertia_time = 750; // msec
3470 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3471 m_last_elapsed = 0;
3472
3473 // Calculate ending drag velocity
3474 size_t n_vel = 10;
3475 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3476 int xacc = 0;
3477 int yacc = 0;
3478 double tacc = 0;
3479 size_t length = m_drag_vec_t.size();
3480 for (size_t i = 0; i < n_vel; i++) {
3481 xacc += m_drag_vec_x.at(length - 1 - i);
3482 yacc += m_drag_vec_y.at(length - 1 - i);
3483 tacc += m_drag_vec_t.at(length - 1 - i);
3484 }
3485
3486 if (tacc == 0) return;
3487
3488 double drag_velocity_x = xacc / tacc;
3489 double drag_velocity_y = yacc / tacc;
3490 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3491 // drag_velocity_y);
3492
3493 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3494 // touch tap.
3495 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3496
3497 m_chart_drag_velocity_x = drag_velocity_x;
3498 m_chart_drag_velocity_y = drag_velocity_y;
3499
3500 m_chart_drag_inertia_active = true;
3501 // First callback as fast as possible.
3502 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3503}
3504
3505void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3506 if (!m_chart_drag_inertia_active) return;
3507 // Calculate time fraction from 0..1
3508 wxLongLong now = wxGetLocalTimeMillis();
3509 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3510 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3511 if (t > 1.0) t = 1.0;
3512 double e = 1.0 - easeOutCubic(t); // 0..1
3513
3514 double dx =
3515 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3516 double dy =
3517 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3518
3519 m_last_elapsed = elapsed;
3520
3521 // Ensure that target destination lies on whole-pixel boundary
3522 // This allows the render engine to use a faster FBO copy method for drawing
3523 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3524 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3525 double inertia_lat, inertia_lon;
3526 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3527 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3528 // Check if ownship has moved off-screen
3529 if (!IsOwnshipOnScreen()) {
3530 m_bFollow = false; // update the follow flag
3531 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3532 UpdateFollowButtonState();
3533 m_OSoffsetx = 0;
3534 m_OSoffsety = 0;
3535 } else {
3536 m_OSoffsetx += dx;
3537 m_OSoffsety -= dy;
3538 }
3539
3540 Refresh(false);
3541
3542 // Stop condition
3543 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3544 m_chart_drag_inertia_timer.Stop();
3545
3546 // Disable chart pan movement logic
3547 m_target_lat = GetVP().clat;
3548 m_target_lon = GetVP().clon;
3549 m_pan_drag.x = m_pan_drag.y = 0;
3550 m_panx = m_pany = 0;
3551 m_chart_drag_inertia_active = false;
3552 DoCanvasUpdate();
3553
3554 } else {
3555 int target_redraw_interval = 40; // msec
3556 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3557 }
3558}
3559
3560void ChartCanvas::StopMovement() {
3561 m_panx = m_pany = 0;
3562 m_panspeed = 0;
3563 m_zoom_factor = 1;
3564 m_rotation_speed = 0;
3565 m_mustmove = 0;
3566#if 0
3567#if !defined(__WXGTK__) && !defined(__WXQT__)
3568 SetFocus();
3569 top_frame::Get()->Raise();
3570#endif
3571#endif
3572}
3573
3574/* instead of integrating in timer callbacks
3575 (which do not always get called fast enough)
3576 we can perform the integration of movement
3577 at each render frame based on the time change */
3578bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3579 // Start/restart the stop movement timer
3580 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3581
3582 if (!pMovementTimer->IsRunning()) {
3583 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3584 }
3585
3586 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3587 // already moving, gets called again because of key-repeat event
3588 return false;
3589 }
3590
3591 m_last_movement_time = wxDateTime::UNow();
3592
3593 return true;
3594}
3595void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3596 int nstep) {
3597 // Save the target
3598 m_target_lat = target_lat;
3599 m_target_lon = target_lon;
3600
3601 // Save the start point
3602 m_start_lat = GetVP().clat;
3603 m_start_lon = GetVP().clon;
3604
3605 m_VPMovementTimer.Start(1, true); // oneshot
3606 m_timed_move_vp_active = true;
3607 m_stvpc = 0;
3608 m_timedVP_step = nstep;
3609}
3610
3611void ChartCanvas::DoTimedMovementVP() {
3612 if (!m_timed_move_vp_active) return; // not active
3613 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3614 StopMovement();
3615 return;
3616 }
3617 // Stop condition
3618 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3619 double d2 =
3620 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3621 d2 = pow(d2, 0.5);
3622
3623 if (d2 < one_pix) {
3624 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3625 StopMovementVP();
3626 return;
3627 }
3628
3629 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3630 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3631 // StopMovementVP();
3632 // return;
3633 // }
3634
3635 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3636 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3637
3638 m_run_lat = new_lat;
3639 m_run_lon = new_lon;
3640
3641 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3642}
3643
3644void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3645
3646void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3647
3648void ChartCanvas::StartTimedMovementTarget() {}
3649
3650void ChartCanvas::DoTimedMovementTarget() {}
3651
3652void ChartCanvas::StopMovementTarget() {}
3653int ntm;
3654
3655void ChartCanvas::DoTimedMovement() {
3656 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3657 !m_rotation_speed)
3658 return; /* not moving */
3659
3660 wxDateTime now = wxDateTime::UNow();
3661 long dt = 0;
3662 if (m_last_movement_time.IsValid())
3663 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3664
3665 m_last_movement_time = now;
3666
3667 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3668 dt = 500;
3669
3670 DoMovement(dt);
3671}
3672
3674 /* if we get here quickly assume 1ms so that some movement occurs */
3675 if (dt == 0) dt = 1;
3676
3677 m_mustmove -= dt;
3678 if (m_mustmove < 0) m_mustmove = 0;
3679
3680 if (!m_inPinch) { // this stops compound zoom/pan
3681 if (m_pan_drag.x || m_pan_drag.y) {
3682 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3683 m_pan_drag.x = m_pan_drag.y = 0;
3684 }
3685
3686 if (m_panx || m_pany) {
3687 const double slowpan = .1, maxpan = 2;
3688 if (m_modkeys == wxMOD_ALT)
3689 m_panspeed = slowpan;
3690 else {
3691 m_panspeed += (double)dt / 500; /* apply acceleration */
3692 m_panspeed = wxMin(maxpan, m_panspeed);
3693 }
3694 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3695 }
3696 }
3697 if (m_zoom_factor != 1) {
3698 double alpha = 400, beta = 1.5;
3699 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3700
3701 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3702
3703 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3704
3705 // Try to hit the zoom target exactly.
3706 // if(m_wheelzoom_stop_oneshot > 0)
3707 {
3708 if (zoom_factor > 1) {
3709 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3710 zoom_factor = VPoint.chart_scale / m_zoom_target;
3711 }
3712
3713 else if (zoom_factor < 1) {
3714 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3715 zoom_factor = VPoint.chart_scale / m_zoom_target;
3716 }
3717 }
3718
3719 if (fabs(zoom_factor - 1) > 1e-4) {
3720 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3721 } else {
3722 StopMovement();
3723 }
3724
3725 if (m_wheelzoom_stop_oneshot > 0) {
3726 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3727 m_wheelzoom_stop_oneshot = 0;
3728 StopMovement();
3729 }
3730
3731 // Don't overshoot the zoom target.
3732 if (zoom_factor > 1) {
3733 if (VPoint.chart_scale <= m_zoom_target) {
3734 m_wheelzoom_stop_oneshot = 0;
3735 StopMovement();
3736 }
3737 } else if (zoom_factor < 1) {
3738 if (VPoint.chart_scale >= m_zoom_target) {
3739 m_wheelzoom_stop_oneshot = 0;
3740 StopMovement();
3741 }
3742 }
3743 }
3744 }
3745
3746 if (m_rotation_speed) { /* in degrees per second */
3747 double speed = m_rotation_speed;
3748 if (m_modkeys == wxMOD_ALT) speed /= 10;
3749 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3750 }
3751}
3752
3753void ChartCanvas::SetColorScheme(ColorScheme cs) {
3754 SetAlertString("");
3755
3756 // Setup ownship image pointers
3757 switch (cs) {
3758 case GLOBAL_COLOR_SCHEME_DAY:
3759 m_pos_image_red = &m_os_image_red_day;
3760 m_pos_image_grey = &m_os_image_grey_day;
3761 m_pos_image_yellow = &m_os_image_yellow_day;
3762 m_pos_image_user = m_pos_image_user_day;
3763 m_pos_image_user_grey = m_pos_image_user_grey_day;
3764 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3765 m_cTideBitmap = m_bmTideDay;
3766 m_cCurrentBitmap = m_bmCurrentDay;
3767
3768 break;
3769 case GLOBAL_COLOR_SCHEME_DUSK:
3770 m_pos_image_red = &m_os_image_red_dusk;
3771 m_pos_image_grey = &m_os_image_grey_dusk;
3772 m_pos_image_yellow = &m_os_image_yellow_dusk;
3773 m_pos_image_user = m_pos_image_user_dusk;
3774 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3775 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3776 m_cTideBitmap = m_bmTideDusk;
3777 m_cCurrentBitmap = m_bmCurrentDusk;
3778 break;
3779 case GLOBAL_COLOR_SCHEME_NIGHT:
3780 m_pos_image_red = &m_os_image_red_night;
3781 m_pos_image_grey = &m_os_image_grey_night;
3782 m_pos_image_yellow = &m_os_image_yellow_night;
3783 m_pos_image_user = m_pos_image_user_night;
3784 m_pos_image_user_grey = m_pos_image_user_grey_night;
3785 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3786 m_cTideBitmap = m_bmTideNight;
3787 m_cCurrentBitmap = m_bmCurrentNight;
3788 break;
3789 default:
3790 m_pos_image_red = &m_os_image_red_day;
3791 m_pos_image_grey = &m_os_image_grey_day;
3792 m_pos_image_yellow = &m_os_image_yellow_day;
3793 m_pos_image_user = m_pos_image_user_day;
3794 m_pos_image_user_grey = m_pos_image_user_grey_day;
3795 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3796 m_cTideBitmap = m_bmTideDay;
3797 m_cCurrentBitmap = m_bmCurrentDay;
3798 break;
3799 }
3800
3801 CreateDepthUnitEmbossMaps(cs);
3802 CreateOZEmbossMapData(cs);
3803
3804 // Set up fog effect base color
3805 m_fog_color = wxColor(
3806 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3807 float dim = 1.0;
3808 switch (cs) {
3809 case GLOBAL_COLOR_SCHEME_DUSK:
3810 dim = 0.5;
3811 break;
3812 case GLOBAL_COLOR_SCHEME_NIGHT:
3813 dim = 0.25;
3814 break;
3815 default:
3816 break;
3817 }
3818 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3819 m_fog_color.Blue() * dim);
3820
3821 // Really dark
3822#if 0
3823 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3824 SetBackgroundColour( wxColour(0,0,0) );
3825
3826 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3827 }
3828 else{
3829 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3830#ifndef __WXMAC__
3831 SetBackgroundColour( wxNullColour );
3832#endif
3833 }
3834#endif
3835
3836 // UpdateToolbarColorScheme(cs);
3837
3838 m_Piano->SetColorScheme(cs);
3839
3840 m_Compass->SetColorScheme(cs);
3841
3842 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3843
3844 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3845
3846 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3847 if (m_notification_button) {
3848 m_notification_button->SetColorScheme(cs);
3849 }
3850
3851#ifdef ocpnUSE_GL
3852 if (g_bopengl && m_glcc) {
3853 m_glcc->SetColorScheme(cs);
3854 g_glTextureManager->ClearAllRasterTextures();
3855 // m_glcc->FlushFBO();
3856 }
3857#endif
3858 SetbTCUpdate(true); // force re-render of tide/current locators
3859 m_brepaint_piano = true;
3860
3861 ReloadVP();
3862
3863 m_cs = cs;
3864}
3865
3866wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3867 wxImage img = Bitmap.ConvertToImage();
3868 int sx = img.GetWidth();
3869 int sy = img.GetHeight();
3870
3871 wxImage new_img(img);
3872
3873 for (int i = 0; i < sx; i++) {
3874 for (int j = 0; j < sy; j++) {
3875 if (!img.IsTransparent(i, j)) {
3876 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3877 (unsigned char)(img.GetGreen(i, j) * factor),
3878 (unsigned char)(img.GetBlue(i, j) * factor));
3879 }
3880 }
3881 }
3882
3883 wxBitmap ret = wxBitmap(new_img);
3884
3885 return ret;
3886}
3887
3888void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3889 int max) {
3890 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3891 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3892
3893 if (!m_pBrightPopup) {
3894 // Calculate size
3895 int x, y;
3896 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3897
3898 m_pBrightPopup = new TimedPopupWin(this, 3);
3899
3900 m_pBrightPopup->SetSize(x, y);
3901 m_pBrightPopup->Move(120, 120);
3902 }
3903
3904 int bmpsx = m_pBrightPopup->GetSize().x;
3905 int bmpsy = m_pBrightPopup->GetSize().y;
3906
3907 wxBitmap bmp(bmpsx, bmpsx);
3908 wxMemoryDC mdc(bmp);
3909
3910 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3911 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3912 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3913 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3914 mdc.Clear();
3915
3916 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3917
3918 mdc.SetFont(*pfont);
3919 wxString val;
3920
3921 if (brightness == max)
3922 val = "MAX";
3923 else if (brightness == min)
3924 val = "MIN";
3925 else
3926 val.Printf("%3d", brightness);
3927
3928 mdc.DrawText(val, 0, 0);
3929
3930 mdc.SelectObject(wxNullBitmap);
3931
3932 m_pBrightPopup->SetBitmap(bmp);
3933 m_pBrightPopup->Show();
3934 m_pBrightPopup->Refresh();
3935}
3936
3937void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3938 m_b_rot_hidef = true;
3939 ReloadVP();
3940}
3941
3942void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3943 if (!g_bRollover) return;
3944
3945 bool b_need_refresh = false;
3946
3947 wxSize win_size = GetSize() * m_displayScale;
3948 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3949
3950 // Handle the AIS Rollover Window first
3951 bool showAISRollover = false;
3952 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3953 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3954 SelectItem *pFind = pSelectAIS->FindSelection(
3955 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3956 if (pFind) {
3957 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3958 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3959
3960 if (ptarget) {
3961 showAISRollover = true;
3962
3963 if (NULL == m_pAISRolloverWin) {
3964 m_pAISRolloverWin = new RolloverWin(this);
3965 m_pAISRolloverWin->IsActive(false);
3966 b_need_refresh = true;
3967 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3968 m_AISRollover_MMSI != FoundAIS_MMSI) {
3969 // Sometimes the mouse moves fast enough to get over a new AIS
3970 // target before the one-shot has fired to remove the old target.
3971 // Result: wrong target data is shown.
3972 // Detect this case,close the existing rollover ASAP, and restart
3973 // the timer.
3974 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3975 m_pAISRolloverWin->IsActive(false);
3976 m_AISRollover_MMSI = 0;
3977 Refresh();
3978 return;
3979 }
3980
3981 m_AISRollover_MMSI = FoundAIS_MMSI;
3982
3983 if (!m_pAISRolloverWin->IsActive()) {
3984 wxString s = ptarget->GetRolloverString();
3985 m_pAISRolloverWin->SetString(s);
3986
3987 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3988 AIS_ROLLOVER, win_size);
3989 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3990 m_pAISRolloverWin->IsActive(true);
3991 b_need_refresh = true;
3992 }
3993 }
3994 } else {
3995 m_AISRollover_MMSI = 0;
3996 showAISRollover = false;
3997 }
3998 }
3999
4000 // Maybe turn the rollover off
4001 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
4002 m_pAISRolloverWin->IsActive(false);
4003 m_AISRollover_MMSI = 0;
4004 b_need_refresh = true;
4005 }
4006
4007 // Now the Route info rollover
4008 // Show the route segment info
4009 bool showRouteRollover = false;
4010
4011 if (NULL == m_pRolloverRouteSeg) {
4012 // Get a list of all selectable sgements, and search for the first
4013 // visible segment as the rollover target.
4014
4015 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4016 SelectableItemList SelList = pSelect->FindSelectionList(
4017 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4018 auto node = SelList.begin();
4019 while (node != SelList.end()) {
4020 SelectItem *pFindSel = *node;
4021
4022 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4023
4024 if (pr && pr->IsVisible()) {
4025 m_pRolloverRouteSeg = pFindSel;
4026 showRouteRollover = true;
4027
4028 if (NULL == m_pRouteRolloverWin) {
4029 m_pRouteRolloverWin = new RolloverWin(this, 10);
4030 m_pRouteRolloverWin->IsActive(false);
4031 }
4032
4033 if (!m_pRouteRolloverWin->IsActive()) {
4034 wxString s;
4035 RoutePoint *segShow_point_a =
4036 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4037 RoutePoint *segShow_point_b =
4038 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4039
4040 double brg, dist;
4041 DistanceBearingMercator(
4042 segShow_point_b->m_lat, segShow_point_b->m_lon,
4043 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4044
4045 if (!pr->m_bIsInLayer)
4046 s.Append(_("Route") + ": ");
4047 else
4048 s.Append(_("Layer Route: "));
4049
4050 if (pr->m_RouteNameString.IsEmpty())
4051 s.Append(_("(unnamed)"));
4052 else
4053 s.Append(pr->m_RouteNameString);
4054
4055 s << "\n"
4056 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4057 << "\n"
4058 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4059 << segShow_point_b->GetName() << "\n";
4060
4061 if (g_bShowTrue)
4062 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4063 (int)floor(brg + 0.5), 0x00B0);
4064 if (g_bShowMag) {
4065 double latAverage =
4066 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4067 double lonAverage =
4068 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4069 double varBrg =
4070 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4071
4072 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4073 (int)floor(varBrg + 0.5), 0x00B0);
4074 }
4075
4076 s << FormatDistanceAdaptive(dist);
4077
4078 // Compute and display cumulative distance from route start point to
4079 // current leg end point and RNG,TTG,ETA from ship to current leg end
4080 // point for active route
4081 double shiptoEndLeg = 0.;
4082 bool validActive = false;
4083 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4084 validActive = true;
4085
4086 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4087 auto node = pr->pRoutePointList->begin();
4088 RoutePoint *prp;
4089 float dist_to_endleg = 0;
4090 wxString t;
4091
4092 for (++node; node != pr->pRoutePointList->end(); ++node) {
4093 prp = *node;
4094 if (validActive)
4095 shiptoEndLeg += prp->m_seg_len;
4096 else if (prp->m_bIsActive)
4097 validActive = true;
4098 dist_to_endleg += prp->m_seg_len;
4099 if (prp->IsSame(segShow_point_a)) break;
4100 }
4101 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4102 }
4103 // write from ship to end selected leg point data if the route is
4104 // active
4105 if (validActive) {
4106 s << "\n"
4107 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4108 shiptoEndLeg +=
4110 ->GetCurrentRngToActivePoint(); // add distance from ship
4111 // to active point
4112 shiptoEndLeg +=
4113 segShow_point_b
4114 ->m_seg_len; // add the lenght of the selected leg
4115 s << FormatDistanceAdaptive(shiptoEndLeg);
4116 // ensure sog/cog are valid and vmg is positive to keep data
4117 // coherent
4118 double vmg = 0.;
4119 if (!std::isnan(gCog) && !std::isnan(gSog))
4120 vmg = gSog *
4121 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4122 PI / 180.);
4123 if (vmg > 0.) {
4124 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4125 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4126 s << " - "
4127 << wxString(ttg_sec > SECONDS_PER_DAY
4128 ? ttg_span.Format(_("%Dd %H:%M"))
4129 : ttg_span.Format(_("%H:%M")));
4130 wxDateTime dtnow, eta;
4131 eta = dtnow.SetToCurrent().Add(ttg_span);
4132 s << " - " << eta.Format("%b").Mid(0, 4)
4133 << eta.Format(" %d %H:%M");
4134 } else
4135 s << " ---- ----";
4136 }
4137 m_pRouteRolloverWin->SetString(s);
4138
4139 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4140 LEG_ROLLOVER, win_size);
4141 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4142 m_pRouteRolloverWin->IsActive(true);
4143 b_need_refresh = true;
4144 showRouteRollover = true;
4145 break;
4146 }
4147 } else {
4148 ++node;
4149 }
4150 }
4151 } else {
4152 // Is the cursor still in select radius, and not timed out?
4153 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4154 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4155 m_pRolloverRouteSeg))
4156 showRouteRollover = false;
4157 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4158 showRouteRollover = false;
4159 else
4160 showRouteRollover = true;
4161 }
4162
4163 // If currently creating a route, do not show this rollover window
4164 if (m_routeState) showRouteRollover = false;
4165
4166 // Similar for AIS target rollover window
4167 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4168 showRouteRollover = false;
4169
4170 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4171 !showRouteRollover) {
4172 m_pRouteRolloverWin->IsActive(false);
4173 m_pRolloverRouteSeg = NULL;
4174 m_pRouteRolloverWin->Destroy();
4175 m_pRouteRolloverWin = NULL;
4176 b_need_refresh = true;
4177 } else if (m_pRouteRolloverWin && showRouteRollover) {
4178 m_pRouteRolloverWin->IsActive(true);
4179 b_need_refresh = true;
4180 }
4181
4182 // Now the Track info rollover
4183 // Show the track segment info
4184 bool showTrackRollover = false;
4185
4186 if (NULL == m_pRolloverTrackSeg) {
4187 // Get a list of all selectable sgements, and search for the first
4188 // visible segment as the rollover target.
4189
4190 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4191 SelectableItemList SelList = pSelect->FindSelectionList(
4192 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4193
4194 auto node = SelList.begin();
4195 while (node != SelList.end()) {
4196 SelectItem *pFindSel = *node;
4197
4198 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4199
4200 if (pt && pt->IsVisible()) {
4201 m_pRolloverTrackSeg = pFindSel;
4202 showTrackRollover = true;
4203
4204 if (NULL == m_pTrackRolloverWin) {
4205 m_pTrackRolloverWin = new RolloverWin(this, 10);
4206 m_pTrackRolloverWin->IsActive(false);
4207 }
4208
4209 if (!m_pTrackRolloverWin->IsActive()) {
4210 wxString s;
4211 TrackPoint *segShow_point_a =
4212 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4213 TrackPoint *segShow_point_b =
4214 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4215
4216 double brg, dist;
4217 DistanceBearingMercator(
4218 segShow_point_b->m_lat, segShow_point_b->m_lon,
4219 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4220
4221 if (!pt->m_bIsInLayer)
4222 s.Append(_("Track") + ": ");
4223 else
4224 s.Append(_("Layer Track: "));
4225
4226 if (pt->GetName().IsEmpty())
4227 s.Append(_("(unnamed)"));
4228 else
4229 s.Append(pt->GetName());
4230 double tlenght = pt->Length();
4231 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4232 if (pt->GetLastPoint()->GetTimeString() &&
4233 pt->GetPoint(0)->GetTimeString()) {
4234 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4235 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4236 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4237 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4238 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4239 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4240 << getUsrSpeedUnit();
4241 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4242 : ttime.Format(" %H:%M"));
4243 }
4244 }
4245
4246 if (g_bShowTrackPointTime &&
4247 strlen(segShow_point_b->GetTimeString())) {
4248 wxString stamp = segShow_point_b->GetTimeString();
4249 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4250 if (timestamp.IsValid()) {
4251 // Format track rollover timestamp to OCPN global TZ setting
4254 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4255 }
4256 s << "\n" << _("Segment Created: ") << stamp;
4257 }
4258
4259 s << "\n";
4260 if (g_bShowTrue)
4261 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4262 0x00B0);
4263
4264 if (g_bShowMag) {
4265 double latAverage =
4266 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4267 double lonAverage =
4268 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4269 double varBrg =
4270 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4271
4272 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4273 0x00B0);
4274 }
4275
4276 s << FormatDistanceAdaptive(dist);
4277
4278 if (segShow_point_a->GetTimeString() &&
4279 segShow_point_b->GetTimeString()) {
4280 wxDateTime apoint = segShow_point_a->GetCreateTime();
4281 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4282 if (apoint.IsValid() && bpoint.IsValid()) {
4283 double segmentSpeed = toUsrSpeed(
4284 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4285 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4286 << getUsrSpeedUnit();
4287 }
4288 }
4289
4290 m_pTrackRolloverWin->SetString(s);
4291
4292 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4293 LEG_ROLLOVER, win_size);
4294 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4295 m_pTrackRolloverWin->IsActive(true);
4296 b_need_refresh = true;
4297 showTrackRollover = true;
4298 break;
4299 }
4300 } else {
4301 ++node;
4302 }
4303 }
4304 } else {
4305 // Is the cursor still in select radius, and not timed out?
4306 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4307 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4308 m_pRolloverTrackSeg))
4309 showTrackRollover = false;
4310 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4311 showTrackRollover = false;
4312 else
4313 showTrackRollover = true;
4314 }
4315
4316 // Similar for AIS target rollover window
4317 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4318 showTrackRollover = false;
4319
4320 // Similar for route rollover window
4321 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4322 showTrackRollover = false;
4323
4324 // TODO We onlt show tracks on primary canvas....
4325 // if(!IsPrimaryCanvas())
4326 // showTrackRollover = false;
4327
4328 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4329 !showTrackRollover) {
4330 m_pTrackRolloverWin->IsActive(false);
4331 m_pRolloverTrackSeg = NULL;
4332 m_pTrackRolloverWin->Destroy();
4333 m_pTrackRolloverWin = NULL;
4334 b_need_refresh = true;
4335 } else if (m_pTrackRolloverWin && showTrackRollover) {
4336 m_pTrackRolloverWin->IsActive(true);
4337 b_need_refresh = true;
4338 }
4339
4340 if (b_need_refresh) Refresh();
4341}
4342
4343void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4344 if ((GetShowENCLights() || m_bsectors_shown) &&
4345 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4346 extendedSectorLegs)) {
4347 if (!m_bsectors_shown) {
4348 ReloadVP(false);
4349 m_bsectors_shown = true;
4350 }
4351 } else {
4352 if (m_bsectors_shown) {
4353 ReloadVP(false);
4354 m_bsectors_shown = false;
4355 }
4356 }
4357
4358// This is here because GTK status window update is expensive..
4359// cairo using pango rebuilds the font every time so is very
4360// inefficient
4361// Anyway, only update the status bar when this timer expires
4362#if defined(__WXGTK__) || defined(__WXQT__)
4363 {
4364 // Check the absolute range of the cursor position
4365 // There could be a window wherein the chart geoereferencing is not
4366 // valid....
4367 double cursor_lat, cursor_lon;
4368 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4369
4370 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4371 while (cursor_lon < -180.) cursor_lon += 360.;
4372
4373 while (cursor_lon > 180.) cursor_lon -= 360.;
4374
4375 SetCursorStatus(cursor_lat, cursor_lon);
4376 }
4377 }
4378#endif
4379}
4380
4381void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4382 if (!top_frame::Get()->GetFrameStatusBar()) return;
4383
4384 wxString s1;
4385 s1 += " ";
4386 s1 += toSDMM(1, cursor_lat);
4387 s1 += " ";
4388 s1 += toSDMM(2, cursor_lon);
4389
4390 if (STAT_FIELD_CURSOR_LL >= 0)
4391 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4392
4393 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4394
4395 double brg, dist;
4396 wxString sm;
4397 wxString st;
4398 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4399 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4400 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4401
4402 wxString s = st + sm;
4403 s << FormatDistanceAdaptive(dist);
4404
4405 // CUSTOMIZATION - LIVE ETA OPTION
4406 // -------------------------------------------------------
4407 // Calculate an "live" ETA based on route starting from the current
4408 // position of the boat and goes to the cursor of the mouse.
4409 // In any case, an standard ETA will be calculated with a default speed
4410 // of the boat to give an estimation of the route (in particular if GPS
4411 // is off).
4412
4413 // Display only if option "live ETA" is selected in Settings > Display >
4414 // General.
4415 if (g_bShowLiveETA) {
4416 float realTimeETA;
4417 float boatSpeed;
4418 float boatSpeedDefault = g_defaultBoatSpeed;
4419
4420 // Calculate Estimate Time to Arrival (ETA) in minutes
4421 // Check before is value not closed to zero (it will make an very big
4422 // number...)
4423 if (!std::isnan(gSog)) {
4424 boatSpeed = gSog;
4425 if (boatSpeed < 0.5) {
4426 realTimeETA = 0;
4427 } else {
4428 realTimeETA = dist / boatSpeed * 60;
4429 }
4430 } else {
4431 realTimeETA = 0;
4432 }
4433
4434 // Add space after distance display
4435 s << " ";
4436 // Display ETA
4437 s << minutesToHoursDays(realTimeETA);
4438
4439 // In any case, display also an ETA with default speed at 6knts
4440
4441 s << " [@";
4442 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4443 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4444 s << " ";
4445 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4446 s << "]";
4447 }
4448 // END OF - LIVE ETA OPTION
4449
4450 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4451}
4452
4453// CUSTOMIZATION - FORMAT MINUTES
4454// -------------------------------------------------------
4455// New function to format minutes into a more readable format:
4456// * Hours + minutes, or
4457// * Days + hours.
4458wxString minutesToHoursDays(float timeInMinutes) {
4459 wxString s;
4460
4461 if (timeInMinutes == 0) {
4462 s << "--min";
4463 }
4464
4465 // Less than 60min, keep time in minutes
4466 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4467 s << wxString::Format("%d", (int)timeInMinutes);
4468 s << "min";
4469 }
4470
4471 // Between 1h and less than 24h, display time in hours, minutes
4472 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4473 int hours;
4474 int min;
4475 hours = (int)timeInMinutes / 60;
4476 min = (int)timeInMinutes % 60;
4477
4478 if (min == 0) {
4479 s << wxString::Format("%d", hours);
4480 s << "h";
4481 } else {
4482 s << wxString::Format("%d", hours);
4483 s << "h";
4484 s << wxString::Format("%d", min);
4485 s << "min";
4486 }
4487
4488 }
4489
4490 // More than 24h, display time in days, hours
4491 else if (timeInMinutes > 24 * 60) {
4492 int days;
4493 int hours;
4494 days = (int)(timeInMinutes / 60) / 24;
4495 hours = (int)(timeInMinutes / 60) % 24;
4496
4497 if (hours == 0) {
4498 s << wxString::Format("%d", days);
4499 s << "d";
4500 } else {
4501 s << wxString::Format("%d", days);
4502 s << "d";
4503 s << wxString::Format("%d", hours);
4504 s << "h";
4505 }
4506 }
4507
4508 return s;
4509}
4510
4511// END OF CUSTOMIZATION - FORMAT MINUTES
4512// Thanks open source code ;-)
4513// -------------------------------------------------------
4514
4515void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4516 double clat, clon;
4517 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4518 *lat = clat;
4519 *lon = clon;
4520}
4521
4522void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4523 wxPoint2DDouble *r) {
4524 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4525}
4526
4528 double rlon, wxPoint2DDouble *r) {
4529 // If the Current Chart is a raster chart, and the
4530 // requested lat/long is within the boundaries of the chart,
4531 // and the VP is not rotated,
4532 // then use the embedded BSB chart georeferencing algorithm
4533 // for greater accuracy
4534 // Additionally, use chart embedded georef if the projection is TMERC
4535 // i.e. NOT MERCATOR and NOT POLYCONIC
4536
4537 // If for some reason the chart rejects the request by returning an error,
4538 // then fall back to Viewport Projection estimate from canvas parameters
4539 if (!g_bopengl && m_singleChart &&
4540 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4541 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4542 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4543 (m_singleChart->GetChartProjectionType() !=
4544 PROJECTION_TRANSVERSE_MERCATOR) &&
4545 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4546 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4547 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4548 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4549 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4550 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4551 // Cur_BSB_Ch->GetCOVRTablenPoints
4552 // ( 0 ), rlon,
4553 // rlat );
4554 // bInside = true;
4555 // if ( bInside )
4556 if (Cur_BSB_Ch) {
4557 // This is a Raster chart....
4558 // If the VP is changing, the raster chart parameters may not yet be
4559 // setup So do that before accessing the chart's embedded
4560 // georeferencing
4561 Cur_BSB_Ch->SetVPRasterParms(vp);
4562 double rpixxd, rpixyd;
4563 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4564 r->m_x = rpixxd;
4565 r->m_y = rpixyd;
4566 return;
4567 }
4568 }
4569 }
4570
4571 // if needed, use the VPoint scaling estimator,
4572 *r = vp.GetDoublePixFromLL(rlat, rlon);
4573}
4574
4575// This routine might be deleted and all of the rendering improved
4576// to have floating point accuracy
4577bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4578 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4579}
4580
4581bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4582 wxPoint *r) {
4583 wxPoint2DDouble p;
4584 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4585
4586 // some projections give nan values when invisible values (other side of
4587 // world) are requested we should stop using integer coordinates or return
4588 // false here (and test it everywhere)
4589 if (std::isnan(p.m_x)) {
4590 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4591 return false;
4592 }
4593
4594 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4595 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4596 else
4597 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4598
4599 return true;
4600}
4601
4602void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4603 double &lon) {
4604 // If the Current Chart is a raster chart, and the
4605 // requested x,y is within the boundaries of the chart,
4606 // and the VP is not rotated,
4607 // then use the embedded BSB chart georeferencing algorithm
4608 // for greater accuracy
4609 // Additionally, use chart embedded georef if the projection is TMERC
4610 // i.e. NOT MERCATOR and NOT POLYCONIC
4611
4612 // If for some reason the chart rejects the request by returning an error,
4613 // then fall back to Viewport Projection estimate from canvas parameters
4614 bool bUseVP = true;
4615
4616 if (!g_bopengl && m_singleChart &&
4617 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4618 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4619 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4620 (m_singleChart->GetChartProjectionType() !=
4621 PROJECTION_TRANSVERSE_MERCATOR) &&
4622 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4623 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4624 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4625 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4626
4627 // TODO maybe need iterative process to validate bInside
4628 // first pass is mercator, then check chart boundaries
4629
4630 if (Cur_BSB_Ch) {
4631 // This is a Raster chart....
4632 // If the VP is changing, the raster chart parameters may not yet be
4633 // setup So do that before accessing the chart's embedded
4634 // georeferencing
4635 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4636
4637 double slat, slon;
4638 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4639 lat = slat;
4640
4641 if (slon < -180.)
4642 slon += 360.;
4643 else if (slon > 180.)
4644 slon -= 360.;
4645
4646 lon = slon;
4647 bUseVP = false;
4648 }
4649 }
4650 }
4651
4652 // if needed, use the VPoint scaling estimator
4653 if (bUseVP) {
4654 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4655 }
4656}
4657
4659 StopMovement();
4660 DoZoomCanvas(factor, false);
4661 extendedSectorLegs.clear();
4662}
4663
4664void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4665 bool stoptimer) {
4666 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4667
4668 if (g_bsmoothpanzoom) {
4669 if (StartTimedMovement(stoptimer)) {
4670 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4671 m_zoom_factor = factor;
4672 }
4673
4674 m_zoom_target = VPoint.chart_scale / factor;
4675 } else {
4676 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4677
4678 DoZoomCanvas(factor, can_zoom_to_cursor);
4679 }
4680
4681 extendedSectorLegs.clear();
4682}
4683
4684void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4685 // possible on startup
4686 if (!ChartData) return;
4687 if (!m_pCurrentStack) return;
4688
4689 /* TODO: queue the quilted loading code to a background thread
4690 so yield is never called from here, and also rendering is not delayed */
4691
4692 // Cannot allow Yield() re-entrancy here
4693 if (m_bzooming) return;
4694 m_bzooming = true;
4695
4696 double old_ppm = GetVP().view_scale_ppm;
4697
4698 // Capture current cursor position for zoom to cursor
4699 double zlat = m_cursor_lat;
4700 double zlon = m_cursor_lon;
4701
4702 double proposed_scale_onscreen =
4703 GetVP().chart_scale /
4704 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4705 bool b_do_zoom = false;
4706
4707 if (factor > 1) {
4708 b_do_zoom = true;
4709
4710 // double zoom_factor = factor;
4711
4712 ChartBase *pc = NULL;
4713
4714 if (!VPoint.b_quilt) {
4715 pc = m_singleChart;
4716 } else {
4717 if (!m_disable_adjust_on_zoom) {
4718 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4719 if (new_db_index >= 0)
4720 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4721 else { // for whatever reason, no reference chart is known
4722 // Choose the smallest scale chart on the current stack
4723 // and then adjust for scale range
4724 int current_ref_stack_index = -1;
4725 if (m_pCurrentStack->nEntry) {
4726 int trial_index =
4727 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4728 m_pQuilt->SetReferenceChart(trial_index);
4729 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4730 if (new_db_index >= 0)
4731 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4732 }
4733 }
4734
4735 if (m_pCurrentStack)
4736 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4737 new_db_index); // highlite the correct bar entry
4738 }
4739 }
4740
4741 if (pc) {
4742 // double target_scale_ppm = GetVPScale() * zoom_factor;
4743 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4744 // target_scale_ppm;
4745
4746 // Query the chart to determine the appropriate zoom range
4747 double min_allowed_scale =
4748 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4749
4750 if (proposed_scale_onscreen < min_allowed_scale) {
4751 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4752 m_zoom_factor = 1; /* stop zooming */
4753 b_do_zoom = false;
4754 } else
4755 proposed_scale_onscreen = min_allowed_scale;
4756 }
4757
4758 } else {
4759 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4760 }
4761
4762 } else if (factor < 1) {
4763 b_do_zoom = true;
4764
4765 ChartBase *pc = NULL;
4766
4767 bool b_smallest = false;
4768
4769 if (!VPoint.b_quilt) { // not quilted
4770 pc = m_singleChart;
4771
4772 if (pc) {
4773 // If m_singleChart is not on the screen, unbound the zoomout
4774 LLBBox viewbox = VPoint.GetBBox();
4775 // BoundingBox chart_box;
4776 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4777 double max_allowed_scale;
4778
4779 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4780
4781 // We can allow essentially unbounded zoomout in single chart mode
4782 // if( ChartData->GetDBBoundingBox( current_index,
4783 // &chart_box ) &&
4784 // !viewbox.IntersectOut( chart_box ) )
4785 // // Clamp the minimum scale zoom-out to the value
4786 // specified by the chart max_allowed_scale =
4787 // wxMin(max_allowed_scale, 4.0 *
4788 // pc->GetNormalScaleMax(
4789 // GetCanvasScaleFactor(),
4790 // GetCanvasWidth() ) );
4791 if (proposed_scale_onscreen > max_allowed_scale) {
4792 m_zoom_factor = 1; /* stop zooming */
4793 proposed_scale_onscreen = max_allowed_scale;
4794 }
4795 }
4796
4797 } else {
4798 if (!m_disable_adjust_on_zoom) {
4799 int new_db_index =
4800 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4801 if (new_db_index >= 0)
4802 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4803
4804 if (m_pCurrentStack)
4805 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4806 new_db_index); // highlite the correct bar entry
4807
4808 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4809
4810 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4811 proposed_scale_onscreen =
4812 wxMin(proposed_scale_onscreen,
4813 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4814 }
4815
4816 // set a minimum scale
4817 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4818 m_absolute_min_scale_ppm)
4819 proposed_scale_onscreen =
4820 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4821 }
4822 }
4823 double new_scale =
4824 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4825
4826 if (b_do_zoom) {
4827 // Disable ZTC if lookahead is ON, and currently b_follow is active
4828 bool b_allow_ztc = true;
4829 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4830 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4831 if (m_bLookAhead) {
4832 double brg, distance;
4833 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4834 &distance);
4835 dir_to_shift = brg;
4836 meters_to_shift = distance * 1852;
4837 }
4838 // Arrange to combine the zoom and pan into one operation for smoother
4839 // appearance
4840 SetVPScale(new_scale, false); // adjust, but deferred refresh
4841 wxPoint r;
4842 GetCanvasPointPix(zlat, zlon, &r);
4843 // this will emit the Refresh()
4844 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4845 } else {
4846 SetVPScale(new_scale);
4847 if (m_bFollow) DoCanvasUpdate();
4848 }
4849 }
4850
4851 m_bzooming = false;
4852}
4853
4854void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4855 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4856 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4857}
4858
4859int rot;
4860void ChartCanvas::RotateCanvas(double dir) {
4861 // SetUpMode(NORTH_UP_MODE);
4862
4863 if (g_bsmoothpanzoom) {
4864 if (StartTimedMovement()) {
4865 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4866 m_rotation_speed = dir * 60;
4867 }
4868 } else {
4869 double speed = dir * 10;
4870 if (m_modkeys == wxMOD_ALT) speed /= 20;
4871 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4872 }
4873}
4874
4875void ChartCanvas::DoRotateCanvas(double rotation) {
4876 while (rotation < 0) rotation += 2 * PI;
4877 while (rotation > 2 * PI) rotation -= 2 * PI;
4878
4879 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4880
4881 SetVPRotation(rotation);
4882 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4883}
4884
4885void ChartCanvas::DoTiltCanvas(double tilt) {
4886 while (tilt < 0) tilt = 0;
4887 while (tilt > .95) tilt = .95;
4888
4889 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4890
4891 VPoint.tilt = tilt;
4892 Refresh(false);
4893}
4894
4895void ChartCanvas::TogglebFollow() {
4896 if (!m_bFollow)
4897 SetbFollow();
4898 else
4899 ClearbFollow();
4900}
4901
4902void ChartCanvas::ClearbFollow() {
4903 m_bFollow = false; // update the follow flag
4904
4905 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4906
4907 UpdateFollowButtonState();
4908
4909 DoCanvasUpdate();
4910 ReloadVP();
4911 top_frame::Get()->SetChartUpdatePeriod();
4912}
4913
4914void ChartCanvas::SetbFollow() {
4915 // Is the OWNSHIP on-screen?
4916 // If not, then reset the OWNSHIP offset to 0 (center screen)
4917 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4918 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4919 m_OSoffsetx = 0;
4920 m_OSoffsety = 0;
4921 }
4922
4923 // Apply the present b_follow offset values to ship position
4924 wxPoint2DDouble p;
4926 p.m_x += m_OSoffsetx;
4927 p.m_y -= m_OSoffsety;
4928
4929 // compute the target center screen lat/lon
4930 double dlat, dlon;
4931 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4932
4933 JumpToPosition(dlat, dlon, GetVPScale());
4934 m_bFollow = true;
4935
4936 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4937 UpdateFollowButtonState();
4938
4939 if (!g_bSmoothRecenter) {
4940 DoCanvasUpdate();
4941 ReloadVP();
4942 }
4943 top_frame::Get()->SetChartUpdatePeriod();
4944}
4945
4946void ChartCanvas::UpdateFollowButtonState() {
4947 if (m_muiBar) {
4948 if (!m_bFollow)
4949 m_muiBar->SetFollowButtonState(0);
4950 else {
4951 if (m_bLookAhead)
4952 m_muiBar->SetFollowButtonState(2);
4953 else
4954 m_muiBar->SetFollowButtonState(1);
4955 }
4956 }
4957
4958#ifdef __ANDROID__
4959 if (!m_bFollow)
4960 androidSetFollowTool(0);
4961 else {
4962 if (m_bLookAhead)
4963 androidSetFollowTool(2);
4964 else
4965 androidSetFollowTool(1);
4966 }
4967#endif
4968
4969 // Look for plugin using API-121 or later
4970 // If found, make the follow state callback.
4971 if (g_pi_manager) {
4972 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4973 if (pic->m_enabled && pic->m_init_state) {
4974 switch (pic->m_api_version) {
4975 case 121: {
4976 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4977 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4978 break;
4979 }
4980 default:
4981 break;
4982 }
4983 }
4984 }
4985 }
4986}
4987
4988void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4989 if (g_bSmoothRecenter && !m_routeState) {
4990 if (StartSmoothJump(lat, lon, scale_ppm))
4991 return;
4992 else {
4993 // move closer to the target destination, and try again
4994 double gcDist, gcBearingEnd;
4995 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4996 &gcBearingEnd);
4997 gcBearingEnd += 180;
4998 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4999 GetCanvasWidth() / GetVPScale(); // meters
5000 double lon_offset =
5001 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
5002 double new_lat = lat + (lat_offset / (1852 * 60));
5003 double new_lon = lon + (lon_offset / (1852 * 60));
5004 SetViewPoint(new_lat, new_lon);
5005 ReloadVP();
5006 StartSmoothJump(lat, lon, scale_ppm);
5007 return;
5008 }
5009 }
5010
5011 if (lon > 180.0) lon -= 360.0;
5012 m_vLat = lat;
5013 m_vLon = lon;
5014 StopMovement();
5015 m_bFollow = false;
5016
5017 if (!GetQuiltMode()) {
5018 double skew = 0;
5019 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5020 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5021 } else {
5022 if (scale_ppm != GetVPScale()) {
5023 // XXX should be done in SetViewPoint
5024 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5025 AdjustQuiltRefChart();
5026 }
5027 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5028 }
5029
5030 ReloadVP();
5031
5032 UpdateFollowButtonState();
5033
5034 // TODO
5035 // if( g_pi_manager ) {
5036 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5037 // }
5038}
5039
5040bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5041 // Check distance to jump, in pixels at current chart scale
5042 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5043 // width.
5044 double gcDist;
5045 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5046 double distance_pixels = gcDist * GetVPScale();
5047 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5048 // Jump is too far, try again
5049 return false;
5050 }
5051
5052 // Save where we're coming from
5053 m_startLat = m_vLat;
5054 m_startLon = m_vLon;
5055 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5056
5057 // Save where we want to end up
5058 m_endLat = lat;
5059 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5060 m_endScale = scale_ppm;
5061
5062 // Setup timing
5063 m_animationDuration = 600; // ms
5064 m_animationStart = wxGetLocalTimeMillis();
5065
5066 // Stop any previous movement, ensure no conflicts
5067 StopMovement();
5068 m_bFollow = false;
5069
5070 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5071 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5072 m_animationActive = true;
5073
5074 return true;
5075}
5076
5077void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5078 // Calculate time fraction from 0..1
5079 wxLongLong now = wxGetLocalTimeMillis();
5080 double elapsed = (now - m_animationStart).ToDouble();
5081 double t = elapsed / m_animationDuration.ToDouble();
5082 if (t > 1.0) t = 1.0;
5083
5084 // Ease function for smoother movement
5085 double e = easeOutCubic(t);
5086
5087 // Interpolate lat/lon/scale
5088 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5089 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5090 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5091
5092 // Update viewpoint
5093 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5094 // portion)
5095 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5096 ReloadVP();
5097
5098 // If we reached the end, stop the timer and finalize
5099 if (t >= 1.0) {
5100 m_easeTimer.Stop();
5101 m_animationActive = false;
5102 UpdateFollowButtonState();
5103 ZoomCanvasSimple(1.0001);
5104 DoCanvasUpdate();
5105 ReloadVP();
5106 }
5107}
5108
5109bool ChartCanvas::PanCanvas(double dx, double dy) {
5110 if (!ChartData) return false;
5111 extendedSectorLegs.clear();
5112
5113 double dlat, dlon;
5114 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5115
5116 int iters = 0;
5117 for (;;) {
5118 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5119
5120 if (iters++ > 5) return false;
5121 if (!std::isnan(dlat)) break;
5122
5123 dx *= .5, dy *= .5;
5124 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5125 }
5126
5127 // avoid overshooting the poles
5128 if (dlat > 90)
5129 dlat = 90;
5130 else if (dlat < -90)
5131 dlat = -90;
5132
5133 if (dlon > 360.) dlon -= 360.;
5134 if (dlon < -360.) dlon += 360.;
5135
5136 // This should not really be necessary, but round-trip georef on some
5137 // charts is not perfect, So we can get creep on repeated unidimensional
5138 // pans, and corrupt chart cacheing.......
5139
5140 // But this only works on north-up projections
5141 // TODO: can we remove this now?
5142 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5143 // .001 ) ) {
5144 //
5145 // if( dx == 0 ) dlon = clon;
5146 // if( dy == 0 ) dlat = clat;
5147 // }
5148
5149 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5150
5151 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5152
5153 if (VPoint.b_quilt) {
5154 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5155 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5156 // Tweak the scale slightly for a new ref chart
5157 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5158 if (pc) {
5159 double tweak_scale_ppm =
5160 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5161 SetVPScale(tweak_scale_ppm);
5162 }
5163 }
5164
5165 if (new_ref_dbIndex == -1) {
5166#pragma GCC diagnostic push
5167#pragma GCC diagnostic ignored "-Warray-bounds"
5168 // The compiler sees a -1 index being used. Does not happen, though.
5169
5170 // for whatever reason, no reference chart is known
5171 // Probably panned out of the coverage region
5172 // If any charts are anywhere on-screen, choose the smallest
5173 // scale chart on the screen to be a new reference chart.
5174 int trial_index = -1;
5175 if (m_pCurrentStack->nEntry) {
5176 int trial_index =
5177 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5178 }
5179
5180 if (trial_index < 0) {
5181 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5182 if (full_screen_array.size())
5183 trial_index = full_screen_array[full_screen_array.size() - 1];
5184 }
5185
5186 if (trial_index >= 0) {
5187 m_pQuilt->SetReferenceChart(trial_index);
5188 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5189 VPoint.rotation);
5190 ReloadVP();
5191 }
5192#pragma GCC diagnostic pop
5193 }
5194 }
5195
5196 // Turn off bFollow only if the ownship has left the screen
5197 if (m_bFollow) {
5198 double offx, offy;
5199 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5200
5201 double offset_angle = atan2(offy, offx);
5202 double offset_distance = sqrt((offy * offy) + (offx * offx));
5203 double chart_angle = GetVPRotation();
5204 double target_angle = chart_angle - offset_angle;
5205 double d_east_mod = offset_distance * cos(target_angle);
5206 double d_north_mod = offset_distance * sin(target_angle);
5207
5208 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5209 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5210
5211 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5212 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5213 m_bFollow = false; // update the follow flag
5214 UpdateFollowButtonState();
5215 }
5216 }
5217
5218 Refresh(false);
5219
5220 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5221
5222 return true;
5223}
5224
5225bool ChartCanvas::IsOwnshipOnScreen() {
5226 wxPoint r;
5228 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5229 ((r.y > 0) && r.y < GetCanvasHeight()))
5230 return true;
5231 else
5232 return false;
5233}
5234
5235void ChartCanvas::ReloadVP(bool b_adjust) {
5236 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5237
5238 LoadVP(VPoint, b_adjust);
5239}
5240
5241void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5242#ifdef ocpnUSE_GL
5243 if (g_bopengl && m_glcc) {
5244 m_glcc->Invalidate();
5245 if (m_glcc->GetSize() != GetSize()) {
5246 m_glcc->SetSize(GetSize());
5247 }
5248 } else
5249#endif
5250 {
5251 m_cache_vp.Invalidate();
5252 m_bm_cache_vp.Invalidate();
5253 }
5254
5255 VPoint.Invalidate();
5256
5257 if (m_pQuilt) m_pQuilt->Invalidate();
5258
5259 // Make sure that the Selected Group is sensible...
5260 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5261 // m_groupIndex = 0;
5262 // if( !CheckGroup( m_groupIndex ) )
5263 // m_groupIndex = 0;
5264
5265 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5266 vp.m_projection_type, b_adjust);
5267}
5268
5269void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5270 m_pQuilt->SetReferenceChart(dbIndex);
5271 VPoint.Invalidate();
5272 m_pQuilt->Invalidate();
5273}
5274
5275double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5276 if (m_pQuilt)
5277 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5278 else
5279 return vp.view_scale_ppm;
5280}
5281
5282// Verify and adjust the current reference chart,
5283// so that it will not lead to excessive overzoom or underzoom onscreen
5284int ChartCanvas::AdjustQuiltRefChart() {
5285 int ret = -1;
5286 if (m_pQuilt) {
5287 wxASSERT(ChartData);
5288 ChartBase *pc =
5289 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5290 if (pc) {
5291 double min_ref_scale =
5292 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5293 double max_ref_scale =
5294 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5295
5296 if (VPoint.chart_scale < min_ref_scale) {
5297 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5298 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5299 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5300 } else {
5301 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5302
5303 if (!brender_ok) {
5304 int target_stack_index = wxNOT_FOUND;
5305 int il = 0;
5306 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5307 if (index == m_pQuilt->GetRefChartdbIndex()) {
5308 target_stack_index = il;
5309 break;
5310 }
5311 il++;
5312 }
5313 if (wxNOT_FOUND == target_stack_index) // should never happen...
5314 target_stack_index = 0;
5315
5316 int ref_family = pc->GetChartFamily();
5317 int extended_array_count =
5318 m_pQuilt->GetExtendedStackIndexArray().size();
5319 while ((!brender_ok) &&
5320 ((int)target_stack_index < (extended_array_count - 1))) {
5321 target_stack_index++;
5322 int test_db_index =
5323 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5324
5325 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5326 IsChartQuiltableRef(test_db_index)) {
5327 // open the target, and check the min_scale
5328 ChartBase *ptest_chart =
5329 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5330 if (ptest_chart) {
5331 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5332 }
5333 }
5334 }
5335
5336 if (brender_ok) { // found a better reference chart
5337 int new_db_index =
5338 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5339 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5340 IsChartQuiltableRef(new_db_index)) {
5341 m_pQuilt->SetReferenceChart(new_db_index);
5342 ret = new_db_index;
5343 } else
5344 ret = m_pQuilt->GetRefChartdbIndex();
5345 } else
5346 ret = m_pQuilt->GetRefChartdbIndex();
5347
5348 } else
5349 ret = m_pQuilt->GetRefChartdbIndex();
5350 }
5351 } else
5352 ret = -1;
5353 }
5354
5355 return ret;
5356}
5357
5358void ChartCanvas::UpdateCanvasOnGroupChange() {
5359 delete m_pCurrentStack;
5360 m_pCurrentStack = new ChartStack;
5361 wxASSERT(ChartData);
5362 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5363 m_groupIndex);
5364
5365 if (m_pQuilt) {
5366 m_pQuilt->Compose(VPoint);
5367 SetFocus();
5368 }
5369}
5370
5371bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5372 double latNE, double lonNE) {
5373 // Center Point
5374 double latc = (latSW + latNE) / 2.0;
5375 double lonc = (lonSW + lonNE) / 2.0;
5376
5377 // Get scale in ppm (latitude)
5378 double ne_easting, ne_northing;
5379 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5380
5381 double sw_easting, sw_northing;
5382 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5383
5384 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5385
5386 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5387}
5388
5389bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5390 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5391 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5392}
5393
5394bool ChartCanvas::SetVPProjection(int projection) {
5395 if (!g_bopengl) // alternative projections require opengl
5396 return false;
5397
5398 // the view scale varies depending on geographic location and projection
5399 // rescale to keep the relative scale on the screen the same
5400 double prev_true_scale_ppm = m_true_scale_ppm;
5401 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5402 VPoint.skew, VPoint.rotation, projection) &&
5403 SetVPScale(wxMax(
5404 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5405 m_absolute_min_scale_ppm));
5406}
5407
5408bool ChartCanvas::SetViewPoint(double lat, double lon) {
5409 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5410 VPoint.rotation);
5411}
5412
5413bool ChartCanvas::SetVPRotation(double angle) {
5414 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5415 VPoint.skew, angle);
5416}
5417bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5418 double skew, double rotation, int projection,
5419 bool b_adjust, bool b_refresh) {
5420 if (ChartData->IsBusy()) return false;
5421 bool b_ret = false;
5422 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5423 skew -= 2 * PI;
5424 // Any sensible change?
5425 if (VPoint.IsValid()) {
5426 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5427 (fabs(VPoint.skew - skew) < 1e-9) &&
5428 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5429 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5430 (VPoint.m_projection_type == projection ||
5431 projection == PROJECTION_UNKNOWN))
5432 return false;
5433 }
5434 if (VPoint.m_projection_type != projection)
5435 VPoint.InvalidateTransformCache(); // invalidate
5436
5437 // Take a local copy of the last viewport
5438 ViewPort last_vp = VPoint;
5439
5440 VPoint.skew = skew;
5441 VPoint.clat = lat;
5442 VPoint.clon = lon;
5443 VPoint.rotation = rotation;
5444 VPoint.view_scale_ppm = scale_ppm;
5445 if (projection != PROJECTION_UNKNOWN)
5446 VPoint.SetProjectionType(projection);
5447 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5448 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5449
5450 // don't allow latitude above 88 for mercator (90 is infinity)
5451 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5452 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5453 if (VPoint.clat > 89.5)
5454 VPoint.clat = 89.5;
5455 else if (VPoint.clat < -89.5)
5456 VPoint.clat = -89.5;
5457 }
5458
5459 // don't zoom out too far for transverse mercator polyconic until we resolve
5460 // issues
5461 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5462 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5463 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5464
5465 // SetVPRotation(rotation);
5466
5467 if (!g_bopengl) // tilt is not possible without opengl
5468 VPoint.tilt = 0;
5469
5470 if ((VPoint.pix_width <= 0) ||
5471 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5472 return false;
5473
5474 bool bwasValid = VPoint.IsValid();
5475 VPoint.Validate(); // Mark this ViewPoint as OK
5476
5477 // Has the Viewport scale changed? If so, invalidate the vp
5478 if (last_vp.view_scale_ppm != scale_ppm) {
5479 m_cache_vp.Invalidate();
5480 InvalidateGL();
5481 }
5482
5483 // A preliminary value, may be tweaked below
5484 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5485
5486 // recompute cursor position
5487 // and send to interested plugins if the mouse is actually in this window
5488 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5489 int mouseX = mouse_x;
5490 int mouseY = mouse_y;
5491 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5492 (mouseY < VPoint.pix_height)) {
5493 double lat_mouse, lon_mouse;
5494 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5495 m_cursor_lat = lat_mouse;
5496 m_cursor_lon = lon_mouse;
5497 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5498 }
5499 }
5500
5501 if (!VPoint.b_quilt && m_singleChart) {
5502 VPoint.SetBoxes();
5503
5504 // Allow the chart to adjust the new ViewPort for performance optimization
5505 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5506 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5507
5508 // If there is a sensible change in the chart render, refresh the whole
5509 // screen
5510 if ((!m_cache_vp.IsValid()) ||
5511 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5512 Refresh(false);
5513 b_ret = true;
5514 } else {
5515 wxPoint cp_last, cp_this;
5516 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5517 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5518
5519 if (cp_last != cp_this) {
5520 Refresh(false);
5521 b_ret = true;
5522 }
5523 }
5524 // Create the stack
5525 if (m_pCurrentStack) {
5526 assert(ChartData != 0);
5527 int current_db_index;
5528 current_db_index =
5529 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5530
5531 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5532 m_groupIndex);
5533 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5534 }
5535
5536 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5537 }
5538
5539 // Handle the quilted case
5540 if (VPoint.b_quilt) {
5541 VPoint.SetBoxes();
5542
5543 if (last_vp.view_scale_ppm != scale_ppm)
5544 m_pQuilt->InvalidateAllQuiltPatchs();
5545
5546 // Create the quilt
5547 if (ChartData /*&& ChartData->IsValid()*/) {
5548 if (!m_pCurrentStack) return false;
5549
5550 int current_db_index;
5551 current_db_index =
5552 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5553
5554 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5555 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5556
5557 // Check to see if the current quilt reference chart is in the new stack
5558 int current_ref_stack_index = -1;
5559 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5560 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5561 current_ref_stack_index = i;
5562 }
5563
5564 if (g_bFullScreenQuilt) {
5565 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5566 }
5567
5568 // We might need a new Reference Chart
5569 bool b_needNewRef = false;
5570
5571 // If the new stack does not contain the current ref chart....
5572 if ((-1 == current_ref_stack_index) &&
5573 (m_pQuilt->GetRefChartdbIndex() >= 0))
5574 b_needNewRef = true;
5575
5576 // Would the current Ref Chart be excessively underzoomed?
5577 // We need to check this here to be sure, since we cannot know where the
5578 // reference chart was assigned. For instance, the reference chart may
5579 // have been selected from the config file, or from a long jump with a
5580 // chart family switch implicit. Anyway, we check to be sure....
5581 bool renderable = true;
5582 ChartBase *referenceChart =
5583 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5584 if (referenceChart) {
5585 double chartMaxScale = referenceChart->GetNormalScaleMax(
5586 GetCanvasScaleFactor(), GetCanvasWidth());
5587 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5588 }
5589 if (!renderable) b_needNewRef = true;
5590
5591 // Need new refchart?
5592 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5593 const ChartTableEntry &cte_ref =
5594 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5595 int target_scale = cte_ref.GetScale();
5596 int target_type = cte_ref.GetChartType();
5597 int candidate_stack_index;
5598
5599 // reset the ref chart in a way that does not lead to excessive
5600 // underzoom, for performance reasons Try to find a chart that is the
5601 // same type, and has a scale of just smaller than the current ref
5602 // chart
5603
5604 candidate_stack_index = 0;
5605 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5606 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5607 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5608 int candidate_scale = cte_candidate.GetScale();
5609 int candidate_type = cte_candidate.GetChartType();
5610
5611 if ((candidate_scale >= target_scale) &&
5612 (candidate_type == target_type)) {
5613 bool renderable = true;
5614 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5615 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5616 if (tentative_referenceChart) {
5617 double chartMaxScale =
5618 tentative_referenceChart->GetNormalScaleMax(
5619 GetCanvasScaleFactor(), GetCanvasWidth());
5620 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5621 }
5622
5623 if (renderable) break;
5624 }
5625
5626 candidate_stack_index++;
5627 }
5628
5629 // If that did not work, look for a chart of just larger scale and
5630 // same type
5631 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5632 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5633 while (candidate_stack_index >= 0) {
5634 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5635 if (idx >= 0) {
5636 const ChartTableEntry &cte_candidate =
5637 ChartData->GetChartTableEntry(idx);
5638 int candidate_scale = cte_candidate.GetScale();
5639 int candidate_type = cte_candidate.GetChartType();
5640
5641 if ((candidate_scale <= target_scale) &&
5642 (candidate_type == target_type))
5643 break;
5644 }
5645 candidate_stack_index--;
5646 }
5647 }
5648
5649 // and if that did not work, chose stack entry 0
5650 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5651 (candidate_stack_index < 0))
5652 candidate_stack_index = 0;
5653
5654 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5655
5656 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5657 }
5658
5659 if (!g_bopengl) {
5660 // Preset the VPoint projection type to match what the quilt projection
5661 // type will be
5662 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5663
5664 // Always keep the default Mercator projection if the reference chart is
5665 // not in the PatchList or the scale is too small for it to render.
5666
5667 bool renderable = true;
5668 ChartBase *referenceChart =
5669 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5670 if (referenceChart) {
5671 double chartMaxScale = referenceChart->GetNormalScaleMax(
5672 GetCanvasScaleFactor(), GetCanvasWidth());
5673 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5674 proj = ChartData->GetDBChartProj(ref_db_index);
5675 } else
5676 proj = PROJECTION_MERCATOR;
5677
5678 VPoint.b_MercatorProjectionOverride =
5679 (m_pQuilt->GetnCharts() == 0 || !renderable);
5680
5681 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5682
5683 VPoint.SetProjectionType(proj);
5684 }
5685
5686 // If this quilt will be a perceptible delta from the existing quilt,
5687 // then refresh the entire screen
5688 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5689 // Allow the quilt to adjust the new ViewPort for performance
5690 // optimization This will normally be only a fractional (i.e.
5691 // sub-pixel) adjustment...
5692 if (b_adjust) {
5693 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5694 }
5695
5696 // ChartData->ClearCacheInUseFlags();
5697 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5698
5699 // wxStopWatch sw;
5700
5701#ifdef __ANDROID__
5702 // This is an optimization for panning on touch screen systems.
5703 // The quilt composition is deferred until the OnPaint() message gets
5704 // finally removed and processed from the message queue.
5705 // Takes advantage of the fact that touch-screen pan gestures are
5706 // usually short in distance,
5707 // so not requiring a full quilt rebuild until the pan gesture is
5708 // complete.
5709 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5710 // qDebug() << "Force compose";
5711 m_pQuilt->Compose(VPoint);
5712 } else {
5713 m_pQuilt->Invalidate();
5714 }
5715#else
5716 m_pQuilt->Compose(VPoint);
5717#endif
5718
5719 // printf("comp time %ld\n", sw.Time());
5720
5721 // If the extended chart stack has changed, invalidate any cached
5722 // render bitmap
5723 // if(m_pQuilt->GetXStackHash() != hash1) {
5724 // m_bm_cache_vp.Invalidate();
5725 // InvalidateGL();
5726 // }
5727
5728 ChartData->PurgeCacheUnusedCharts(0.7);
5729
5730 if (b_refresh) Refresh(false);
5731
5732 b_ret = true;
5733 }
5734 }
5735
5736 VPoint.skew = 0.; // Quilting supports 0 Skew
5737 } else if (!g_bopengl) {
5738 OcpnProjType projection = PROJECTION_UNKNOWN;
5739 if (m_singleChart) // viewport projection must match chart projection
5740 // without opengl
5741 projection = m_singleChart->GetChartProjectionType();
5742 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5743 VPoint.SetProjectionType(projection);
5744 }
5745
5746 // Has the Viewport projection changed? If so, invalidate the vp
5747 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5748 m_cache_vp.Invalidate();
5749 InvalidateGL();
5750 }
5751
5752 UpdateCanvasControlBar(); // Refresh the Piano
5753
5754 VPoint.chart_scale = 1.0; // fallback default value
5755
5756 if (VPoint.GetBBox().GetValid()) {
5757 // Update the viewpoint reference scale
5758 if (m_singleChart)
5759 VPoint.ref_scale = m_singleChart->GetNativeScale();
5760 else {
5761#ifdef __ANDROID__
5762 // This is an optimization for panning on touch screen systems.
5763 // See above.
5764 // Quilt might not be fully composed at this point, so for cm93
5765 // the reference scale may not be known.
5766 // In this case, do not update the VP ref_scale.
5767 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5768 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5769 }
5770#else
5771 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5772#endif
5773 }
5774
5775 // Calculate the on-screen displayed actual scale
5776 // by a simple traverse northward from the center point
5777 // of roughly one eighth of the canvas height
5778 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5779
5780 double delta_check =
5781 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5782 delta_check /= 8.;
5783
5784 double check_point = wxMin(89., VPoint.clat);
5785
5786 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5787
5788 double rhumbDist;
5789 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5790 VPoint.clon, 0, &rhumbDist);
5791
5792 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5793 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5794 // Calculate the distance between r1 and r in physical pixels.
5795 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5796 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5797
5798 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5799
5800 // A fall back in case of very high zoom-out, giving delta_y == 0
5801 // which can probably only happen with vector charts
5802 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5803
5804 // Another fallback, for highly zoomed out charts
5805 // This adjustment makes the displayed TrueScale correspond to the
5806 // same algorithm used to calculate the chart zoom-out limit for
5807 // ChartDummy.
5808 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5809
5810 if (m_true_scale_ppm)
5811 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5812 else
5813 VPoint.chart_scale = 1.0;
5814
5815 // Create a nice renderable string
5816 double round_factor = 1000.;
5817 if (VPoint.chart_scale <= 1000.)
5818 round_factor = 10.;
5819 else if (VPoint.chart_scale <= 10000.)
5820 round_factor = 100.;
5821 else if (VPoint.chart_scale <= 100000.)
5822 round_factor = 1000.;
5823
5824 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5825 double retina_coef = 1;
5826#ifdef ocpnUSE_GL
5827#ifdef __WXOSX__
5828 if (g_bopengl) {
5829 retina_coef = GetContentScaleFactor();
5830 }
5831#endif
5832#endif
5833
5834 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5835 // rounded to the nearest 10, 100 or 1000.
5836 //
5837 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5838 // true_scale_display. That does not make sense. The chart scale should be
5839 // the same as the true scale within the limits of the rounding factor.
5840 double true_scale_display =
5841 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5842 wxString text;
5843
5844 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5845
5846 if (m_displayed_scale_factor > 10.0)
5847 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5848 m_displayed_scale_factor);
5849 else if (m_displayed_scale_factor > 1.0)
5850 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5851 m_displayed_scale_factor);
5852 else if (m_displayed_scale_factor > 0.1) {
5853 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5854 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5855 } else if (m_displayed_scale_factor > 0.01) {
5856 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5857 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5858 } else {
5859 text.Printf(
5860 "%s %4.0f (---)", _("Scale"),
5861 true_scale_display); // Generally, no chart, so no chart scale factor
5862 }
5863
5864 m_scaleValue = true_scale_display;
5865 m_scaleText = text;
5866 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5867
5868 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5869 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5870 STAT_FIELD_SCALE)) {
5871 // Check to see if the text will fit in the StatusBar field...
5872 bool b_noshow = false;
5873 {
5874 int w = 0;
5875 int h;
5876 wxClientDC dc(top_frame::Get()->GetStatusBar());
5877 if (dc.IsOk()) {
5878 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5879 dc.SetFont(*templateFont);
5880 dc.GetTextExtent(text, &w, &h);
5881
5882 // If text is too long for the allocated field, try to reduce the text
5883 // string a bit.
5884 wxRect rect;
5885 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5886 rect);
5887 if (w && w > rect.width) {
5888 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5889 }
5890
5891 // Test again...if too big still, then give it up.
5892 dc.GetTextExtent(text, &w, &h);
5893
5894 if (w && w > rect.width) {
5895 b_noshow = true;
5896 }
5897 }
5898 }
5899
5900 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5901 }
5902 }
5903
5904 // Maintain member vLat/vLon
5905 m_vLat = VPoint.clat;
5906 m_vLon = VPoint.clon;
5907
5908 return b_ret;
5909}
5910
5911// Static Icon definitions for some symbols requiring
5912// scaling/rotation/translation Very specific wxDC draw commands are
5913// necessary to properly render these icons...See the code in
5914// ShipDraw()
5915
5916// This icon was adapted and scaled from the S52 Presentation Library
5917// version 3_03.
5918// Symbol VECGND02
5919
5920static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5921
5922// This ownship icon was adapted and scaled from the S52 Presentation
5923// Library version 3_03 Symbol OWNSHP05
5924static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5925 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5926
5927wxColour ChartCanvas::PredColor() {
5928 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5929 // visibility.
5930 if (SHIP_NORMAL == m_ownship_state)
5931 return GetGlobalColor("URED");
5932
5933 else if (SHIP_LOWACCURACY == m_ownship_state)
5934 return GetGlobalColor("YELO1");
5935
5936 return GetGlobalColor("NODTA");
5937}
5938
5939wxColour ChartCanvas::ShipColor() {
5940 // Establish ship color
5941 // It changes color based on GPS and Chart accuracy/availability
5942
5943 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5944
5945 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5946
5947 return GetGlobalColor("URED"); // default is OK
5948}
5949
5950void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5951 wxPoint2DDouble lShipMidPoint) {
5952 dc.SetPen(wxPen(PredColor(), 2));
5953
5954 if (SHIP_NORMAL == m_ownship_state)
5955 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5956 else
5957 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5958
5959 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5960 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5961
5962 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5963 lShipMidPoint.m_y);
5964 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5965 lShipMidPoint.m_y + 12);
5966}
5967
5968void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5969 wxPoint GPSOffsetPixels,
5970 wxPoint2DDouble lGPSPoint) {
5971 // if (m_animationActive) return;
5972 // Develop a uniform length for course predictor line dash length, based on
5973 // physical display size Use this reference length to size all other graphics
5974 // elements
5975 float ref_dim = m_display_size_mm / 24;
5976 ref_dim = wxMin(ref_dim, 12);
5977 ref_dim = wxMax(ref_dim, 6);
5978
5979 wxColour cPred;
5980 cPred.Set(g_cog_predictor_color);
5981 if (cPred == wxNullColour) cPred = PredColor();
5982
5983 // Establish some graphic element line widths dependent on the platform
5984 // display resolution
5985 // double nominal_line_width_pix = wxMax(1.0,
5986 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5987 // not less than 1 pixel
5988 double nominal_line_width_pix = wxMax(
5989 1.0,
5990 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5991
5992 // If the calculated value is greater than the config file spec value, then
5993 // use it.
5994 if (nominal_line_width_pix > g_cog_predictor_width)
5995 g_cog_predictor_width = nominal_line_width_pix;
5996
5997 // Calculate ownship Position Predictor
5998 wxPoint lPredPoint, lHeadPoint;
5999
6000 float pCog = std::isnan(gCog) ? 0 : gCog;
6001 float pSog = std::isnan(gSog) ? 0 : gSog;
6002
6003 double pred_lat, pred_lon;
6004 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
6005 &pred_lat, &pred_lon);
6006 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
6007
6008 // test to catch the case where COG/HDG line crosses the screen
6009 LLBBox box;
6010
6011 // Should we draw the Head vector?
6012 // Compare the points lHeadPoint and lPredPoint
6013 // If they differ by more than n pixels, and the head vector is valid, then
6014 // render the head vector
6015
6016 float ndelta_pix = 10.;
6017 double hdg_pred_lat, hdg_pred_lon;
6018 bool b_render_hdt = false;
6019 if (!std::isnan(gHdt)) {
6020 // Calculate ownship Heading pointer as a predictor
6021 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6022 &hdg_pred_lon);
6023 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6024 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6025 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6026 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6027 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6028 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6029 }
6030 }
6031
6032 // draw course over ground if they are longer than the ship
6033 wxPoint lShipMidPoint;
6034 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6035 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6036 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6037 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6038
6039 if (lpp >= img_height / 2) {
6040 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6041 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6042 !std::isnan(gSog)) {
6043 // COG Predictor
6044 float dash_length = ref_dim;
6045 wxDash dash_long[2];
6046 dash_long[0] =
6047 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6048 g_cog_predictor_width); // Long dash , in mm <---------+
6049 dash_long[1] = dash_long[0] / 2.0; // Short gap
6050
6051 // On ultra-hi-res displays, do not allow the dashes to be greater than
6052 // 250, since it is defined as (char)
6053 if (dash_length > 250.) {
6054 dash_long[0] = 250. / g_cog_predictor_width;
6055 dash_long[1] = dash_long[0] / 2;
6056 }
6057
6058 wxPen ppPen2(cPred, g_cog_predictor_width,
6059 (wxPenStyle)g_cog_predictor_style);
6060 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6061 ppPen2.SetDashes(2, dash_long);
6062 dc.SetPen(ppPen2);
6063 dc.StrokeLine(
6064 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6065 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6066
6067 if (g_cog_predictor_width > 1) {
6068 float line_width = g_cog_predictor_width / 3.;
6069
6070 wxDash dash_long3[2];
6071 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6072 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6073
6074 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6075 (wxPenStyle)g_cog_predictor_style);
6076 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6077 ppPen3.SetDashes(2, dash_long3);
6078 dc.SetPen(ppPen3);
6079 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6080 lGPSPoint.m_y + GPSOffsetPixels.y,
6081 lPredPoint.x + GPSOffsetPixels.x,
6082 lPredPoint.y + GPSOffsetPixels.y);
6083 }
6084
6085 if (g_cog_predictor_endmarker) {
6086 // Prepare COG predictor endpoint icon
6087 double png_pred_icon_scale_factor = .4;
6088 if (g_ShipScaleFactorExp > 1.0)
6089 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6090 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6091
6092 wxPoint icon[4];
6093
6094 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6095 (float)(lPredPoint.x - lShipMidPoint.x));
6096 cog_rad += (float)PI;
6097
6098 for (int i = 0; i < 4; i++) {
6099 int j = i * 2;
6100 double pxa = (double)(s_png_pred_icon[j]);
6101 double pya = (double)(s_png_pred_icon[j + 1]);
6102
6103 pya *= png_pred_icon_scale_factor;
6104 pxa *= png_pred_icon_scale_factor;
6105
6106 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6107 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6108
6109 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6110 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6111 }
6112
6113 // Render COG endpoint icon
6114 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6115 wxPENSTYLE_SOLID);
6116 dc.SetPen(ppPen1);
6117 dc.SetBrush(wxBrush(cPred));
6118
6119 dc.StrokePolygon(4, icon);
6120 }
6121 }
6122 }
6123
6124 // HDT Predictor
6125 if (b_render_hdt) {
6126 float hdt_dash_length = ref_dim * 0.4;
6127
6128 cPred.Set(g_ownship_HDTpredictor_color);
6129 if (cPred == wxNullColour) cPred = PredColor();
6130 float hdt_width =
6131 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6132 : g_cog_predictor_width * 0.8);
6133 wxDash dash_short[2];
6134 dash_short[0] =
6135 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6136 hdt_width); // Short dash , in mm <---------+
6137 dash_short[1] =
6138 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6139 hdt_width); // Short gap |
6140
6141 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6142 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6143 ppPen2.SetDashes(2, dash_short);
6144
6145 dc.SetPen(ppPen2);
6146 dc.StrokeLine(
6147 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6148 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6149
6150 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6151 dc.SetPen(ppPen1);
6152 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6153
6154 if (g_ownship_HDTpredictor_endmarker) {
6155 double nominal_circle_size_pixels = wxMax(
6156 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6157
6158 // Scale the circle to ChartScaleFactor, slightly softened....
6159 if (g_ShipScaleFactorExp > 1.0)
6160 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6161
6162 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6163 lHeadPoint.y + GPSOffsetPixels.y,
6164 nominal_circle_size_pixels / 2);
6165 }
6166 }
6167
6168 // Draw radar rings if activated
6169 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6170 double factor = 1.00;
6171 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6172 factor = 1 / 1.852;
6173 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6174 if (std::isnan(gSog))
6175 factor = 0.0;
6176 else
6177 factor = gSog / 60;
6178 }
6179 factor *= g_fNavAidRadarRingsStep;
6180
6181 double tlat, tlon;
6182 wxPoint r;
6183 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6184 GetCanvasPointPix(tlat, tlon, &r);
6185
6186 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6187 pow((double)(lGPSPoint.m_y - r.y), 2));
6188 int pix_radius = (int)lpp;
6189
6190 wxColor rangeringcolour =
6191 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6192
6193 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6194
6195 dc.SetPen(ppPen1);
6196 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6197
6198 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6199 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6200 }
6201}
6202
6203#if ocpnUSE_GL
6204void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6205bool ChartCanvas::CanAccelerateGlPanning() {
6206 return GetglCanvas()->CanAcceleratePanning();
6207}
6208void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6209
6210#else
6211void ChartCanvas::ResetGlGridFont() {}
6212bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6213void ChartCanvas::SetupGlCompression() {}
6214#endif
6215
6216void ChartCanvas::ComputeShipScaleFactor(
6217 float icon_hdt, int ownShipWidth, int ownShipLength,
6218 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6219 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6220 float screenResolution = m_pix_per_mm;
6221
6222 // Calculate the true ship length in exact pixels
6223 double ship_bow_lat, ship_bow_lon;
6224 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6225 &ship_bow_lat, &ship_bow_lon);
6226 wxPoint lShipBowPoint;
6227 wxPoint2DDouble b_point =
6228 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6229 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6230
6231 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6232 powf((float)(b_point.m_y - a_point.m_y), 2));
6233
6234 // And in mm
6235 float shipLength_mm = shipLength_px / screenResolution;
6236
6237 // Set minimum ownship drawing size
6238 float ownship_min_mm = g_n_ownship_min_mm;
6239 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6240
6241 // Calculate Nautical Miles distance from midships to gps antenna
6242 float hdt_ant = icon_hdt + 180.;
6243 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6244 float dx = g_n_gps_antenna_offset_x / 1852.;
6245 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6246 {
6247 hdt_ant = icon_hdt;
6248 dy = -dy;
6249 }
6250
6251 // If the drawn ship size is going to be clamped, adjust the gps antenna
6252 // offsets
6253 if (shipLength_mm < ownship_min_mm) {
6254 dy /= shipLength_mm / ownship_min_mm;
6255 dx /= shipLength_mm / ownship_min_mm;
6256 }
6257
6258 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6259
6260 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6261 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6262 &ship_mid_lon1);
6263
6264 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6265 &lShipMidPoint);
6266
6267 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6268 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6269
6270 float scale_factor = shipLength_px / ownShipLength;
6271
6272 // Calculate a scale factor that would produce a reasonably sized icon
6273 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6274
6275 // And choose the correct one
6276 scale_factor = wxMax(scale_factor, scale_factor_min);
6277
6278 scale_factor_y = scale_factor;
6279 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6280 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6281}
6282
6283void ChartCanvas::ShipDraw(ocpnDC &dc) {
6284 if (!GetVP().IsValid()) return;
6285
6286 wxPoint GPSOffsetPixels(0, 0);
6287 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6288
6289 // COG/SOG may be undefined in NMEA data stream
6290 float pCog = std::isnan(gCog) ? 0 : gCog;
6291 float pSog = std::isnan(gSog) ? 0 : gSog;
6292
6293 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6294
6295 lShipMidPoint = lGPSPoint;
6296
6297 // Draw the icon rotated to the COG
6298 // or to the Hdt if available
6299 float icon_hdt = pCog;
6300 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6301
6302 // COG may be undefined in NMEA data stream
6303 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6304
6305 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6306 // predictor
6307 double osd_head_lat, osd_head_lon;
6308 wxPoint osd_head_point;
6309
6310 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6311 &osd_head_lon);
6312
6313 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6314
6315 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6316 (float)(osd_head_point.x - lShipMidPoint.m_x));
6317 icon_rad += (float)PI;
6318
6319 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6320
6321 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6322 // nominal size and is just barely outside the viewport ....
6323 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6324
6325 // TODO: fix to include actual size of boat that will be rendered
6326 int img_height = 0;
6327 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6328 if (GetVP().chart_scale >
6329 300000) // According to S52, this should be 50,000
6330 {
6331 ShipDrawLargeScale(dc, lShipMidPoint);
6332 img_height = 20;
6333 } else {
6334 wxImage pos_image;
6335
6336 // Substitute user ownship image if found
6337 if (m_pos_image_user)
6338 pos_image = m_pos_image_user->Copy();
6339 else if (SHIP_NORMAL == m_ownship_state)
6340 pos_image = m_pos_image_red->Copy();
6341 if (SHIP_LOWACCURACY == m_ownship_state)
6342 pos_image = m_pos_image_yellow->Copy();
6343 else if (SHIP_NORMAL != m_ownship_state)
6344 pos_image = m_pos_image_grey->Copy();
6345
6346 // Substitute user ownship image if found
6347 if (m_pos_image_user) {
6348 pos_image = m_pos_image_user->Copy();
6349
6350 if (SHIP_LOWACCURACY == m_ownship_state)
6351 pos_image = m_pos_image_user_yellow->Copy();
6352 else if (SHIP_NORMAL != m_ownship_state)
6353 pos_image = m_pos_image_user_grey->Copy();
6354 }
6355
6356 img_height = pos_image.GetHeight();
6357
6358 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6359 g_OwnShipIconType > 0) // use large ship
6360 {
6361 int ownShipWidth = 22; // Default values from s_ownship_icon
6362 int ownShipLength = 84;
6363 if (g_OwnShipIconType == 1) {
6364 ownShipWidth = pos_image.GetWidth();
6365 ownShipLength = pos_image.GetHeight();
6366 }
6367
6368 float scale_factor_x, scale_factor_y;
6369 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6370 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6371 scale_factor_x, scale_factor_y);
6372
6373 if (g_OwnShipIconType == 1) { // Scaled bitmap
6374 pos_image.Rescale(ownShipWidth * scale_factor_x,
6375 ownShipLength * scale_factor_y,
6376 wxIMAGE_QUALITY_HIGH);
6377 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6378 wxImage rot_image =
6379 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6380
6381 // Simple sharpening algorithm.....
6382 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6383 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6384 if (rot_image.GetAlpha(ip, jp) > 64)
6385 rot_image.SetAlpha(ip, jp, 255);
6386
6387 wxBitmap os_bm(rot_image);
6388
6389 int w = os_bm.GetWidth();
6390 int h = os_bm.GetHeight();
6391 img_height = h;
6392
6393 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6394 lShipMidPoint.m_y - h / 2, true);
6395
6396 // Maintain dirty box,, missing in __WXMSW__ library
6397 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6398 lShipMidPoint.m_y - h / 2);
6399 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6400 lShipMidPoint.m_y - h / 2 + h);
6401 }
6402
6403 else if (g_OwnShipIconType == 2) { // Scaled Vector
6404 wxPoint ownship_icon[10];
6405
6406 for (int i = 0; i < 10; i++) {
6407 int j = i * 2;
6408 float pxa = (float)(s_ownship_icon[j]);
6409 float pya = (float)(s_ownship_icon[j + 1]);
6410 pya *= scale_factor_y;
6411 pxa *= scale_factor_x;
6412
6413 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6414 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6415
6416 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6417 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6418 }
6419
6420 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6421 dc.SetPen(ppPen1);
6422 dc.SetBrush(wxBrush(ShipColor()));
6423
6424 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6425
6426 // draw reference point (midships) cross
6427 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6428 ownship_icon[7].y);
6429 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6430 ownship_icon[9].y);
6431 }
6432
6433 img_height = ownShipLength * scale_factor_y;
6434
6435 // Reference point, where the GPS antenna is
6436 int circle_rad = 3;
6437 if (m_pos_image_user) circle_rad = 1;
6438
6439 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6440 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6441 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6442 } else { // Fixed bitmap icon.
6443 /* non opengl, or suboptimal opengl via ocpndc: */
6444 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6445 wxImage rot_image =
6446 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6447
6448 // Simple sharpening algorithm.....
6449 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6450 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6451 if (rot_image.GetAlpha(ip, jp) > 64)
6452 rot_image.SetAlpha(ip, jp, 255);
6453
6454 wxBitmap os_bm(rot_image);
6455
6456 if (g_ShipScaleFactorExp > 1) {
6457 wxImage scaled_image = os_bm.ConvertToImage();
6458 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6459 1.0; // soften the scale factor a bit
6460 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6461 scaled_image.GetHeight() * factor,
6462 wxIMAGE_QUALITY_HIGH));
6463 }
6464 int w = os_bm.GetWidth();
6465 int h = os_bm.GetHeight();
6466 img_height = h;
6467
6468 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6469 lShipMidPoint.m_y - h / 2, true);
6470
6471 // Reference point, where the GPS antenna is
6472 int circle_rad = 3;
6473 if (m_pos_image_user) circle_rad = 1;
6474
6475 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6476 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6477 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6478
6479 // Maintain dirty box,, missing in __WXMSW__ library
6480 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6481 lShipMidPoint.m_y - h / 2);
6482 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6483 lShipMidPoint.m_y - h / 2 + h);
6484 }
6485 } // ownship draw
6486 }
6487
6488 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6489}
6490
6491/* @ChartCanvas::CalcGridSpacing
6492 **
6493 ** Calculate the major and minor spacing between the lat/lon grid
6494 **
6495 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6496 *window
6497 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6498 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6499 ** @return [void]
6500 */
6501void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6502 float &MinorSpacing) {
6503 // table for calculating the distance between the grids
6504 // [0] view_scale ppm
6505 // [1] spacing between major grid lines in degrees
6506 // [2] spacing between minor grid lines in degrees
6507 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6508 {.000001f, 45.0f, 15.0f},
6509 {.0002f, 30.0f, 10.0f},
6510 {.0003f, 10.0f, 2.0f},
6511 {.0008f, 5.0f, 1.0f},
6512 {.001f, 2.0f, 30.0f / 60.0f},
6513 {.003f, 1.0f, 20.0f / 60.0f},
6514 {.006f, 0.5f, 10.0f / 60.0f},
6515 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6516 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6517 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6518 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6519 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6520 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6521 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6522 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6523
6524 unsigned int tabi;
6525 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6526 if (view_scale_ppm < lltab[tabi][0]) break;
6527 MajorSpacing = lltab[tabi][1]; // major latitude distance
6528 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6529 return;
6530}
6531/* @ChartCanvas::CalcGridText *************************************
6532 **
6533 ** Calculates text to display at the major grid lines
6534 **
6535 ** @param [r] latlon [float] latitude or longitude of grid line
6536 ** @param [r] spacing [float] distance between two major grid lines
6537 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6538 **
6539 ** @return
6540 */
6541
6542wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6543 int deg = (int)fabs(latlon); // degrees
6544 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6545 char postfix;
6546
6547 // calculate postfix letter (NSEW)
6548 if (latlon > 0.0) {
6549 if (bPostfix) {
6550 postfix = 'N';
6551 } else {
6552 postfix = 'E';
6553 }
6554 } else if (latlon < 0.0) {
6555 if (bPostfix) {
6556 postfix = 'S';
6557 } else {
6558 postfix = 'W';
6559 }
6560 } else {
6561 postfix = ' '; // no postfix for equator and greenwich
6562 }
6563 // calculate text, display minutes only if spacing is smaller than one degree
6564
6565 wxString ret;
6566 if (spacing >= 1.0) {
6567 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6568 } else if (spacing >= (1.0 / 60.0)) {
6569 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6570 } else {
6571 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6572 }
6573
6574 return ret;
6575}
6576
6577/* @ChartCanvas::GridDraw *****************************************
6578 **
6579 ** Draws major and minor Lat/Lon Grid on the chart
6580 ** - distance between Grid-lm ines are calculated automatic
6581 ** - major grid lines will be across the whole chart window
6582 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6583 **
6584 ** @param [w] dc [wxDC&] the wx drawing context
6585 **
6586 ** @return [void]
6587 ************************************************************************/
6588void ChartCanvas::GridDraw(ocpnDC &dc) {
6589 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6590
6591 double nlat, elon, slat, wlon;
6592 float lat, lon;
6593 float dlon;
6594 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6595 wxCoord w, h;
6596 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6597 dc.SetPen(GridPen);
6598 if (!m_pgridFont) SetupGridFont();
6599 dc.SetFont(*m_pgridFont);
6600 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6601
6602 w = m_canvas_width;
6603 h = m_canvas_height;
6604
6605 GetCanvasPixPoint(0, 0, nlat,
6606 wlon); // get lat/lon of upper left point of the window
6607 GetCanvasPixPoint(w, h, slat,
6608 elon); // get lat/lon of lower right point of the window
6609 dlon =
6610 elon -
6611 wlon; // calculate how many degrees of longitude are shown in the window
6612 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6613 {
6614 dlon = dlon + 360.0;
6615 }
6616 // calculate distance between latitude grid lines
6617 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6618
6619 // calculate position of first major latitude grid line
6620 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6621
6622 // Draw Major latitude grid lines and text
6623 while (lat < nlat) {
6624 wxPoint r;
6625 wxString st =
6626 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6627 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6628 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6629 dc.DrawText(st, 0, r.y); // draw text
6630 lat = lat + gridlatMajor;
6631
6632 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6633 }
6634
6635 // calculate position of first minor latitude grid line
6636 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6637
6638 // Draw minor latitude grid lines
6639 while (lat < nlat) {
6640 wxPoint r;
6641 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6642 dc.DrawLine(0, r.y, 10, r.y, false);
6643 dc.DrawLine(w - 10, r.y, w, r.y, false);
6644 lat = lat + gridlatMinor;
6645 }
6646
6647 // calculate distance between grid lines
6648 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6649
6650 // calculate position of first major latitude grid line
6651 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6652
6653 // draw major longitude grid lines
6654 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6655 wxPoint r;
6656 wxString st = CalcGridText(lon, gridlonMajor, false);
6657 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6658 dc.DrawLine(r.x, 0, r.x, h, false);
6659 dc.DrawText(st, r.x, 0);
6660 lon = lon + gridlonMajor;
6661 if (lon > 180.0) {
6662 lon = lon - 360.0;
6663 }
6664
6665 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6666 }
6667
6668 // calculate position of first minor longitude grid line
6669 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6670 // draw minor longitude grid lines
6671 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6672 wxPoint r;
6673 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6674 dc.DrawLine(r.x, 0, r.x, 10, false);
6675 dc.DrawLine(r.x, h - 10, r.x, h, false);
6676 lon = lon + gridlonMinor;
6677 if (lon > 180.0) {
6678 lon = lon - 360.0;
6679 }
6680 }
6681}
6682
6683void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6684 if (0 ) {
6685 double blat, blon, tlat, tlon;
6686 wxPoint r;
6687
6688 int x_origin = m_bDisplayGrid ? 60 : 20;
6689 int y_origin = m_canvas_height - 50;
6690
6691 float dist;
6692 int count;
6693 wxPen pen1, pen2;
6694
6695 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6696 {
6697 dist = 10.0;
6698 count = 5;
6699 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6700 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6701 } else // Draw 1 mile scale as SCALEB10
6702 {
6703 dist = 1.0;
6704 count = 10;
6705 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6706 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6707 }
6708
6709 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6710 double rotation = -VPoint.rotation;
6711
6712 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6713 GetCanvasPointPix(tlat, tlon, &r);
6714 int l1 = (y_origin - r.y) / count;
6715
6716 for (int i = 0; i < count; i++) {
6717 int y = l1 * i;
6718 if (i & 1)
6719 dc.SetPen(pen1);
6720 else
6721 dc.SetPen(pen2);
6722
6723 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6724 }
6725 } else {
6726 double blat, blon, tlat, tlon;
6727
6728 int x_origin = 5.0 * GetPixPerMM();
6729 int chartbar_height = GetChartbarHeight();
6730 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6731 // if (style->chartStatusWindowTransparent)
6732 // chartbar_height = 0;
6733 int y_origin = m_canvas_height - chartbar_height - 5;
6734#ifdef __WXOSX__
6735 if (!g_bopengl)
6736 y_origin =
6737 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6738#endif
6739
6740 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6741 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6742
6743 double d;
6744 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6745 d /= 2;
6746
6747 int unit = g_iDistanceFormat;
6748 if (d < .5 &&
6749 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6750 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6751
6752 // nice number
6753 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6754 float places = floor(logdist), rem = logdist - places;
6755 dist = pow(10, places);
6756
6757 if (rem < .2)
6758 dist /= 5;
6759 else if (rem < .5)
6760 dist /= 2;
6761
6762 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6763 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6764 double rotation = -VPoint.rotation;
6765
6766 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6767 &tlat, &tlon);
6768 wxPoint r;
6769 GetCanvasPointPix(tlat, tlon, &r);
6770 int l1 = r.x - x_origin;
6771
6772 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6773 12); // Store this for later reference
6774
6775 dc.SetPen(pen1);
6776
6777 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6778 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6779 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6780
6781 if (!m_pgridFont) SetupGridFont();
6782 dc.SetFont(*m_pgridFont);
6783 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6784 int w, h;
6785 dc.GetTextExtent(s, &w, &h);
6786 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6787 if (g_bopengl) {
6788 w /= dpi_factor;
6789 h /= dpi_factor;
6790 }
6791 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6792 }
6793}
6794
6795void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6796 // Constants?
6797 double da_min = 2.;
6798 double da_max = 6.;
6799 double ra_min = 0.;
6800 double ra_max = 40.;
6801
6802 wxPen pen_save = dc.GetPen();
6803
6804 wxDateTime now = wxDateTime::Now();
6805
6806 dc.SetPen(pen);
6807
6808 int x0, y0, x1, y1;
6809
6810 x0 = x1 = x + radius; // Start point
6811 y0 = y1 = y;
6812 double angle = 0.;
6813 int i = 0;
6814
6815 while (angle < 360.) {
6816 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6817 angle += da;
6818
6819 if (angle > 360.) angle = 360.;
6820
6821 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6822
6823 double r;
6824 if (i & 1)
6825 r = radius + ra;
6826 else
6827 r = radius - ra;
6828
6829 x1 = (int)(x + cos(angle * PI / 180.) * r);
6830 y1 = (int)(y + sin(angle * PI / 180.) * r);
6831
6832 dc.DrawLine(x0, y0, x1, y1);
6833
6834 x0 = x1;
6835 y0 = y1;
6836
6837 i++;
6838 }
6839
6840 dc.DrawLine(x + radius, y, x1, y1); // closure
6841
6842 dc.SetPen(pen_save);
6843}
6844
6845static bool bAnchorSoundPlaying = false;
6846
6847static void onAnchorSoundFinished(void *ptr) {
6848 o_sound::g_anchorwatch_sound->UnLoad();
6849 bAnchorSoundPlaying = false;
6850}
6851
6852void ChartCanvas::AlertDraw(ocpnDC &dc) {
6853 using namespace o_sound;
6854 // Visual and audio alert for anchorwatch goes here
6855 bool play_sound = false;
6856 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6857 if (AnchorAlertOn1) {
6858 wxPoint TargetPoint;
6860 &TargetPoint);
6861 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6862 TargetPoint.y, 100);
6863 play_sound = true;
6864 }
6865 } else
6866 AnchorAlertOn1 = false;
6867
6868 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6869 if (AnchorAlertOn2) {
6870 wxPoint TargetPoint;
6872 &TargetPoint);
6873 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6874 TargetPoint.y, 100);
6875 play_sound = true;
6876 }
6877 } else
6878 AnchorAlertOn2 = false;
6879
6880 if (play_sound) {
6881 if (!bAnchorSoundPlaying) {
6882 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6883 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6884 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6885 if (g_anchorwatch_sound->IsOk()) {
6886 bAnchorSoundPlaying = true;
6887 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6888 g_anchorwatch_sound->Play();
6889 }
6890 }
6891 }
6892}
6893
6894void ChartCanvas::UpdateShips() {
6895 // Get the rectangle in the current dc which bounds the "ownship" symbol
6896
6897 wxClientDC dc(this);
6898 // Sometimes during startup the dc x or y size is zero, which causes the
6899 // bitmap creation to fail. Just skip the update in this case.
6900 if (!dc.IsOk() || dc.GetSize().x < 1 || dc.GetSize().y < 1) return;
6901
6902 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6903 if (!test_bitmap.IsOk()) return;
6904
6905 wxMemoryDC temp_dc(test_bitmap);
6906
6907 temp_dc.ResetBoundingBox();
6908 temp_dc.DestroyClippingRegion();
6909 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6910
6911 // Draw the ownship on the temp_dc
6912 ocpnDC ocpndc = ocpnDC(temp_dc);
6913 ShipDraw(ocpndc);
6914
6915 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6916 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6917 if (p) {
6918 wxPoint px;
6919 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6920 ocpndc.CalcBoundingBox(px.x, px.y);
6921 }
6922 }
6923
6924 ship_draw_rect =
6925 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6926 temp_dc.MaxY() - temp_dc.MinY());
6927
6928 wxRect own_ship_update_rect = ship_draw_rect;
6929
6930 if (!own_ship_update_rect.IsEmpty()) {
6931 // The required invalidate rectangle is the union of the last drawn
6932 // rectangle and this drawn rectangle
6933 own_ship_update_rect.Union(ship_draw_last_rect);
6934 own_ship_update_rect.Inflate(2);
6935 }
6936
6937 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6938
6939 ship_draw_last_rect = ship_draw_rect;
6940
6941 temp_dc.SelectObject(wxNullBitmap);
6942}
6943
6944void ChartCanvas::UpdateAlerts() {
6945 // Get the rectangle in the current dc which bounds the detected Alert
6946 // targets
6947
6948 // Use this dc
6949 wxClientDC dc(this);
6950
6951 // Get dc boundary
6952 int sx, sy;
6953 dc.GetSize(&sx, &sy);
6954
6955 // Need a bitmap
6956 wxBitmap test_bitmap(sx, sy, -1);
6957
6958 // Create a memory DC
6959 wxMemoryDC temp_dc;
6960 temp_dc.SelectObject(test_bitmap);
6961
6962 temp_dc.ResetBoundingBox();
6963 temp_dc.DestroyClippingRegion();
6964 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6965
6966 // Draw the Alert Targets on the temp_dc
6967 ocpnDC ocpndc = ocpnDC(temp_dc);
6968 AlertDraw(ocpndc);
6969
6970 // Retrieve the drawing extents
6971 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6972 temp_dc.MaxX() - temp_dc.MinX(),
6973 temp_dc.MaxY() - temp_dc.MinY());
6974
6975 if (!alert_rect.IsEmpty())
6976 alert_rect.Inflate(2); // clear all drawing artifacts
6977
6978 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6979 // The required invalidate rectangle is the union of the last drawn
6980 // rectangle and this drawn rectangle
6981 wxRect alert_update_rect = alert_draw_rect;
6982 alert_update_rect.Union(alert_rect);
6983
6984 // Invalidate the rectangular region
6985 RefreshRect(alert_update_rect, false);
6986 }
6987
6988 // Save this rectangle for next time
6989 alert_draw_rect = alert_rect;
6990
6991 temp_dc.SelectObject(wxNullBitmap); // clean up
6992}
6993
6994void ChartCanvas::UpdateAIS() {
6995 if (!g_pAIS) return;
6996
6997 // Get the rectangle in the current dc which bounds the detected AIS targets
6998
6999 // Use this dc
7000 wxClientDC dc(this);
7001
7002 // Get dc boundary
7003 int sx, sy;
7004 dc.GetSize(&sx, &sy);
7005
7006 wxRect ais_rect;
7007
7008 // How many targets are there?
7009
7010 // If more than "some number", it will be cheaper to refresh the entire
7011 // screen than to build update rectangles for each target.
7012 if (g_pAIS->GetTargetList().size() > 10) {
7013 ais_rect = wxRect(0, 0, sx, sy); // full screen
7014 } else {
7015 // Need a bitmap
7016 wxBitmap test_bitmap(sx, sy, -1);
7017
7018 // Create a memory DC
7019 wxMemoryDC temp_dc;
7020 temp_dc.SelectObject(test_bitmap);
7021
7022 temp_dc.ResetBoundingBox();
7023 temp_dc.DestroyClippingRegion();
7024 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7025
7026 // Draw the AIS Targets on the temp_dc
7027 ocpnDC ocpndc = ocpnDC(temp_dc);
7028 AISDraw(ocpndc, GetVP(), this);
7029 AISDrawAreaNotices(ocpndc, GetVP(), this);
7030
7031 // Retrieve the drawing extents
7032 ais_rect =
7033 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7034 temp_dc.MaxY() - temp_dc.MinY());
7035
7036 if (!ais_rect.IsEmpty())
7037 ais_rect.Inflate(2); // clear all drawing artifacts
7038
7039 temp_dc.SelectObject(wxNullBitmap); // clean up
7040 }
7041
7042 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7043 // The required invalidate rectangle is the union of the last drawn
7044 // rectangle and this drawn rectangle
7045 wxRect ais_update_rect = ais_draw_rect;
7046 ais_update_rect.Union(ais_rect);
7047
7048 // Invalidate the rectangular region
7049 RefreshRect(ais_update_rect, false);
7050 }
7051
7052 // Save this rectangle for next time
7053 ais_draw_rect = ais_rect;
7054}
7055
7056void ChartCanvas::ToggleCPAWarn() {
7057 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7058 wxString mess;
7059 if (g_bCPAWarn) {
7060 g_bTCPA_Max = true;
7061 mess = _("ON");
7062 } else {
7063 g_bTCPA_Max = false;
7064 mess = _("OFF");
7065 }
7066 // Print to status bar if available.
7067 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7068 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7069 } else {
7070 if (!g_AisFirstTimeUse) {
7071 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7072 _("CPA") + " " + mess, 4, 4);
7073 }
7074 }
7075}
7076
7077void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7078
7079void ChartCanvas::OnSize(wxSizeEvent &event) {
7080 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7081 // GetClientSize returns the size of the canvas area in logical pixels.
7082 GetClientSize(&m_canvas_width, &m_canvas_height);
7083
7084#ifdef __WXOSX__
7085 // Support scaled HDPI displays.
7086 m_displayScale = GetContentScaleFactor();
7087#endif
7088
7089 // Convert to physical pixels.
7090 m_canvas_width *= m_displayScale;
7091 m_canvas_height *= m_displayScale;
7092
7093 // Resize the current viewport
7094 VPoint.pix_width = m_canvas_width;
7095 VPoint.pix_height = m_canvas_height;
7096 VPoint.SetPixelScale(m_displayScale);
7097
7098 // Get some canvas metrics
7099
7100 // Rescale to current value, in order to rebuild VPoint data
7101 // structures for new canvas size
7103
7104 m_absolute_min_scale_ppm =
7105 m_canvas_width /
7106 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7107
7108 // Inform the parent Frame that I am being resized...
7109 top_frame::Get()->ProcessCanvasResize();
7110
7111 // if MUIBar is active, size the bar
7112 // if(g_useMUI && !m_muiBar){ // rebuild if
7113 // necessary
7114 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7115 // m_muiBarHOSize = m_muiBar->GetSize();
7116 // }
7117
7118 if (m_muiBar) {
7119 SetMUIBarPosition();
7120 UpdateFollowButtonState();
7121 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7122 }
7123
7124 // Set up the scroll margins
7125 xr_margin = m_canvas_width * 95 / 100;
7126 xl_margin = m_canvas_width * 5 / 100;
7127 yt_margin = m_canvas_height * 5 / 100;
7128 yb_margin = m_canvas_height * 95 / 100;
7129
7130 if (m_pQuilt)
7131 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7132
7133 // Resize the scratch BM
7134 delete pscratch_bm;
7135 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7136 m_brepaint_piano = true;
7137
7138 // Resize the Route Calculation BM
7139 m_dc_route.SelectObject(wxNullBitmap);
7140 delete proute_bm;
7141 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7142 m_dc_route.SelectObject(*proute_bm);
7143
7144 // Resize the saved Bitmap
7145 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7146
7147 // Resize the working Bitmap
7148 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7149
7150 // Rescale again, to capture all the changes for new canvas size
7152
7153#ifdef ocpnUSE_GL
7154 if (/*g_bopengl &&*/ m_glcc) {
7155 // FIXME (dave) This can go away?
7156 m_glcc->OnSize(event);
7157 }
7158#endif
7159
7160 FormatPianoKeys();
7161 // Invalidate the whole window
7162 ReloadVP();
7163}
7164
7165void ChartCanvas::ProcessNewGUIScale() {
7166 // m_muiBar->Hide();
7167 delete m_muiBar;
7168 m_muiBar = 0;
7169
7170 CreateMUIBar();
7171}
7172
7173void ChartCanvas::CreateMUIBar() {
7174 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7175 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7176 m_muiBar->SetColorScheme(m_cs);
7177 m_muiBarHOSize = m_muiBar->m_size;
7178 }
7179
7180 if (m_muiBar) {
7181 // We need to update the m_bENCGroup flag, not least for the initial
7182 // creation of a MUIBar
7183 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7184
7185 SetMUIBarPosition();
7186 UpdateFollowButtonState();
7187 m_muiBar->UpdateDynamicValues();
7188 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7189 }
7190}
7191
7192void ChartCanvas::SetMUIBarPosition() {
7193 // if MUIBar is active, size the bar
7194 if (m_muiBar) {
7195 // We estimate the piano width based on the canvas width
7196 int pianoWidth = GetClientSize().x * 0.6f;
7197 // If the piano already exists, we can use its exact width
7198 // if(m_Piano)
7199 // pianoWidth = m_Piano->GetWidth();
7200
7201 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7202 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7203 delete m_muiBar;
7204 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7205 m_muiBar->SetColorScheme(m_cs);
7206 }
7207 }
7208
7209 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7210 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7211 delete m_muiBar;
7212 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7213 m_muiBar->SetColorScheme(m_cs);
7214 }
7215 }
7216
7217 m_muiBar->SetBestPosition();
7218 }
7219}
7220
7221void ChartCanvas::DestroyMuiBar() {
7222 if (m_muiBar) {
7223 delete m_muiBar;
7224 m_muiBar = NULL;
7225 }
7226}
7227
7228void ChartCanvas::ShowCompositeInfoWindow(
7229 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7230 if (n_charts > 0) {
7231 if (NULL == m_pCIWin) {
7232 m_pCIWin = new ChInfoWin(this);
7233 m_pCIWin->Hide();
7234 }
7235
7236 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7237 wxString s;
7238
7239 s = _("Composite of ");
7240
7241 wxString s1;
7242 s1.Printf("%d ", n_charts);
7243 if (n_charts > 1)
7244 s1 += _("charts");
7245 else
7246 s1 += _("chart");
7247 s += s1;
7248 s += '\n';
7249
7250 s1.Printf(_("Chart scale"));
7251 s1 += ": ";
7252 wxString s2;
7253 s2.Printf("1:%d\n", scale);
7254 s += s1;
7255 s += s2;
7256
7257 s1 = _("Zoom in for more information");
7258 s += s1;
7259 s += '\n';
7260
7261 int char_width = s1.Length();
7262 int char_height = 3;
7263
7264 if (g_bChartBarEx) {
7265 s += '\n';
7266 int j = 0;
7267 for (int i : index_vector) {
7268 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7269 wxString path = cte.GetFullSystemPath();
7270 s += path;
7271 s += '\n';
7272 char_height++;
7273 char_width = wxMax(char_width, path.Length());
7274 if (j++ >= 9) break;
7275 }
7276 if (j >= 9) {
7277 s += " .\n .\n .\n";
7278 char_height += 3;
7279 }
7280 s += '\n';
7281 char_height += 1;
7282
7283 char_width += 4; // Fluff
7284 }
7285
7286 m_pCIWin->SetString(s);
7287
7288 m_pCIWin->FitToChars(char_width, char_height);
7289
7290 wxPoint p;
7291 p.x = x / GetContentScaleFactor();
7292 if ((p.x + m_pCIWin->GetWinSize().x) >
7293 (m_canvas_width / GetContentScaleFactor()))
7294 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7295 m_pCIWin->GetWinSize().x) /
7296 2; // centered
7297
7298 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7299 4 - m_pCIWin->GetWinSize().y;
7300
7301 m_pCIWin->dbIndex = 0;
7302 m_pCIWin->chart_scale = 0;
7303 m_pCIWin->SetPosition(p);
7304 m_pCIWin->SetBitmap();
7305 m_pCIWin->Refresh();
7306 m_pCIWin->Show();
7307 }
7308 } else {
7309 HideChartInfoWindow();
7310 }
7311}
7312
7313void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7314 if (dbIndex >= 0) {
7315 if (NULL == m_pCIWin) {
7316 m_pCIWin = new ChInfoWin(this);
7317 m_pCIWin->Hide();
7318 }
7319
7320 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7321 wxString s;
7322 ChartBase *pc = NULL;
7323
7324 // TOCTOU race but worst case will reload chart.
7325 // need to lock it or the background spooler may evict charts in
7326 // OpenChartFromDBAndLock
7327 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7328 pc = ChartData->OpenChartFromDBAndLock(
7329 dbIndex, FULL_INIT); // this must come from cache
7330
7331 int char_width, char_height;
7332 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7333 if (pc) ChartData->UnLockCacheChart(dbIndex);
7334
7335 m_pCIWin->SetString(s);
7336 m_pCIWin->FitToChars(char_width, char_height);
7337
7338 wxPoint p;
7339 p.x = x / GetContentScaleFactor();
7340 if ((p.x + m_pCIWin->GetWinSize().x) >
7341 (m_canvas_width / GetContentScaleFactor()))
7342 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7343 m_pCIWin->GetWinSize().x) /
7344 2; // centered
7345
7346 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7347 4 - m_pCIWin->GetWinSize().y;
7348
7349 m_pCIWin->dbIndex = dbIndex;
7350 m_pCIWin->SetPosition(p);
7351 m_pCIWin->SetBitmap();
7352 m_pCIWin->Refresh();
7353 m_pCIWin->Show();
7354 }
7355 } else {
7356 HideChartInfoWindow();
7357 }
7358}
7359
7360void ChartCanvas::HideChartInfoWindow() {
7361 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7362 m_pCIWin->Hide();
7363 m_pCIWin->Destroy();
7364 m_pCIWin = NULL;
7365
7366#ifdef __ANDROID__
7367 androidForceFullRepaint();
7368#endif
7369 }
7370}
7371
7372void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7373 wxMouseEvent ev(wxEVT_MOTION);
7374 ev.m_x = mouse_x;
7375 ev.m_y = mouse_y;
7376 ev.m_leftDown = mouse_leftisdown;
7377
7378 wxEvtHandler *evthp = GetEventHandler();
7379
7380 ::wxPostEvent(evthp, ev);
7381}
7382
7383void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7384 if ((m_panx_target_final - m_panx_target_now) ||
7385 (m_pany_target_final - m_pany_target_now)) {
7386 DoTimedMovementTarget();
7387 } else
7388 DoTimedMovement();
7389}
7390
7391void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7392
7393bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7394 int delta) {
7395 if (m_disable_edge_pan) return false;
7396
7397 bool bft = false;
7398 int pan_margin = m_canvas_width * margin / 100;
7399 int pan_timer_set = 200;
7400 double pan_delta = GetVP().pix_width * delta / 100;
7401 int pan_x = 0;
7402 int pan_y = 0;
7403
7404 if (x > m_canvas_width - pan_margin) {
7405 bft = true;
7406 pan_x = pan_delta;
7407 }
7408
7409 else if (x < pan_margin) {
7410 bft = true;
7411 pan_x = -pan_delta;
7412 }
7413
7414 if (y < pan_margin) {
7415 bft = true;
7416 pan_y = -pan_delta;
7417 }
7418
7419 else if (y > m_canvas_height - pan_margin) {
7420 bft = true;
7421 pan_y = pan_delta;
7422 }
7423
7424 // Of course, if dragging, and the mouse left button is not down, we must
7425 // stop the event injection
7426 if (bdragging) {
7427 if (!g_btouch) {
7428 wxMouseState state = ::wxGetMouseState();
7429#if wxCHECK_VERSION(3, 0, 0)
7430 if (!state.LeftIsDown())
7431#else
7432 if (!state.LeftDown())
7433#endif
7434 bft = false;
7435 }
7436 }
7437 if ((bft) && !pPanTimer->IsRunning()) {
7438 PanCanvas(pan_x, pan_y);
7439 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7440 return true;
7441 }
7442
7443 // This mouse event must not be due to pan timer event injector
7444 // Mouse is out of the pan zone, so prevent any orphan event injection
7445 if ((!bft) && pPanTimer->IsRunning()) {
7446 pPanTimer->Stop();
7447 }
7448
7449 return (false);
7450}
7451
7452// Look for waypoints at the current position.
7453// Used to determine what a mouse event should act on.
7454
7455void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7456 bool setBeingEdited) {
7457 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7458 m_pRoutePointEditTarget = NULL;
7459 m_pFoundPoint = NULL;
7460
7461 SelectItem *pFind = NULL;
7462 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7463 SelectableItemList SelList = pSelect->FindSelectionList(
7464 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7465 for (SelectItem *pFind : SelList) {
7466 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7467
7468 // Get an array of all routes using this point
7469 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7470 // TODO: delete m_pEditRouteArray after use?
7471
7472 // Use route array to determine actual visibility for the point
7473 bool brp_viz = false;
7474 if (m_pEditRouteArray) {
7475 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7476 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7477 if (pr->IsVisible()) {
7478 brp_viz = true;
7479 break;
7480 }
7481 }
7482 } else
7483 brp_viz = frp->IsVisible(); // isolated point
7484
7485 if (brp_viz) {
7486 // Use route array to rubberband all affected routes
7487 if (m_pEditRouteArray) // Editing Waypoint as part of route
7488 {
7489 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7490 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7491 pr->m_bIsBeingEdited = setBeingEdited;
7492 }
7493 m_bRouteEditing = setBeingEdited;
7494 } else // editing Mark
7495 {
7496 frp->m_bRPIsBeingEdited = setBeingEdited;
7497 m_bMarkEditing = setBeingEdited;
7498 }
7499
7500 m_pRoutePointEditTarget = frp;
7501 m_pFoundPoint = pFind;
7502 break; // out of the while(node)
7503 }
7504 } // for (SelectItem...
7505}
7506std::shared_ptr<HostApi121::PiPointContext>
7507ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7508 // General Right Click
7509 // Look for selectable objects
7510 double slat, slon;
7511 GetCanvasPixPoint(x, y, slat, slon);
7512
7513 SelectItem *pFindAIS;
7514 SelectItem *pFindRP;
7515 SelectItem *pFindRouteSeg;
7516 SelectItem *pFindTrackSeg;
7517 SelectItem *pFindCurrent = NULL;
7518 SelectItem *pFindTide = NULL;
7519
7520 // Get all the selectable things at the selected point
7521 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7522 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7523 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7524 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7525 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7526
7527 if (m_bShowCurrent)
7528 pFindCurrent =
7529 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7530
7531 if (m_bShowTide) // look for tide stations
7532 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7533
7534 int seltype = 0;
7535
7536 // Try for AIS targets first
7537 int FoundAIS_MMSI = 0;
7538 if (pFindAIS) {
7539 FoundAIS_MMSI = pFindAIS->GetUserData();
7540
7541 // Make sure the target data is available
7542 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7543 seltype |= SELTYPE_AISTARGET;
7544 }
7545
7546 // Now the various Route Parts
7547
7548 RoutePoint *FoundRoutePoint = NULL;
7549 Route *SelectedRoute = NULL;
7550
7551 if (pFindRP) {
7552 RoutePoint *pFirstVizPoint = NULL;
7553 RoutePoint *pFoundActiveRoutePoint = NULL;
7554 RoutePoint *pFoundVizRoutePoint = NULL;
7555 Route *pSelectedActiveRoute = NULL;
7556 Route *pSelectedVizRoute = NULL;
7557
7558 // There is at least one routepoint, so get the whole list
7559 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7560 SelectableItemList SelList =
7561 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7562 for (SelectItem *pFindSel : SelList) {
7563 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7564
7565 // Get an array of all routes using this point
7566 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7567
7568 // Use route array (if any) to determine actual visibility for this point
7569 bool brp_viz = false;
7570 if (proute_array) {
7571 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7572 Route *pr = (Route *)proute_array->Item(ir);
7573 if (pr->IsVisible()) {
7574 brp_viz = true;
7575 break;
7576 }
7577 }
7578 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7579 // but still exists as a waypoint
7580 brp_viz = prp->IsVisible(); // so treat as isolated point
7581
7582 } else
7583 brp_viz = prp->IsVisible(); // isolated point
7584
7585 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7586
7587 // Use route array to choose the appropriate route
7588 // Give preference to any active route, otherwise select the first visible
7589 // route in the array for this point
7590 if (proute_array) {
7591 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7592 Route *pr = (Route *)proute_array->Item(ir);
7593 if (pr->m_bRtIsActive) {
7594 pSelectedActiveRoute = pr;
7595 pFoundActiveRoutePoint = prp;
7596 break;
7597 }
7598 }
7599
7600 if (NULL == pSelectedVizRoute) {
7601 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7602 Route *pr = (Route *)proute_array->Item(ir);
7603 if (pr->IsVisible()) {
7604 pSelectedVizRoute = pr;
7605 pFoundVizRoutePoint = prp;
7606 break;
7607 }
7608 }
7609 }
7610
7611 delete proute_array;
7612 }
7613 }
7614
7615 // Now choose the "best" selections
7616 if (pFoundActiveRoutePoint) {
7617 FoundRoutePoint = pFoundActiveRoutePoint;
7618 SelectedRoute = pSelectedActiveRoute;
7619 } else if (pFoundVizRoutePoint) {
7620 FoundRoutePoint = pFoundVizRoutePoint;
7621 SelectedRoute = pSelectedVizRoute;
7622 } else
7623 // default is first visible point in list
7624 FoundRoutePoint = pFirstVizPoint;
7625
7626 if (SelectedRoute) {
7627 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7628 } else if (FoundRoutePoint) {
7629 seltype |= SELTYPE_MARKPOINT;
7630 }
7631
7632 // Highlight the selected point, to verify the proper right click selection
7633#if 0
7634 if (m_pFoundRoutePoint) {
7635 m_pFoundRoutePoint->m_bPtIsSelected = true;
7636 wxRect wp_rect;
7637 RoutePointGui(*m_pFoundRoutePoint)
7638 .CalculateDCRect(m_dc_route, this, &wp_rect);
7639 RefreshRect(wp_rect, true);
7640 }
7641#endif
7642 }
7643
7644 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7645 // routes But call the popup handler with identifier appropriate to the type
7646 if (pFindRouteSeg) // there is at least one select item
7647 {
7648 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7649 SelectableItemList SelList =
7650 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7651
7652 if (NULL == SelectedRoute) // the case where a segment only is selected
7653 {
7654 // Choose the first visible route containing segment in the list
7655 for (SelectItem *pFindSel : SelList) {
7656 Route *pr = (Route *)pFindSel->m_pData3;
7657 if (pr->IsVisible()) {
7658 SelectedRoute = pr;
7659 break;
7660 }
7661 }
7662 }
7663
7664 if (SelectedRoute) {
7665 if (NULL == FoundRoutePoint)
7666 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7667
7668 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7669 seltype |= SELTYPE_ROUTESEGMENT;
7670 }
7671 }
7672
7673 if (pFindTrackSeg) {
7674 m_pSelectedTrack = NULL;
7675 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7676 SelectableItemList SelList =
7677 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7678
7679 // Choose the first visible track containing segment in the list
7680 for (SelectItem *pFindSel : SelList) {
7681 Track *pt = (Track *)pFindSel->m_pData3;
7682 if (pt->IsVisible()) {
7683 m_pSelectedTrack = pt;
7684 break;
7685 }
7686 }
7687 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7688 }
7689
7690 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7691
7692 // Populate the return struct
7693 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7694 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7695 rstruct->object_ident = "";
7696
7697 if (seltype == SELTYPE_AISTARGET) {
7698 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7699 wxString val;
7700 val.Printf("%d", FoundAIS_MMSI);
7701 rstruct->object_ident = val.ToStdString();
7702 } else if (seltype & SELTYPE_MARKPOINT) {
7703 if (FoundRoutePoint) {
7704 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7705 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7706 }
7707 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7708 if (SelectedRoute) {
7709 rstruct->object_type =
7710 HostApi121::PiContextObjectType::kObjectRoutesegment;
7711 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7712 }
7713 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7714 if (m_pSelectedTrack) {
7715 rstruct->object_type =
7716 HostApi121::PiContextObjectType::kObjectTracksegment;
7717 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7718 }
7719 }
7720
7721 return rstruct;
7722}
7723
7724void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7725 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7726 singleClickEventIsValid = false;
7727 m_DoubleClickTimer->Stop();
7728}
7729
7730bool leftIsDown;
7731
7732bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7733 if (!m_bChartDragging && !m_bDrawingRoute) {
7734 /*
7735 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7736 * mouse event coordinates are in logical pixels.
7737 */
7738 if (m_Compass && m_Compass->IsShown()) {
7739 wxRect logicalRect = m_Compass->GetLogicalRect();
7740 bool isInCompass = logicalRect.Contains(event.GetPosition());
7741 if (isInCompass || m_mouseWasInCompass) {
7742 if (m_Compass->MouseEvent(event)) {
7743 cursor_region = CENTER;
7744 if (!g_btouch) SetCanvasCursor(event);
7745 m_mouseWasInCompass = isInCompass;
7746 return true;
7747 }
7748 }
7749 m_mouseWasInCompass = isInCompass;
7750 }
7751
7752 if (m_notification_button && m_notification_button->IsShown()) {
7753 wxRect logicalRect = m_notification_button->GetLogicalRect();
7754 bool isinButton = logicalRect.Contains(event.GetPosition());
7755 if (isinButton) {
7756 SetCursor(*pCursorArrow);
7757 if (event.LeftDown()) HandleNotificationMouseClick();
7758 return true;
7759 }
7760 }
7761
7762 if (MouseEventToolbar(event)) return true;
7763
7764 if (MouseEventChartBar(event)) return true;
7765
7766 if (MouseEventMUIBar(event)) return true;
7767
7768 if (MouseEventIENCBar(event)) return true;
7769 }
7770 return false;
7771}
7772
7773void ChartCanvas::HandleNotificationMouseClick() {
7774 if (!m_NotificationsList) {
7775 m_NotificationsList = new NotificationsList(this);
7776
7777 // calculate best size for Notification list
7778 m_NotificationsList->RecalculateSize();
7779 m_NotificationsList->Hide();
7780 }
7781
7782 if (m_NotificationsList->IsShown()) {
7783 m_NotificationsList->Hide();
7784 } else {
7785 m_NotificationsList->RecalculateSize();
7786 m_NotificationsList->ReloadNotificationList();
7787 m_NotificationsList->Show();
7788 }
7789}
7790bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7791 if (!g_bShowChartBar) return false;
7792
7793 if (!m_Piano->MouseEvent(event)) return false;
7794
7795 cursor_region = CENTER;
7796 if (!g_btouch) SetCanvasCursor(event);
7797 return true;
7798}
7799
7800bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7801 if (!IsPrimaryCanvas()) return false;
7802
7803 if (g_MainToolbar) {
7804 if (!g_MainToolbar->MouseEvent(event))
7805 return false;
7806 else
7807 g_MainToolbar->RefreshToolbar();
7808 }
7809
7810 cursor_region = CENTER;
7811 if (!g_btouch) SetCanvasCursor(event);
7812 return true;
7813}
7814
7815bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7816 if (!IsPrimaryCanvas()) return false;
7817
7818 if (g_iENCToolbar) {
7819 if (!g_iENCToolbar->MouseEvent(event))
7820 return false;
7821 else {
7822 g_iENCToolbar->RefreshToolbar();
7823 return true;
7824 }
7825 }
7826 return false;
7827}
7828
7829bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7830 if (m_muiBar) {
7831 if (!m_muiBar->MouseEvent(event)) return false;
7832 }
7833
7834 cursor_region = CENTER;
7835 if (!g_btouch) SetCanvasCursor(event);
7836 if (m_muiBar)
7837 return true;
7838 else
7839 return false;
7840}
7841
7842bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7843 int x, y;
7844
7845 bool bret = false;
7846
7847 event.GetPosition(&x, &y);
7848
7849 x *= m_displayScale;
7850 y *= m_displayScale;
7851
7852 m_MouseDragging = event.Dragging();
7853
7854 // Some systems produce null drag events, where the pointer position has not
7855 // changed from the previous value. Detect this case, and abort further
7856 // processing (FS#1748)
7857#ifdef __WXMSW__
7858 if (event.Dragging()) {
7859 if ((x == mouse_x) && (y == mouse_y)) return true;
7860 }
7861#endif
7862
7863 mouse_x = x;
7864 mouse_y = y;
7865 mouse_leftisdown = event.LeftDown();
7867
7868 // Establish the event region
7869 cursor_region = CENTER;
7870
7871 int chartbar_height = GetChartbarHeight();
7872
7873 if (m_Compass && m_Compass->IsShown() &&
7874 m_Compass->GetRect().Contains(event.GetPosition())) {
7875 cursor_region = CENTER;
7876 } else if (x > xr_margin) {
7877 cursor_region = MID_RIGHT;
7878 } else if (x < xl_margin) {
7879 cursor_region = MID_LEFT;
7880 } else if (y > yb_margin - chartbar_height &&
7881 y < m_canvas_height - chartbar_height) {
7882 cursor_region = MID_TOP;
7883 } else if (y < yt_margin) {
7884 cursor_region = MID_BOT;
7885 } else {
7886 cursor_region = CENTER;
7887 }
7888
7889 if (!g_btouch) SetCanvasCursor(event);
7890
7891 // Protect from leftUp's coming from event handlers in child
7892 // windows who return focus to the canvas.
7893 leftIsDown = event.LeftDown();
7894
7895#ifndef __WXOSX__
7896 if (event.LeftDown()) {
7897 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7898 // The menu bar is temporarily visible due to alt having been pressed.
7899 // Clicking will hide it, and do nothing else.
7900 g_bTempShowMenuBar = false;
7901 top_frame::Get()->ApplyGlobalSettings(false);
7902 return (true);
7903 }
7904 }
7905#endif
7906
7907 // Update modifiers here; some window managers never send the key event
7908 m_modkeys = 0;
7909 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7910 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7911
7912#ifdef __WXMSW__
7913 // TODO Test carefully in other platforms, remove ifdef....
7914 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7915 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7916#endif
7917
7918 event.SetEventObject(this);
7919 if (SendMouseEventToPlugins(event))
7920 return (true); // PlugIn did something, and does not want the canvas to
7921 // do anything else
7922
7923 // Capture LeftUp's and time them, unless it already came from the timer.
7924
7925 // Detect end of chart dragging
7926 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7927 StartChartDragInertia();
7928 }
7929
7930 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7931 !singleClickEventIsValid) {
7932 // Ignore the second LeftUp after the DClick.
7933 if (m_DoubleClickTimer->IsRunning()) {
7934 m_DoubleClickTimer->Stop();
7935 return (true);
7936 }
7937
7938 // Save the event for later running if there is no DClick.
7939 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7940 singleClickEvent = event;
7941 singleClickEventIsValid = true;
7942 return (true);
7943 }
7944
7945 // This logic is necessary on MSW to handle the case where
7946 // a context (right-click) menu is dismissed without action
7947 // by clicking on the chart surface.
7948 // We need to avoid an unintentional pan by eating some clicks...
7949#ifdef __WXMSW__
7950 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7951 if (g_click_stop > 0) {
7952 g_click_stop--;
7953 return (true);
7954 }
7955 }
7956#endif
7957
7958 // Kick off the Rotation control timer
7959 if (GetUpMode() == COURSE_UP_MODE) {
7960 m_b_rot_hidef = false;
7961 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7962 } else
7963 pRotDefTimer->Stop();
7964
7965 // Retrigger the route leg / AIS target popup timer
7966 bool bRoll = !g_btouch;
7967#ifdef __ANDROID__
7968 bRoll = g_bRollover;
7969#endif
7970 if (bRoll) {
7971 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7972 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7973 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7974 m_RolloverPopupTimer.Start(
7975 10,
7976 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7977 else
7978 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7979 }
7980
7981 // Retrigger the cursor tracking timer
7982 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7983
7984// Show cursor position on Status Bar, if present
7985// except for GTK, under which status bar updates are very slow
7986// due to Update() call.
7987// In this case, as a workaround, update the status window
7988// after an interval timer (pCurTrackTimer) pops, which will happen
7989// whenever the mouse has stopped moving for specified interval.
7990// See the method OnCursorTrackTimerEvent()
7991#if !defined(__WXGTK__) && !defined(__WXQT__)
7992 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7993#endif
7994
7995 // Send the current cursor lat/lon to all PlugIns requesting it
7996 if (g_pi_manager) {
7997 // Occasionally, MSW will produce nonsense events on right click....
7998 // This results in an error in cursor geo position, so we skip this case
7999 if ((x >= 0) && (y >= 0))
8000 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8001 }
8002
8003 if (!g_btouch) {
8004 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8005 wxPoint p = ClientToScreen(wxPoint(x, y));
8006 }
8007 }
8008
8009 if (1 ) {
8010 // Route Creation Rubber Banding
8011 if (m_routeState >= 2) {
8012 r_rband.x = x;
8013 r_rband.y = y;
8014 m_bDrawingRoute = true;
8015
8016 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8017 Refresh(false);
8018 }
8019
8020 // Measure Tool Rubber Banding
8021 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8022 r_rband.x = x;
8023 r_rband.y = y;
8024 m_bDrawingRoute = true;
8025
8026 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8027 Refresh(false);
8028 }
8029 }
8030 return bret;
8031}
8032
8033int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8034 // On general Right Click
8035 // Look for selectable objects
8036 double slat = lat;
8037 double slon = lon;
8038
8039#if defined(__WXMAC__) || defined(__ANDROID__)
8040 wxScreenDC sdc;
8041 ocpnDC dc(sdc);
8042#else
8043 wxClientDC cdc(GetParent());
8044 ocpnDC dc(cdc);
8045#endif
8046
8047 SelectItem *pFindAIS;
8048 SelectItem *pFindRP;
8049 SelectItem *pFindRouteSeg;
8050 SelectItem *pFindTrackSeg;
8051 SelectItem *pFindCurrent = NULL;
8052 SelectItem *pFindTide = NULL;
8053
8054 // Deselect any current objects
8055 if (m_pSelectedRoute) {
8056 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8057 m_pSelectedRoute->DeSelectRoute();
8058#ifdef ocpnUSE_GL
8059 if (g_bopengl && m_glcc) {
8060 InvalidateGL();
8061 Update();
8062 } else
8063#endif
8064 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8065 }
8066
8067 if (m_pFoundRoutePoint) {
8068 m_pFoundRoutePoint->m_bPtIsSelected = false;
8069 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8070 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8071 }
8072
8075 if (g_btouch && m_pRoutePointEditTarget) {
8076 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8077 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8078 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8079 }
8080
8081 // Get all the selectable things at the cursor
8082 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8083 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8084 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8085 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8086 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8087
8088 if (m_bShowCurrent)
8089 pFindCurrent =
8090 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8091
8092 if (m_bShowTide) // look for tide stations
8093 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8094
8095 int seltype = 0;
8096
8097 // Try for AIS targets first
8098 if (pFindAIS) {
8099 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8100
8101 // Make sure the target data is available
8102 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8103 seltype |= SELTYPE_AISTARGET;
8104 }
8105
8106 // Now examine the various Route parts
8107
8108 m_pFoundRoutePoint = NULL;
8109 if (pFindRP) {
8110 RoutePoint *pFirstVizPoint = NULL;
8111 RoutePoint *pFoundActiveRoutePoint = NULL;
8112 RoutePoint *pFoundVizRoutePoint = NULL;
8113 Route *pSelectedActiveRoute = NULL;
8114 Route *pSelectedVizRoute = NULL;
8115
8116 // There is at least one routepoint, so get the whole list
8117 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8118 SelectableItemList SelList =
8119 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8120 for (SelectItem *pFindSel : SelList) {
8121 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8122
8123 // Get an array of all routes using this point
8124 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8125
8126 // Use route array (if any) to determine actual visibility for this point
8127 bool brp_viz = false;
8128 if (proute_array) {
8129 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8130 Route *pr = (Route *)proute_array->Item(ir);
8131 if (pr->IsVisible()) {
8132 brp_viz = true;
8133 break;
8134 }
8135 }
8136 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8137 // but still exists as a waypoint
8138 brp_viz = prp->IsVisible(); // so treat as isolated point
8139
8140 } else
8141 brp_viz = prp->IsVisible(); // isolated point
8142
8143 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8144
8145 // Use route array to choose the appropriate route
8146 // Give preference to any active route, otherwise select the first visible
8147 // route in the array for this point
8148 m_pSelectedRoute = NULL;
8149 if (proute_array) {
8150 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8151 Route *pr = (Route *)proute_array->Item(ir);
8152 if (pr->m_bRtIsActive) {
8153 pSelectedActiveRoute = pr;
8154 pFoundActiveRoutePoint = prp;
8155 break;
8156 }
8157 }
8158
8159 if (NULL == pSelectedVizRoute) {
8160 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8161 Route *pr = (Route *)proute_array->Item(ir);
8162 if (pr->IsVisible()) {
8163 pSelectedVizRoute = pr;
8164 pFoundVizRoutePoint = prp;
8165 break;
8166 }
8167 }
8168 }
8169
8170 delete proute_array;
8171 }
8172 }
8173
8174 // Now choose the "best" selections
8175 if (pFoundActiveRoutePoint) {
8176 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8177 m_pSelectedRoute = pSelectedActiveRoute;
8178 } else if (pFoundVizRoutePoint) {
8179 m_pFoundRoutePoint = pFoundVizRoutePoint;
8180 m_pSelectedRoute = pSelectedVizRoute;
8181 } else
8182 // default is first visible point in list
8183 m_pFoundRoutePoint = pFirstVizPoint;
8184
8185 if (m_pSelectedRoute) {
8186 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8187 } else if (m_pFoundRoutePoint) {
8188 seltype |= SELTYPE_MARKPOINT;
8189 }
8190
8191 // Highlight the selected point, to verify the proper right click selection
8192 if (m_pFoundRoutePoint) {
8193 m_pFoundRoutePoint->m_bPtIsSelected = true;
8194 wxRect wp_rect;
8195 RoutePointGui(*m_pFoundRoutePoint)
8196 .CalculateDCRect(m_dc_route, this, &wp_rect);
8197 RefreshRect(wp_rect, true);
8198 }
8199 }
8200
8201 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8202 // routes But call the popup handler with identifier appropriate to the type
8203 if (pFindRouteSeg) // there is at least one select item
8204 {
8205 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8206 SelectableItemList SelList =
8207 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8208
8209 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8210 {
8211 // Choose the first visible route containing segment in the list
8212 for (SelectItem *pFindSel : SelList) {
8213 Route *pr = (Route *)pFindSel->m_pData3;
8214 if (pr->IsVisible()) {
8215 m_pSelectedRoute = pr;
8216 break;
8217 }
8218 }
8219 }
8220
8221 if (m_pSelectedRoute) {
8222 if (NULL == m_pFoundRoutePoint)
8223 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8224
8225 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8226 if (m_pSelectedRoute->m_bRtIsSelected) {
8227#ifdef ocpnUSE_GL
8228 if (g_bopengl && m_glcc) {
8229 InvalidateGL();
8230 Update();
8231 } else
8232#endif
8233 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8234 }
8235 seltype |= SELTYPE_ROUTESEGMENT;
8236 }
8237 }
8238
8239 if (pFindTrackSeg) {
8240 m_pSelectedTrack = NULL;
8241 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8242 SelectableItemList SelList =
8243 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8244
8245 // Choose the first visible track containing segment in the list
8246 for (SelectItem *pFindSel : SelList) {
8247 Track *pt = (Track *)pFindSel->m_pData3;
8248 if (pt->IsVisible()) {
8249 m_pSelectedTrack = pt;
8250 break;
8251 }
8252 }
8253 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8254 }
8255
8256#if 0 // disable tide and current graph on right click
8257 {
8258 if (pFindCurrent) {
8259 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8260 seltype |= SELTYPE_CURRENTPOINT;
8261 }
8262
8263 else if (pFindTide) {
8264 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8265 seltype |= SELTYPE_TIDEPOINT;
8266 }
8267 }
8268#endif
8269
8270 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8271
8272 return seltype;
8273}
8274
8275IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8276 // There may be multiple current entries at the same point.
8277 // For example, there often is a current substation (with directions
8278 // specified) co-located with its master. We want to select the
8279 // substation, so that the direction will be properly indicated on the
8280 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8281 // substation)
8282 IDX_entry *pIDX_best_candidate;
8283
8284 SelectItem *pFind = NULL;
8285 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8286 SelectableItemList SelList =
8287 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8288
8289 // Default is first entry
8290 pFind = *SelList.begin();
8291 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8292
8293 auto node = SelList.begin();
8294 if (SelList.size() > 1) {
8295 for (++node; node != SelList.end(); ++node) {
8296 pFind = *node;
8297 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8298 if (pIDX_candidate->IDX_type == 'c') {
8299 pIDX_best_candidate = pIDX_candidate;
8300 break;
8301 }
8302 } // while (node)
8303 } else {
8304 pFind = *SelList.begin();
8305 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8306 }
8307
8308 return pIDX_best_candidate;
8309}
8310void ChartCanvas::CallPopupMenu(int x, int y) {
8311 last_drag.x = x;
8312 last_drag.y = y;
8313 if (m_routeState) { // creating route?
8314 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8315 return;
8316 }
8317
8319
8320 // If tide or current point is selected, then show the TC dialog immediately
8321 // without context menu
8322 if (SELTYPE_CURRENTPOINT == seltype) {
8323 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8324 Refresh(false);
8325 return;
8326 }
8327
8328 if (SELTYPE_TIDEPOINT == seltype) {
8329 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8330 Refresh(false);
8331 return;
8332 }
8333
8334 InvokeCanvasMenu(x, y, seltype);
8335
8336 // Clean up if not deleted in InvokeCanvasMenu
8337 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8338 m_pSelectedRoute->m_bRtIsSelected = false;
8339 }
8340
8341 m_pSelectedRoute = NULL;
8342
8343 if (m_pFoundRoutePoint) {
8344 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8345 m_pFoundRoutePoint->m_bPtIsSelected = false;
8346 }
8347 m_pFoundRoutePoint = NULL;
8348
8349 Refresh(true);
8350 // Refresh(false); // needed for MSW, not GTK Why??
8351}
8352
8353bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8354 // For now just bail out completely if the point clicked is not on the chart
8355 if (std::isnan(m_cursor_lat)) return false;
8356
8357 // Mouse Clicks
8358 bool ret = false; // return true if processed
8359
8360 int x, y, mx, my;
8361 event.GetPosition(&x, &y);
8362 mx = x;
8363 my = y;
8364
8365 // Calculate meaningful SelectRadius
8366 float SelectRadius;
8367 SelectRadius = g_Platform->GetSelectRadiusPix() /
8368 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8369
8371 // We start with Double Click processing. The first left click just starts a
8372 // timer and is remembered, then we actually do something if there is a
8373 // LeftDClick. If there is, the two single clicks are ignored.
8374
8375 if (event.LeftDClick() && (cursor_region == CENTER)) {
8376 m_DoubleClickTimer->Start();
8377 singleClickEventIsValid = false;
8378
8379 double zlat, zlon;
8381 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8382
8383 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8384 if (m_bShowAIS) {
8385 SelectItem *pFindAIS;
8386 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8387
8388 if (pFindAIS) {
8389 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8390 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8391 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8392 }
8393 return true;
8394 }
8395 }
8396
8397 SelectableItemList rpSelList =
8398 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8399 bool b_onRPtarget = false;
8400 for (SelectItem *pFind : rpSelList) {
8401 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8402 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8403 b_onRPtarget = true;
8404 break;
8405 }
8406 }
8407
8408 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8409
8410 // Get and honor the plugin API ContextMenuMask
8411 std::unique_ptr<HostApi> host_api = GetHostApi();
8412 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8413
8414 if (m_pRoutePointEditTarget) {
8415 if (b_onRPtarget) {
8416 if ((api_121->GetContextMenuMask() &
8417 api_121->kContextMenuDisableWaypoint))
8418 return true;
8419 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8420 return true;
8421 } else {
8422 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8423 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8424 if (g_btouch)
8425 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8426 wxRect wp_rect;
8427 RoutePointGui(*m_pRoutePointEditTarget)
8428 .CalculateDCRect(m_dc_route, this, &wp_rect);
8429 m_pRoutePointEditTarget = NULL; // cancel selection
8430 RefreshRect(wp_rect, true);
8431 return true;
8432 }
8433 } else {
8434 auto node = rpSelList.begin();
8435 if (node != rpSelList.end()) {
8436 SelectItem *pFind = *node;
8437 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8438 if (frp) {
8439 wxArrayPtrVoid *proute_array =
8441
8442 // Use route array (if any) to determine actual visibility for this
8443 // point
8444 bool brp_viz = false;
8445 if (proute_array) {
8446 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8447 Route *pr = (Route *)proute_array->Item(ir);
8448 if (pr->IsVisible()) {
8449 brp_viz = true;
8450 break;
8451 }
8452 }
8453 delete proute_array;
8454 if (!brp_viz &&
8455 frp->IsShared()) // is not visible as part of route, but
8456 // still exists as a waypoint
8457 brp_viz = frp->IsVisible(); // so treat as isolated point
8458 } else
8459 brp_viz = frp->IsVisible(); // isolated point
8460
8461 if (brp_viz) {
8462 if ((api_121->GetContextMenuMask() &
8463 api_121->kContextMenuDisableWaypoint))
8464 return true;
8465
8466 ShowMarkPropertiesDialog(frp);
8467 return true;
8468 }
8469 }
8470 }
8471 }
8472
8473 SelectItem *cursorItem;
8474
8475 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8476 if (cursorItem) {
8477 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8478 return true;
8479 Route *pr = (Route *)cursorItem->m_pData3;
8480 if (pr->IsVisible()) {
8481 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8482 return true;
8483 }
8484 }
8485
8486 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8487 if (cursorItem) {
8488 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8489 return true;
8490 Track *pt = (Track *)cursorItem->m_pData3;
8491 if (pt->IsVisible()) {
8492 ShowTrackPropertiesDialog(pt);
8493 return true;
8494 }
8495 }
8496
8497 // Tide and current points
8498 SelectItem *pFindCurrent = NULL;
8499 SelectItem *pFindTide = NULL;
8500
8501 if (m_bShowCurrent) { // look for current stations
8502 pFindCurrent =
8503 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8504 if (pFindCurrent) {
8505 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8506 // Check for plugin graphic override
8507 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8508 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8509 PlugInContainer *pic = plugin_array->Item(i);
8510 if (pic->m_enabled && pic->m_init_state &&
8511 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8512 if (ptcmgr) {
8513 TCClickInfo info;
8514 if (m_pIDXCandidate) {
8515 info.point_type = CURRENT_STATION;
8516 info.index = m_pIDXCandidate->IDX_rec_num;
8517 info.name = m_pIDXCandidate->IDX_station_name;
8518 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8519 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8520 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8521 };
8522 auto plugin =
8523 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8524 if (plugin) plugin->OnTideCurrentClick(info);
8525 return true;
8526 }
8527 }
8528 }
8529 }
8530
8531 // Default action
8532 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8533 Refresh(false);
8534 return true;
8535 }
8536 }
8537
8538 if (m_bShowTide) { // look for tide stations
8539 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8540 if (pFindTide) {
8541 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8542 // Check for plugin graphic override
8543 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8544 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8545 PlugInContainer *pic = plugin_array->Item(i);
8546 if (pic->m_enabled && pic->m_init_state &&
8547 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8548 if (ptcmgr) {
8549 TCClickInfo info;
8550 if (m_pIDXCandidate) {
8551 info.point_type = TIDE_STATION;
8552 info.index = m_pIDXCandidate->IDX_rec_num;
8553 info.name = m_pIDXCandidate->IDX_station_name;
8554 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8555 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8556 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8557 };
8558 auto plugin =
8559 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8560 if (plugin) plugin->OnTideCurrentClick(info);
8561 return true;
8562 }
8563 }
8564 }
8565 }
8566
8567 // Default action
8568 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8569 Refresh(false);
8570 return true;
8571 }
8572 }
8573
8574 // Found no object to act on, so show chart info.
8575 ShowObjectQueryWindow(x, y, zlat, zlon);
8576 return true;
8577 }
8578
8580 if (event.LeftDown()) {
8581 // This really should not be needed, but....
8582 // on Windows, when using wxAUIManager, sometimes the focus is lost
8583 // when clicking into another pane, e.g.the AIS target list, and then back
8584 // to this pane. Oddly, some mouse events are not lost, however. Like this
8585 // one....
8586 SetFocus();
8587
8588 last_drag.x = mx;
8589 last_drag.y = my;
8590 leftIsDown = true;
8591
8592 if (!g_btouch) {
8593 if (m_routeState) // creating route?
8594 {
8595 double rlat, rlon;
8596 bool appending = false;
8597 bool inserting = false;
8598 Route *tail = 0;
8599
8600 SetCursor(*pCursorPencil);
8601 rlat = m_cursor_lat;
8602 rlon = m_cursor_lon;
8603
8604 m_bRouteEditing = true;
8605
8606 if (m_routeState == 1) {
8607 m_pMouseRoute = new Route();
8608 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8609 pRouteList->push_back(m_pMouseRoute);
8610 r_rband.x = x;
8611 r_rband.y = y;
8612 }
8613
8614 // Check to see if there is a nearby point which may be reused
8615 RoutePoint *pMousePoint = NULL;
8616
8617 // Calculate meaningful SelectRadius
8618 double nearby_radius_meters =
8619 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8620
8621 RoutePoint *pNearbyPoint =
8622 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8623 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8624 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8625 wxArrayPtrVoid *proute_array =
8626 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8627
8628 // Use route array (if any) to determine actual visibility for this
8629 // point
8630 bool brp_viz = false;
8631 if (proute_array) {
8632 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8633 Route *pr = (Route *)proute_array->Item(ir);
8634 if (pr->IsVisible()) {
8635 brp_viz = true;
8636 break;
8637 }
8638 }
8639 delete proute_array;
8640 if (!brp_viz &&
8641 pNearbyPoint->IsShared()) // is not visible as part of route,
8642 // but still exists as a waypoint
8643 brp_viz =
8644 pNearbyPoint->IsVisible(); // so treat as isolated point
8645 } else
8646 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8647
8648 if (brp_viz) {
8649 wxString msg = _("Use nearby waypoint?");
8650 // Don't add a mark without name to the route. Name it if needed
8651 const bool noname(pNearbyPoint->GetName() == "");
8652 if (noname) {
8653 msg =
8654 _("Use nearby nameless waypoint and name it M with"
8655 " a unique number?");
8656 }
8657 // Avoid route finish on focus change for message dialog
8658 m_FinishRouteOnKillFocus = false;
8659 int dlg_return =
8660 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8661 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8662 m_FinishRouteOnKillFocus = true;
8663 if (dlg_return == wxID_YES) {
8664 if (noname) {
8665 if (m_pMouseRoute) {
8666 int last_wp_num = m_pMouseRoute->GetnPoints();
8667 // AP-ECRMB will truncate to 6 characters
8668 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8669 wxString wp_name = wxString::Format(
8670 "M%002i-%s", last_wp_num + 1, guid_short);
8671 pNearbyPoint->SetName(wp_name);
8672 } else
8673 pNearbyPoint->SetName("WPXX");
8674 }
8675 pMousePoint = pNearbyPoint;
8676
8677 // Using existing waypoint, so nothing to delete for undo.
8678 if (m_routeState > 1)
8679 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8680 Undo_HasParent, NULL);
8681
8682 tail =
8683 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8684 bool procede = false;
8685 if (tail) {
8686 procede = true;
8687 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8688 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8689 procede = false;
8690 }
8691
8692 if (procede) {
8693 int dlg_return;
8694 m_FinishRouteOnKillFocus = false;
8695 if (m_routeState ==
8696 1) { // first point in new route, preceeding route to be
8697 // added? Not touch case
8698
8699 wxString dmsg =
8700 _("Insert first part of this route in the new route?");
8701 if (tail->GetIndexOf(pMousePoint) ==
8702 tail->GetnPoints()) // Starting on last point of another
8703 // route?
8704 dmsg = _("Insert this route in the new route?");
8705
8706 if (tail->GetIndexOf(pMousePoint) > 0) { // Anything to do?
8707 dlg_return = OCPNMessageBox(
8708 this, dmsg, _("OpenCPN Route Create"),
8709 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8710 m_FinishRouteOnKillFocus = true;
8711
8712 if (dlg_return == wxID_YES) {
8713 inserting = true; // part of the other route will be
8714 // preceeding the new route
8715 }
8716 }
8717 } else {
8718 wxString dmsg =
8719 _("Append last part of this route to the new route?");
8720 if (tail->GetIndexOf(pMousePoint) == 1)
8721 dmsg = _(
8722 "Append this route to the new route?"); // Picking the
8723 // first point
8724 // of another
8725 // route?
8726
8727 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8728 dlg_return = OCPNMessageBox(
8729 this, dmsg, _("OpenCPN Route Create"),
8730 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8731 m_FinishRouteOnKillFocus = true;
8732
8733 if (dlg_return == wxID_YES) {
8734 appending = true; // part of the other route will be
8735 // appended to the new route
8736 }
8737 }
8738 }
8739 }
8740
8741 // check all other routes to see if this point appears in any
8742 // other route If it appears in NO other route, then it should e
8743 // considered an isolated mark
8744 if (!FindRouteContainingWaypoint(pMousePoint))
8745 pMousePoint->SetShared(true);
8746 }
8747 }
8748 }
8749
8750 if (NULL == pMousePoint) { // need a new point
8751 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8752 "", wxEmptyString);
8753 pMousePoint->SetNameShown(false);
8754
8755 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8756
8757 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8758
8759 if (m_routeState > 1)
8760 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8761 Undo_IsOrphanded, NULL);
8762 }
8763
8764 if (m_pMouseRoute) {
8765 if (m_routeState == 1) {
8766 // First point in the new route.
8767 m_pMouseRoute->AddPoint(pMousePoint);
8768 } else {
8769 if (m_pMouseRoute->m_NextLegGreatCircle) {
8770 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8771 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8772 &rhumbBearing, &rhumbDist);
8773 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8774 rlat, &gcDist, &gcBearing, NULL);
8775 double gcDistNM = gcDist / 1852.0;
8776
8777 // Empirically found expression to get reasonable route segments.
8778 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8779 pow(rhumbDist - gcDistNM - 1, 0.5);
8780
8781 wxString msg;
8782 msg << _("For this leg the Great Circle route is ")
8783 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8784 << _(" shorter than rhumbline.\n\n")
8785 << _("Would you like include the Great Circle routing points "
8786 "for this leg?");
8787
8788 m_FinishRouteOnKillFocus = false;
8789 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8790 // does not fully capture mouse
8791
8792 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8793 wxYES_NO | wxNO_DEFAULT);
8794
8795 m_disable_edge_pan = false;
8796 m_FinishRouteOnKillFocus = true;
8797
8798 if (answer == wxID_YES) {
8799 RoutePoint *gcPoint;
8800 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8801 wxRealPoint gcCoord;
8802
8803 for (int i = 1; i <= segmentCount; i++) {
8804 double fraction = (double)i * (1.0 / (double)segmentCount);
8805 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8806 gcDist * fraction, gcBearing,
8807 &gcCoord.x, &gcCoord.y, NULL);
8808
8809 if (i < segmentCount) {
8810 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8811 wxEmptyString);
8812 gcPoint->SetNameShown(false);
8813 // pConfig->AddNewWayPoint(gcPoint, -1);
8814 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8815
8816 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8817 gcPoint);
8818 } else {
8819 gcPoint = pMousePoint; // Last point, previously exsisting!
8820 }
8821
8822 m_pMouseRoute->AddPoint(gcPoint);
8823 pSelect->AddSelectableRouteSegment(
8824 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8825 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8826 prevGcPoint = gcPoint;
8827 }
8828
8829 undo->CancelUndoableAction(true);
8830
8831 } else {
8832 m_pMouseRoute->AddPoint(pMousePoint);
8833 pSelect->AddSelectableRouteSegment(
8834 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8835 pMousePoint, m_pMouseRoute);
8836 undo->AfterUndoableAction(m_pMouseRoute);
8837 }
8838 } else {
8839 // Ordinary rhumblinesegment.
8840 m_pMouseRoute->AddPoint(pMousePoint);
8841 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8842 rlon, m_prev_pMousePoint,
8843 pMousePoint, m_pMouseRoute);
8844 undo->AfterUndoableAction(m_pMouseRoute);
8845 }
8846 }
8847 }
8848 m_prev_rlat = rlat;
8849 m_prev_rlon = rlon;
8850 m_prev_pMousePoint = pMousePoint;
8851 if (m_pMouseRoute)
8852 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8853
8854 m_routeState++;
8855
8856 if (appending ||
8857 inserting) { // Appending a route or making a new route
8858 int connect = tail->GetIndexOf(pMousePoint);
8859 if (connect == 0) {
8860 inserting = false; // there is nothing to insert
8861 appending = true; // so append
8862 }
8863 int length = tail->GetnPoints();
8864
8865 int i;
8866 int start, stop;
8867 if (appending) {
8868 start = connect + 1;
8869 stop = length;
8870 } else { // inserting
8871 start = 1;
8872 stop = connect + 1;
8873 // Remove the first and only point of the new route
8874 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8875 }
8876 for (i = start; i <= stop; i++) {
8877 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8878 if (m_pMouseRoute)
8879 m_pMouseRoute->m_lastMousePointIndex =
8880 m_pMouseRoute->GetnPoints();
8881 m_routeState++;
8882 top_frame::Get()->RefreshAllCanvas();
8883 ret = true;
8884 }
8885 m_prev_rlat =
8886 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8887 m_prev_rlon =
8888 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8889 m_pMouseRoute->FinalizeForRendering();
8890 }
8891 top_frame::Get()->RefreshAllCanvas();
8892 ret = true;
8893 }
8894
8895 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8896 {
8897 SetCursor(*pCursorPencil);
8898
8899 if (!m_pMeasureRoute) {
8900 m_pMeasureRoute = new Route();
8901 pRouteList->push_back(m_pMeasureRoute);
8902 }
8903
8904 if (m_nMeasureState == 1) {
8905 r_rband.x = x;
8906 r_rband.y = y;
8907 }
8908
8909 RoutePoint *pMousePoint =
8910 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8911 wxEmptyString, wxEmptyString);
8912 pMousePoint->m_bShowName = false;
8913 pMousePoint->SetShowWaypointRangeRings(false);
8914
8915 m_pMeasureRoute->AddPoint(pMousePoint);
8916
8917 m_prev_rlat = m_cursor_lat;
8918 m_prev_rlon = m_cursor_lon;
8919 m_prev_pMousePoint = pMousePoint;
8920 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8921
8922 m_nMeasureState++;
8923 top_frame::Get()->RefreshAllCanvas();
8924 ret = true;
8925 }
8926
8927 else {
8928 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8929 }
8930 } // !g_btouch
8931 else { // g_btouch
8932 m_last_touch_down_pos = event.GetPosition();
8933
8934 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8935 // if near screen edge, pan with injection
8936 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8937 // return;
8938 // }
8939 }
8940 }
8941
8942 if (ret) return true;
8943 }
8944
8945 if (event.Dragging()) {
8946 // in touch screen mode ensure the finger/cursor is on the selected point's
8947 // radius to allow dragging
8948 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8949 if (g_btouch) {
8950 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8951 SelectItem *pFind = NULL;
8952 SelectableItemList SelList = pSelect->FindSelectionList(
8953 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8954 for (SelectItem *pFind : SelList) {
8955 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8956 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8957 }
8958 }
8959
8960 // Check for use of dragHandle
8961 if (m_pRoutePointEditTarget &&
8962 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8963 SelectItem *pFind = NULL;
8964 SelectableItemList SelList = pSelect->FindSelectionList(
8965 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8966 for (SelectItem *pFind : SelList) {
8967 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8968 if (m_pRoutePointEditTarget == frp) {
8969 m_bIsInRadius = true;
8970 break;
8971 }
8972 }
8973
8974 if (!m_dragoffsetSet) {
8975 RoutePointGui(*m_pRoutePointEditTarget)
8976 .PresetDragOffset(this, mouse_x, mouse_y);
8977 m_dragoffsetSet = true;
8978 }
8979 }
8980 }
8981
8982 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8983 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8984
8985 if (NULL == g_pMarkInfoDialog) {
8986 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8987 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8988 DraggingAllowed = false;
8989
8990 if (m_pRoutePointEditTarget &&
8991 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8992 DraggingAllowed = false;
8993
8994 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8995
8996 if (DraggingAllowed) {
8997 if (!undo->InUndoableAction()) {
8998 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8999 Undo_NeedsCopy, m_pFoundPoint);
9000 }
9001
9002 // Get the update rectangle for the union of the un-edited routes
9003 wxRect pre_rect;
9004
9005 if (!g_bopengl && m_pEditRouteArray) {
9006 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9007 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9008 // Need to validate route pointer
9009 // Route may be gone due to drgging close to ownship with
9010 // "Delete On Arrival" state set, as in the case of
9011 // navigating to an isolated waypoint on a temporary route
9012 if (g_pRouteMan->IsRouteValid(pr)) {
9013 wxRect route_rect;
9014 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9015 pre_rect.Union(route_rect);
9016 }
9017 }
9018 }
9019
9020 double new_cursor_lat = m_cursor_lat;
9021 double new_cursor_lon = m_cursor_lon;
9022
9023 if (CheckEdgePan(x, y, true, 5, 2))
9024 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9025
9026 // update the point itself
9027 if (g_btouch) {
9028 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9029 // new_cursor_lat, new_cursor_lon);
9030 RoutePointGui(*m_pRoutePointEditTarget)
9031 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9032 // update the Drag Handle entry in the pSelect list
9033 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9034 m_pRoutePointEditTarget,
9035 SELTYPE_DRAGHANDLE);
9036 m_pFoundPoint->m_slat =
9037 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9038 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9039 } else {
9040 m_pRoutePointEditTarget->m_lat =
9041 new_cursor_lat; // update the RoutePoint entry
9042 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9043 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9044 m_pFoundPoint->m_slat =
9045 new_cursor_lat; // update the SelectList entry
9046 m_pFoundPoint->m_slon = new_cursor_lon;
9047 }
9048
9049 // Update the MarkProperties Dialog, if currently shown
9050 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9051 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9052 g_pMarkInfoDialog->UpdateProperties(true);
9053 }
9054
9055 if (g_bopengl) {
9056 // InvalidateGL();
9057 Refresh(false);
9058 } else {
9059 // Get the update rectangle for the edited route
9060 wxRect post_rect;
9061
9062 if (m_pEditRouteArray) {
9063 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9064 ir++) {
9065 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9066 if (g_pRouteMan->IsRouteValid(pr)) {
9067 wxRect route_rect;
9068 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9069 post_rect.Union(route_rect);
9070 }
9071 }
9072 }
9073
9074 // Invalidate the union region
9075 pre_rect.Union(post_rect);
9076 RefreshRect(pre_rect, false);
9077 }
9078 top_frame::Get()->RefreshCanvasOther(this);
9079 m_bRoutePoinDragging = true;
9080 }
9081 ret = true;
9082 } // if Route Editing
9083
9084 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9085 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9086
9087 if (NULL == g_pMarkInfoDialog) {
9088 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9089 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9090 DraggingAllowed = false;
9091
9092 if (m_pRoutePointEditTarget &&
9093 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9094 DraggingAllowed = false;
9095
9096 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9097
9098 if (DraggingAllowed) {
9099 if (!undo->InUndoableAction()) {
9100 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9101 Undo_NeedsCopy, m_pFoundPoint);
9102 }
9103
9104 // The mark may be an anchorwatch
9105 double lpp1 = 0.;
9106 double lpp2 = 0.;
9107 double lppmax;
9108
9109 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9110 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9111 }
9112 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9113 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9114 }
9115 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9116
9117 // Get the update rectangle for the un-edited mark
9118 wxRect pre_rect;
9119 if (!g_bopengl) {
9120 RoutePointGui(*m_pRoutePointEditTarget)
9121 .CalculateDCRect(m_dc_route, this, &pre_rect);
9122 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9123 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9124 (int)(lppmax - (pre_rect.height / 2)));
9125 }
9126
9127 // update the point itself
9128 if (g_btouch) {
9129 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9130 // m_cursor_lat, m_cursor_lon);
9131 RoutePointGui(*m_pRoutePointEditTarget)
9132 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9133 // update the Drag Handle entry in the pSelect list
9134 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9135 m_pRoutePointEditTarget,
9136 SELTYPE_DRAGHANDLE);
9137 m_pFoundPoint->m_slat =
9138 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9139 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9140 } else {
9141 m_pRoutePointEditTarget->m_lat =
9142 m_cursor_lat; // update the RoutePoint entry
9143 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9144 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9145 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9146 m_pFoundPoint->m_slon = m_cursor_lon;
9147 }
9148
9149 // Update the MarkProperties Dialog, if currently shown
9150 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9151 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9152 g_pMarkInfoDialog->UpdateProperties(true);
9153 }
9154
9155 // Invalidate the union region
9156 if (g_bopengl) {
9157 if (!g_btouch) InvalidateGL();
9158 Refresh(false);
9159 } else {
9160 // Get the update rectangle for the edited mark
9161 wxRect post_rect;
9162 RoutePointGui(*m_pRoutePointEditTarget)
9163 .CalculateDCRect(m_dc_route, this, &post_rect);
9164 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9165 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9166 (int)(lppmax - (post_rect.height / 2)));
9167
9168 // Invalidate the union region
9169 pre_rect.Union(post_rect);
9170 RefreshRect(pre_rect, false);
9171 }
9172 top_frame::Get()->RefreshCanvasOther(this);
9173 m_bRoutePoinDragging = true;
9174 }
9175 ret = g_btouch ? m_bRoutePoinDragging : true;
9176 }
9177
9178 if (ret) return true;
9179 } // dragging
9180
9181 if (event.LeftUp()) {
9182 bool b_startedit_route = false;
9183 m_dragoffsetSet = false;
9184
9185 if (g_btouch) {
9186 m_bChartDragging = false;
9187 m_bIsInRadius = false;
9188
9189 if (m_routeState) // creating route?
9190 {
9191 // Check displacement from touchdown to distinguish taps from pans.
9192 // On touchscreens, even a "tap" can have several pixels of jitter,
9193 // so use a generous threshold (20px) to avoid false positives.
9194 int dx = x - m_touchdownPos.x;
9195 int dy = y - m_touchdownPos.y;
9196 int dist2 = dx * dx + dy * dy;
9197 bool wasPan = (dist2 > 20 * 20);
9198
9199 if (wasPan) {
9200 return false;
9201 }
9202
9203 if (m_bedge_pan) {
9204 m_bedge_pan = false;
9205 return false;
9206 }
9207
9208 double rlat, rlon;
9209 bool appending = false;
9210 bool inserting = false;
9211 Route *tail = 0;
9212
9213 rlat = m_cursor_lat;
9214 rlon = m_cursor_lon;
9215
9216 if (m_pRoutePointEditTarget) {
9217 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9218 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9219 if (!g_bopengl) {
9220 wxRect wp_rect;
9221 RoutePointGui(*m_pRoutePointEditTarget)
9222 .CalculateDCRect(m_dc_route, this, &wp_rect);
9223 RefreshRect(wp_rect, true);
9224 }
9225 m_pRoutePointEditTarget = NULL;
9226 }
9227 m_bRouteEditing = true;
9228
9229 if (m_routeState == 1) {
9230 m_pMouseRoute = new Route();
9231 m_pMouseRoute->SetHiLite(50);
9232 pRouteList->push_back(m_pMouseRoute);
9233 r_rband.x = x;
9234 r_rband.y = y;
9235 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9236 }
9237
9238 // Check to see if there is a nearby point which may be reused
9239 RoutePoint *pMousePoint = NULL;
9240
9241 // Calculate meaningful SelectRadius
9242 double nearby_radius_meters =
9243 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9244
9245 RoutePoint *pNearbyPoint =
9246 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9247 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9248 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9249 int dlg_return;
9250#ifndef __WXOSX__
9251 m_FinishRouteOnKillFocus =
9252 false; // Avoid route finish on focus change for message dialog
9253 dlg_return = OCPNMessageBox(
9254 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9255 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9256 m_FinishRouteOnKillFocus = true;
9257#else
9258 dlg_return = wxID_YES;
9259#endif
9260 if (dlg_return == wxID_YES) {
9261 pMousePoint = pNearbyPoint;
9262
9263 // Using existing waypoint, so nothing to delete for undo.
9264 if (m_routeState > 1)
9265 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9266 Undo_HasParent, NULL);
9267 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9268
9269 bool procede = false;
9270 if (tail) {
9271 procede = true;
9272 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9273 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9274 procede = false;
9275 }
9276
9277 if (procede) {
9278 int dlg_return;
9279 m_FinishRouteOnKillFocus = false;
9280 if (m_routeState == 1) { // first point in new route, preceeding
9281 // route to be added? touch case
9282
9283 wxString dmsg =
9284 _("Insert first part of this route in the new route?");
9285 if (tail->GetIndexOf(pMousePoint) ==
9286 tail->GetnPoints()) // Starting on last point of another
9287 // route?
9288 dmsg = _("Insert this route in the new route?");
9289
9290 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9291 dlg_return =
9292 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9293 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9294 m_FinishRouteOnKillFocus = true;
9295
9296 if (dlg_return == wxID_YES) {
9297 inserting = true; // part of the other route will be
9298 // preceeding the new route
9299 }
9300 }
9301 } else {
9302 wxString dmsg =
9303 _("Append last part of this route to the new route?");
9304 if (tail->GetIndexOf(pMousePoint) == 1)
9305 dmsg = _(
9306 "Append this route to the new route?"); // Picking the
9307 // first point of
9308 // another route?
9309
9310 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9311 dlg_return =
9312 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9313 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9314 m_FinishRouteOnKillFocus = true;
9315
9316 if (dlg_return == wxID_YES) {
9317 appending = true; // part of the other route will be
9318 // appended to the new route
9319 }
9320 }
9321 }
9322 }
9323
9324 // check all other routes to see if this point appears in any other
9325 // route If it appears in NO other route, then it should e
9326 // considered an isolated mark
9327 if (!FindRouteContainingWaypoint(pMousePoint))
9328 pMousePoint->SetShared(true);
9329 }
9330 }
9331
9332 if (NULL == pMousePoint) { // need a new point
9333 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9334 "", wxEmptyString);
9335 pMousePoint->SetNameShown(false);
9336
9337 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9338
9339 if (m_routeState > 1)
9340 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9341 Undo_IsOrphanded, NULL);
9342 }
9343
9344 if (m_routeState == 1) {
9345 // First point in the route.
9346 m_pMouseRoute->AddPoint(pMousePoint);
9347 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9348
9349 } else {
9350 if (m_pMouseRoute->m_NextLegGreatCircle) {
9351 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9352 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9353 &rhumbBearing, &rhumbDist);
9354 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9355 &gcDist, &gcBearing, NULL);
9356 double gcDistNM = gcDist / 1852.0;
9357
9358 // Empirically found expression to get reasonable route segments.
9359 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9360 pow(rhumbDist - gcDistNM - 1, 0.5);
9361
9362 wxString msg;
9363 msg << _("For this leg the Great Circle route is ")
9364 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9365 << _(" shorter than rhumbline.\n\n")
9366 << _("Would you like include the Great Circle routing points "
9367 "for this leg?");
9368
9369#ifndef __WXOSX__
9370 m_FinishRouteOnKillFocus = false;
9371 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9372 wxYES_NO | wxNO_DEFAULT);
9373 m_FinishRouteOnKillFocus = true;
9374#else
9375 int answer = wxID_NO;
9376#endif
9377
9378 if (answer == wxID_YES) {
9379 RoutePoint *gcPoint;
9380 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9381 wxRealPoint gcCoord;
9382
9383 for (int i = 1; i <= segmentCount; i++) {
9384 double fraction = (double)i * (1.0 / (double)segmentCount);
9385 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9386 gcDist * fraction, gcBearing,
9387 &gcCoord.x, &gcCoord.y, NULL);
9388
9389 if (i < segmentCount) {
9390 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9391 wxEmptyString);
9392 gcPoint->SetNameShown(false);
9393 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9394 gcPoint);
9395 } else {
9396 gcPoint = pMousePoint; // Last point, previously exsisting!
9397 }
9398
9399 m_pMouseRoute->AddPoint(gcPoint);
9400 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9401
9402 pSelect->AddSelectableRouteSegment(
9403 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9404 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9405 prevGcPoint = gcPoint;
9406 }
9407
9408 undo->CancelUndoableAction(true);
9409
9410 } else {
9411 m_pMouseRoute->AddPoint(pMousePoint);
9412 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9413 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9414 rlon, m_prev_pMousePoint,
9415 pMousePoint, m_pMouseRoute);
9416 undo->AfterUndoableAction(m_pMouseRoute);
9417 }
9418 } else {
9419 // Ordinary rhumblinesegment.
9420 m_pMouseRoute->AddPoint(pMousePoint);
9421 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9422
9423 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9424 rlon, m_prev_pMousePoint,
9425 pMousePoint, m_pMouseRoute);
9426 undo->AfterUndoableAction(m_pMouseRoute);
9427 }
9428 }
9429
9430 m_prev_rlat = rlat;
9431 m_prev_rlon = rlon;
9432 m_prev_pMousePoint = pMousePoint;
9433 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9434
9435 m_routeState++;
9436
9437 if (appending ||
9438 inserting) { // Appending a route or making a new route
9439 int connect = tail->GetIndexOf(pMousePoint);
9440 if (connect == 1) {
9441 inserting = false; // there is nothing to insert
9442 appending = true; // so append
9443 }
9444 int length = tail->GetnPoints();
9445
9446 int i;
9447 int start, stop;
9448 if (appending) {
9449 start = connect + 1;
9450 stop = length;
9451 } else { // inserting
9452 start = 1;
9453 stop = connect;
9454 m_pMouseRoute->RemovePoint(
9455 m_pMouseRoute
9456 ->GetLastPoint()); // Remove the first and only point
9457 }
9458 for (i = start; i <= stop; i++) {
9459 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9460 if (m_pMouseRoute)
9461 m_pMouseRoute->m_lastMousePointIndex =
9462 m_pMouseRoute->GetnPoints();
9463 m_routeState++;
9464 top_frame::Get()->RefreshAllCanvas();
9465 ret = true;
9466 }
9467 m_prev_rlat =
9468 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9469 m_prev_rlon =
9470 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9471 m_pMouseRoute->FinalizeForRendering();
9472 }
9473
9474 Refresh(true);
9475 ret = true;
9476 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9477 {
9478 if (m_bedge_pan) {
9479 m_bedge_pan = false;
9480 return false;
9481 }
9482
9483 // Skip if user panned (same logic as route handler above)
9484 {
9485 int mdx = x - m_touchdownPos.x;
9486 int mdy = y - m_touchdownPos.y;
9487 if (mdx * mdx + mdy * mdy > 20 * 20) return false;
9488 }
9489
9490 if (m_nMeasureState == 1) {
9491 m_pMeasureRoute = new Route();
9492 pRouteList->push_back(m_pMeasureRoute);
9493 r_rband.x = x;
9494 r_rband.y = y;
9495 }
9496
9497 if (m_pMeasureRoute) {
9498 RoutePoint *pMousePoint =
9499 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9500 wxEmptyString, wxEmptyString);
9501 pMousePoint->m_bShowName = false;
9502
9503 m_pMeasureRoute->AddPoint(pMousePoint);
9504
9505 m_prev_rlat = m_cursor_lat;
9506 m_prev_rlon = m_cursor_lon;
9507 m_prev_pMousePoint = pMousePoint;
9508 m_pMeasureRoute->m_lastMousePointIndex =
9509 m_pMeasureRoute->GetnPoints();
9510
9511 m_nMeasureState++;
9512 } else {
9513 CancelMeasureRoute();
9514 }
9515
9516 Refresh(true);
9517 ret = true;
9518 } else {
9519 bool bSelectAllowed = true;
9520 if (NULL == g_pMarkInfoDialog) {
9521 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9522 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9523 bSelectAllowed = false;
9524
9525 // Avoid accidental selection of routepoint if last touchdown started
9526 // a significant chart drag operation
9527 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9528 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9529 significant_drag) ||
9530 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9531 significant_drag)) {
9532 bSelectAllowed = false;
9533 }
9534
9535 /*if this left up happens at the end of a route point dragging and if
9536 the cursor/thumb is on the draghandle icon, not on the point iself a new
9537 selection will select nothing and the drag will never be ended, so the
9538 legs around this point never selectable. At this step we don't need a
9539 new selection, just keep the previoulsly selected and dragged point */
9540 if (m_bRoutePoinDragging) bSelectAllowed = false;
9541
9542 if (bSelectAllowed) {
9543 bool b_was_editing_mark = m_bMarkEditing;
9544 bool b_was_editing_route = m_bRouteEditing;
9545 FindRoutePointsAtCursor(SelectRadius,
9546 true); // Possibly selecting a point in a
9547 // route for later dragging
9548
9549 /*route and a mark points in layer can't be dragged so should't be
9550 * selected and no draghandle icon*/
9551 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9552 m_pRoutePointEditTarget = NULL;
9553
9554 if (!b_was_editing_route) {
9555 if (m_pEditRouteArray) {
9556 b_startedit_route = true;
9557
9558 // Hide the track and route rollover during route point edit, not
9559 // needed, and may be confusing
9560 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9561 m_pTrackRolloverWin->IsActive(false);
9562 }
9563 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9564 m_pRouteRolloverWin->IsActive(false);
9565 }
9566
9567 wxRect pre_rect;
9568 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9569 ir++) {
9570 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9571 // Need to validate route pointer
9572 // Route may be gone due to drgging close to ownship with
9573 // "Delete On Arrival" state set, as in the case of
9574 // navigating to an isolated waypoint on a temporary route
9575 if (g_pRouteMan->IsRouteValid(pr)) {
9576 // pr->SetHiLite(50);
9577 wxRect route_rect;
9578 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9579 pre_rect.Union(route_rect);
9580 }
9581 }
9582 RefreshRect(pre_rect, true);
9583 }
9584 } else {
9585 b_startedit_route = false;
9586 }
9587
9588 // Mark editing in touch mode, left-up event.
9589 if (m_pRoutePointEditTarget) {
9590 if (b_was_editing_mark ||
9591 b_was_editing_route) { // kill previous hilight
9592 if (m_lastRoutePointEditTarget) {
9593 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9594 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9595 RoutePointGui(*m_lastRoutePointEditTarget)
9596 .EnableDragHandle(false);
9597 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9598 SELTYPE_DRAGHANDLE);
9599 }
9600 }
9601
9602 if (m_pRoutePointEditTarget) {
9603 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9604 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9605 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9606 wxPoint2DDouble dragHandlePoint =
9607 RoutePointGui(*m_pRoutePointEditTarget)
9608 .GetDragHandlePoint(this);
9609 pSelect->AddSelectablePoint(
9610 dragHandlePoint.m_y, dragHandlePoint.m_x,
9611 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9612 }
9613 } else { // Deselect everything
9614 if (m_lastRoutePointEditTarget) {
9615 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9616 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9617 RoutePointGui(*m_lastRoutePointEditTarget)
9618 .EnableDragHandle(false);
9619 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9620 SELTYPE_DRAGHANDLE);
9621
9622 // Clear any routes being edited, probably orphans
9623 wxArrayPtrVoid *lastEditRouteArray =
9625 m_lastRoutePointEditTarget);
9626 if (lastEditRouteArray) {
9627 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9628 ir++) {
9629 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9630 if (g_pRouteMan->IsRouteValid(pr)) {
9631 pr->m_bIsBeingEdited = false;
9632 }
9633 }
9634 delete lastEditRouteArray;
9635 }
9636 }
9637 }
9638
9639 // Do the refresh
9640
9641 if (g_bopengl) {
9642 InvalidateGL();
9643 Refresh(false);
9644 } else {
9645 if (m_lastRoutePointEditTarget) {
9646 wxRect wp_rect;
9647 RoutePointGui(*m_lastRoutePointEditTarget)
9648 .CalculateDCRect(m_dc_route, this, &wp_rect);
9649 RefreshRect(wp_rect, true);
9650 }
9651
9652 if (m_pRoutePointEditTarget) {
9653 wxRect wp_rect;
9654 RoutePointGui(*m_pRoutePointEditTarget)
9655 .CalculateDCRect(m_dc_route, this, &wp_rect);
9656 RefreshRect(wp_rect, true);
9657 }
9658 }
9659 }
9660 } // bSelectAllowed
9661
9662 // Check to see if there is a route or AIS target under the cursor
9663 // If so, start the rollover timer which creates the popup
9664 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9665 bool b_start_rollover = false;
9666 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9667 SelectItem *pFind = pSelectAIS->FindSelection(
9668 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9669 if (pFind) b_start_rollover = true;
9670 }
9671
9672 if (!b_start_rollover && !b_startedit_route) {
9673 SelectableItemList SelList = pSelect->FindSelectionList(
9674 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9675 for (SelectItem *pFindSel : SelList) {
9676 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9677 if (pr && pr->IsVisible()) {
9678 b_start_rollover = true;
9679 break;
9680 }
9681 } // while
9682 }
9683
9684 if (!b_start_rollover && !b_startedit_route) {
9685 SelectableItemList SelList = pSelect->FindSelectionList(
9686 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9687 for (SelectItem *pFindSel : SelList) {
9688 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9689 if (tr && tr->IsVisible()) {
9690 b_start_rollover = true;
9691 break;
9692 }
9693 } // while
9694 }
9695
9696 if (b_start_rollover)
9697 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9698 wxTIMER_ONE_SHOT);
9699 Route *tail = 0;
9700 Route *current = 0;
9701 bool appending = false;
9702 bool inserting = false;
9703 int connect = 0;
9704 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9705 // drag
9706 if (m_pRoutePointEditTarget) {
9707 // Check to see if there is a nearby point which may replace the
9708 // dragged one
9709 RoutePoint *pMousePoint = NULL;
9710
9711 int index_last;
9712 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9713 double nearby_radius_meters =
9714 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9715 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9716 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9717 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9718 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9719 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9720 bool duplicate =
9721 false; // ensure we won't create duplicate point in routes
9722 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9723 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9724 ir++) {
9725 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9726 if (pr && pr->pRoutePointList) {
9727 auto *list = pr->pRoutePointList;
9728 auto pos =
9729 std::find(list->begin(), list->end(), pNearbyPoint);
9730 if (pos != list->end()) {
9731 duplicate = true;
9732 break;
9733 }
9734 }
9735 }
9736 }
9737
9738 // Special case:
9739 // Allow "re-use" of a route's waypoints iff it is a simple
9740 // isolated route. This allows, for instance, creation of a closed
9741 // polygon route
9742 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9743
9744 if (!duplicate) {
9745 int dlg_return;
9746 dlg_return =
9747 OCPNMessageBox(this,
9748 _("Replace this RoutePoint by the nearby "
9749 "Waypoint?"),
9750 _("OpenCPN RoutePoint change"),
9751 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9752 if (dlg_return == wxID_YES) {
9753 /*double confirmation if the dragged point has been manually
9754 * created which can be important and could be deleted
9755 * unintentionally*/
9756
9757 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9758 pNearbyPoint);
9759 current =
9760 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9761
9762 if (tail && current && (tail != current)) {
9763 int dlg_return1;
9764 connect = tail->GetIndexOf(pNearbyPoint);
9765 int index_current_route =
9766 current->GetIndexOf(m_pRoutePointEditTarget);
9767 index_last = current->GetIndexOf(current->GetLastPoint());
9768 dlg_return1 = wxID_NO;
9769 if (index_last ==
9770 index_current_route) { // we are dragging the last
9771 // point of the route
9772 if (connect != tail->GetnPoints()) { // anything to do?
9773
9774 wxString dmsg(
9775 _("Last part of route to be appended to dragged "
9776 "route?"));
9777 if (connect == 1)
9778 dmsg =
9779 _("Full route to be appended to dragged route?");
9780
9781 dlg_return1 = OCPNMessageBox(
9782 this, dmsg, _("OpenCPN Route Create"),
9783 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9784 if (dlg_return1 == wxID_YES) {
9785 appending = true;
9786 }
9787 }
9788 } else if (index_current_route ==
9789 1) { // dragging the first point of the route
9790 if (connect != 1) { // anything to do?
9791
9792 wxString dmsg(
9793 _("First part of route to be inserted into dragged "
9794 "route?"));
9795 if (connect == tail->GetnPoints())
9796 dmsg = _(
9797 "Full route to be inserted into dragged route?");
9798
9799 dlg_return1 = OCPNMessageBox(
9800 this, dmsg, _("OpenCPN Route Create"),
9801 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9802 if (dlg_return1 == wxID_YES) {
9803 inserting = true;
9804 }
9805 }
9806 }
9807 }
9808
9809 if (m_pRoutePointEditTarget->IsShared()) {
9810 // dlg_return = wxID_NO;
9811 dlg_return = OCPNMessageBox(
9812 this,
9813 _("Do you really want to delete and replace this "
9814 "WayPoint") +
9815 "\n" + _("which has been created manually?"),
9816 ("OpenCPN RoutePoint warning"),
9817 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9818 }
9819 }
9820 if (dlg_return == wxID_YES) {
9821 pMousePoint = pNearbyPoint;
9822 if (pMousePoint->m_bIsolatedMark) {
9823 pMousePoint->SetShared(true);
9824 }
9825 pMousePoint->m_bIsolatedMark =
9826 false; // definitely no longer isolated
9827 pMousePoint->m_bIsInRoute = true;
9828 }
9829 }
9830 }
9831 }
9832 if (!pMousePoint)
9833 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9834
9835 if (m_pEditRouteArray) {
9836 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9837 ir++) {
9838 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9839 if (g_pRouteMan->IsRouteValid(pr)) {
9840 if (pMousePoint) { // remove the dragged point and insert the
9841 // nearby
9842 auto *list = pr->pRoutePointList;
9843 auto pos = std::find(list->begin(), list->end(),
9844 m_pRoutePointEditTarget);
9845
9846 pSelect->DeleteAllSelectableRoutePoints(pr);
9847 pSelect->DeleteAllSelectableRouteSegments(pr);
9848
9849 pr->pRoutePointList->insert(pos, pMousePoint);
9850 pos = std::find(list->begin(), list->end(),
9851 m_pRoutePointEditTarget);
9852 pr->pRoutePointList->erase(pos);
9853
9854 pSelect->AddAllSelectableRouteSegments(pr);
9855 pSelect->AddAllSelectableRoutePoints(pr);
9856 }
9857 pr->FinalizeForRendering();
9858 pr->UpdateSegmentDistances();
9859 if (m_bRoutePoinDragging) {
9860 // pConfig->UpdateRoute(pr);
9861 NavObj_dB::GetInstance().UpdateRoute(pr);
9862 }
9863 }
9864 }
9865 }
9866
9867 // Update the RouteProperties Dialog, if currently shown
9868 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9869 if (m_pEditRouteArray) {
9870 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9871 ir++) {
9872 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9873 if (g_pRouteMan->IsRouteValid(pr)) {
9874 if (pRoutePropDialog->GetRoute() == pr) {
9875 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9876 }
9877 /* cannot edit track points anyway
9878 else if ( ( NULL !=
9879 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9880 pTrackPropDialog->m_pTrack == pr ) {
9881 pTrackPropDialog->SetTrackAndUpdate(
9882 pr );
9883 }
9884 */
9885 }
9886 }
9887 }
9888 }
9889 if (pMousePoint) { // clear all about the dragged point
9890 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9891 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9892 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9893 // Hide mark properties dialog if open on the replaced point
9894 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9895 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9896 g_pMarkInfoDialog->Hide();
9897
9898 delete m_pRoutePointEditTarget;
9899 m_lastRoutePointEditTarget = NULL;
9900 m_pRoutePointEditTarget = NULL;
9901 undo->AfterUndoableAction(pMousePoint);
9902 undo->InvalidateUndo();
9903 }
9904 }
9905 }
9906
9907 else if (m_bMarkEditing) { // End of way point drag
9908 if (m_pRoutePointEditTarget)
9909 if (m_bRoutePoinDragging) {
9910 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9911 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9912 }
9913 }
9914
9915 if (m_pRoutePointEditTarget)
9916 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9917
9918 if (!m_pRoutePointEditTarget) {
9919 delete m_pEditRouteArray;
9920 m_pEditRouteArray = NULL;
9921 m_bRouteEditing = false;
9922 }
9923 m_bRoutePoinDragging = false;
9924
9925 if (appending) { // Appending to the route of which the last point is
9926 // dragged onto another route
9927
9928 // copy tail from connect until length to end of current after dragging
9929
9930 int length = tail->GetnPoints();
9931 for (int i = connect + 1; i <= length; i++) {
9932 current->AddPointAndSegment(tail->GetPoint(i), false);
9933 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9934 m_routeState++;
9935 top_frame::Get()->RefreshAllCanvas();
9936 ret = true;
9937 }
9938 current->FinalizeForRendering();
9939 current->m_bIsBeingEdited = false;
9940 FinishRoute();
9941 g_pRouteMan->DeleteRoute(tail);
9942 }
9943 if (inserting) {
9944 pSelect->DeleteAllSelectableRoutePoints(current);
9945 pSelect->DeleteAllSelectableRouteSegments(current);
9946 for (int i = 1; i < connect; i++) { // numbering in the tail route
9947 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9948 }
9949 pSelect->AddAllSelectableRouteSegments(current);
9950 pSelect->AddAllSelectableRoutePoints(current);
9951 current->FinalizeForRendering();
9952 current->m_bIsBeingEdited = false;
9953 g_pRouteMan->DeleteRoute(tail);
9954 }
9955
9956 // Update the RouteProperties Dialog, if currently shown
9957 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9958 if (m_pEditRouteArray) {
9959 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9960 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9961 if (g_pRouteMan->IsRouteValid(pr)) {
9962 if (pRoutePropDialog->GetRoute() == pr) {
9963 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9964 }
9965 }
9966 }
9967 }
9968 }
9969
9970 } // g_btouch
9971
9972 else { // !g_btouch
9973 if (m_bRouteEditing) { // End of RoutePoint drag
9974 Route *tail = 0;
9975 Route *current = 0;
9976 bool appending = false;
9977 bool inserting = false;
9978 int connect = 0;
9979 int index_last;
9980 if (m_pRoutePointEditTarget) {
9981 m_pRoutePointEditTarget->m_bBlink = false;
9982 // Check to see if there is a nearby point which may replace the
9983 // dragged one
9984 RoutePoint *pMousePoint = NULL;
9985 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9986 double nearby_radius_meters =
9987 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9988 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9989 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9990 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9991 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9992 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9993 bool duplicate = false; // don't create duplicate point in routes
9994 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9995 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9996 ir++) {
9997 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9998 if (pr && pr->pRoutePointList) {
9999 auto *list = pr->pRoutePointList;
10000 auto pos =
10001 std::find(list->begin(), list->end(), pNearbyPoint);
10002 if (pos != list->end()) {
10003 duplicate = true;
10004 break;
10005 }
10006 }
10007 }
10008 }
10009
10010 // Special case:
10011 // Allow "re-use" of a route's waypoints iff it is a simple
10012 // isolated route. This allows, for instance, creation of a closed
10013 // polygon route
10014 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
10015
10016 if (!duplicate) {
10017 int dlg_return;
10018 dlg_return =
10019 OCPNMessageBox(this,
10020 _("Replace this RoutePoint by the nearby "
10021 "Waypoint?"),
10022 _("OpenCPN RoutePoint change"),
10023 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10024 if (dlg_return == wxID_YES) {
10025 /*double confirmation if the dragged point has been manually
10026 * created which can be important and could be deleted
10027 * unintentionally*/
10028 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10029 pNearbyPoint);
10030 current =
10031 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10032
10033 if (tail && current && (tail != current)) {
10034 int dlg_return1;
10035 connect = tail->GetIndexOf(pNearbyPoint);
10036 int index_current_route =
10037 current->GetIndexOf(m_pRoutePointEditTarget);
10038 index_last = current->GetIndexOf(current->GetLastPoint());
10039 dlg_return1 = wxID_NO;
10040 if (index_last ==
10041 index_current_route) { // we are dragging the last
10042 // point of the route
10043 if (connect != tail->GetnPoints()) { // anything to do?
10044
10045 wxString dmsg(
10046 _("Last part of route to be appended to dragged "
10047 "route?"));
10048 if (connect == 1)
10049 dmsg =
10050 _("Full route to be appended to dragged route?");
10051
10052 dlg_return1 = OCPNMessageBox(
10053 this, dmsg, _("OpenCPN Route Create"),
10054 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10055 if (dlg_return1 == wxID_YES) {
10056 appending = true;
10057 }
10058 }
10059 } else if (index_current_route ==
10060 1) { // dragging the first point of the route
10061 if (connect != 1) { // anything to do?
10062
10063 wxString dmsg(
10064 _("First part of route to be inserted into dragged "
10065 "route?"));
10066 if (connect == tail->GetnPoints())
10067 dmsg = _(
10068 "Full route to be inserted into dragged route?");
10069
10070 dlg_return1 = OCPNMessageBox(
10071 this, dmsg, _("OpenCPN Route Create"),
10072 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10073 if (dlg_return1 == wxID_YES) {
10074 inserting = true;
10075 }
10076 }
10077 }
10078 }
10079
10080 if (m_pRoutePointEditTarget->IsShared()) {
10081 dlg_return = wxID_NO;
10082 dlg_return = OCPNMessageBox(
10083 this,
10084 _("Do you really want to delete and replace this "
10085 "WayPoint") +
10086 "\n" + _("which has been created manually?"),
10087 ("OpenCPN RoutePoint warning"),
10088 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10089 }
10090 }
10091 if (dlg_return == wxID_YES) {
10092 pMousePoint = pNearbyPoint;
10093 if (pMousePoint->m_bIsolatedMark) {
10094 pMousePoint->SetShared(true);
10095 }
10096 pMousePoint->m_bIsolatedMark =
10097 false; // definitely no longer isolated
10098 pMousePoint->m_bIsInRoute = true;
10099 }
10100 }
10101 }
10102 }
10103 if (!pMousePoint)
10104 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10105
10106 if (m_pEditRouteArray) {
10107 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10108 ir++) {
10109 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10110 if (g_pRouteMan->IsRouteValid(pr)) {
10111 if (pMousePoint) { // replace dragged point by nearby one
10112 auto *list = pr->pRoutePointList;
10113 auto pos = std::find(list->begin(), list->end(),
10114 m_pRoutePointEditTarget);
10115
10116 pSelect->DeleteAllSelectableRoutePoints(pr);
10117 pSelect->DeleteAllSelectableRouteSegments(pr);
10118
10119 pr->pRoutePointList->insert(pos, pMousePoint);
10120 pos = std::find(list->begin(), list->end(),
10121 m_pRoutePointEditTarget);
10122 if (pos != list->end()) list->erase(pos);
10123 // pr->pRoutePointList->erase(pos + 1);
10124
10125 pSelect->AddAllSelectableRouteSegments(pr);
10126 pSelect->AddAllSelectableRoutePoints(pr);
10127 }
10128 pr->FinalizeForRendering();
10129 pr->UpdateSegmentDistances();
10130 pr->m_bIsBeingEdited = false;
10131
10132 if (m_bRoutePoinDragging) {
10133 // Special case optimization.
10134 // Dragging a single point of a route
10135 // without any point additions or re-ordering
10136 if (!pMousePoint)
10137 NavObj_dB::GetInstance().UpdateRoutePoint(
10138 m_pRoutePointEditTarget);
10139 else
10140 NavObj_dB::GetInstance().UpdateRoute(pr);
10141 }
10142 pr->SetHiLite(0);
10143 }
10144 }
10145 Refresh(false);
10146 }
10147
10148 if (appending) {
10149 // copy tail from connect until length to end of current after
10150 // dragging
10151
10152 int length = tail->GetnPoints();
10153 for (int i = connect + 1; i <= length; i++) {
10154 current->AddPointAndSegment(tail->GetPoint(i), false);
10155 if (current)
10156 current->m_lastMousePointIndex = current->GetnPoints();
10157 m_routeState++;
10158 top_frame::Get()->RefreshAllCanvas();
10159 ret = true;
10160 }
10161 current->FinalizeForRendering();
10162 current->m_bIsBeingEdited = false;
10163 FinishRoute();
10164 g_pRouteMan->DeleteRoute(tail);
10165 }
10166 if (inserting) {
10167 pSelect->DeleteAllSelectableRoutePoints(current);
10168 pSelect->DeleteAllSelectableRouteSegments(current);
10169 for (int i = 1; i < connect; i++) { // numbering in the tail route
10170 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10171 }
10172 pSelect->AddAllSelectableRouteSegments(current);
10173 pSelect->AddAllSelectableRoutePoints(current);
10174 current->FinalizeForRendering();
10175 current->m_bIsBeingEdited = false;
10176 g_pRouteMan->DeleteRoute(tail);
10177 }
10178
10179 // Update the RouteProperties Dialog, if currently shown
10180 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10181 if (m_pEditRouteArray) {
10182 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10183 ir++) {
10184 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10185 if (g_pRouteMan->IsRouteValid(pr)) {
10186 if (pRoutePropDialog->GetRoute() == pr) {
10187 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10188 }
10189 }
10190 }
10191 }
10192 }
10193
10194 if (pMousePoint) {
10195 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10196 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10197 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10198 // Hide mark properties dialog if open on the replaced point
10199 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10200 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10201 g_pMarkInfoDialog->Hide();
10202
10203 delete m_pRoutePointEditTarget;
10204 m_lastRoutePointEditTarget = NULL;
10205 undo->AfterUndoableAction(pMousePoint);
10206 undo->InvalidateUndo();
10207 } else {
10208 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10209 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10210
10211 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10212 }
10213
10214 delete m_pEditRouteArray;
10215 m_pEditRouteArray = NULL;
10216 }
10217
10218 InvalidateGL();
10219 m_bRouteEditing = false;
10220 m_pRoutePointEditTarget = NULL;
10221
10222 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10223 ret = true;
10224 }
10225
10226 else if (m_bMarkEditing) { // end of Waypoint drag
10227 if (m_pRoutePointEditTarget) {
10228 if (m_bRoutePoinDragging) {
10229 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10230 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10231 }
10232 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10233 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10234 if (!g_bopengl) {
10235 wxRect wp_rect;
10236 RoutePointGui(*m_pRoutePointEditTarget)
10237 .CalculateDCRect(m_dc_route, this, &wp_rect);
10238 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10239 RefreshRect(wp_rect, true);
10240 }
10241 }
10242 m_pRoutePointEditTarget = NULL;
10243 m_bMarkEditing = false;
10244 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10245 ret = true;
10246 }
10247
10248 else if (leftIsDown) { // left click for chart center
10249 leftIsDown = false;
10250 ret = false;
10251
10252 if (!g_btouch) {
10253 if (!m_bChartDragging && !m_bMeasure_Active) {
10254 } else {
10255 m_bChartDragging = false;
10256 }
10257 }
10258 }
10259 m_bRoutePoinDragging = false;
10260 } // !btouch
10261
10262 if (ret) return true;
10263 } // left up
10264
10265 if (event.RightDown()) {
10266 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10267 last_drag.x = mx;
10268 last_drag.y = my;
10269
10270 if (g_btouch) {
10271 // if( m_pRoutePointEditTarget )
10272 // return false;
10273 }
10274
10275 ret = true;
10276 m_FinishRouteOnKillFocus = false;
10277 CallPopupMenu(mx, my);
10278 m_FinishRouteOnKillFocus = true;
10279 } // Right down
10280
10281 return ret;
10282}
10283
10284bool panleftIsDown;
10285bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10286 // Skip all mouse processing if shift is held.
10287 // This allows plugins to implement shift+drag behaviors.
10288 if (event.ShiftDown()) {
10289 return false;
10290 }
10291 int x, y;
10292 event.GetPosition(&x, &y);
10293
10294 x *= m_displayScale;
10295 y *= m_displayScale;
10296
10297 // Check for wheel rotation
10298 // ideally, should be just longer than the time between
10299 // processing accumulated mouse events from the event queue
10300 // as would happen during screen redraws.
10301 int wheel_dir = event.GetWheelRotation();
10302
10303 if (wheel_dir) {
10304 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10305 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10306
10307 double factor = g_mouse_zoom_sensitivity;
10308 if (wheel_dir < 0) factor = 1 / factor;
10309
10310 if (g_bsmoothpanzoom) {
10311 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10312 if (wheel_dir == m_last_wheel_dir) {
10313 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10314 // m_zoom_target /= factor;
10315 } else
10316 StopMovement();
10317 } else {
10318 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10319 m_wheelstopwatch.Start(0);
10320 // m_zoom_target = VPoint.chart_scale / factor;
10321 }
10322 }
10323
10324 m_last_wheel_dir = wheel_dir;
10325
10326 ZoomCanvas(factor, true, false);
10327 }
10328
10329 if (event.LeftDown()) {
10330 // Skip the first left click if it will cause a canvas focus shift
10331 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10332 return false;
10333 }
10334
10335 last_drag.x = x, last_drag.y = y;
10336 m_touchdownPos = wxPoint(x, y);
10337 panleftIsDown = true;
10338 }
10339
10340 if (event.LeftUp()) {
10341 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10342 // seen here.
10343 panleftIsDown = false;
10344
10345 if (!g_btouch) {
10346 if (!m_bChartDragging && !m_bMeasure_Active) {
10347 switch (cursor_region) {
10348 case MID_RIGHT: {
10349 PanCanvas(100, 0);
10350 break;
10351 }
10352
10353 case MID_LEFT: {
10354 PanCanvas(-100, 0);
10355 break;
10356 }
10357
10358 case MID_TOP: {
10359 PanCanvas(0, 100);
10360 break;
10361 }
10362
10363 case MID_BOT: {
10364 PanCanvas(0, -100);
10365 break;
10366 }
10367
10368 case CENTER: {
10369 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10370 break;
10371 }
10372 }
10373 } else {
10374 m_bChartDragging = false;
10375 }
10376 }
10377 }
10378 }
10379
10380 if (event.Dragging() && event.LeftIsDown()) {
10381 /*
10382 * fixed dragging.
10383 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10384 * before the drag event. Hence, as there is no mouse down event, last_drag
10385 * is not reset before the drag. And that results in one single drag
10386 * session, meaning you cannot drag the map a few miles north, lift your
10387 * finger, and the go even further north. Instead, the map resets itself
10388 * always to the very first drag start (since there is not reset of
10389 * last_drag).
10390 *
10391 * Besides, should not left down and dragging be enough of a situation to
10392 * start a drag procedure?
10393 *
10394 * Anyways, guarded it to be active in touch situations only.
10395 */
10396 if (g_btouch && !m_inPinch) {
10397 struct timespec now;
10398 clock_gettime(CLOCK_MONOTONIC, &now);
10399 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10400
10401 bool trigger_hold = false;
10402 if (false == m_bChartDragging) {
10403 if (m_DragTrigger < 0) {
10404 // printf("\ntrigger1\n");
10405 m_DragTrigger = 0;
10406 m_DragTriggerStartTime = tnow;
10407 trigger_hold = true;
10408 } else {
10409 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10410 m_DragTrigger = -1; // Reset trigger
10411 // printf("trigger fired\n");
10412 }
10413 }
10414 }
10415 if (trigger_hold) return true;
10416
10417 if (false == m_bChartDragging) {
10418 // printf("starting drag\n");
10419 // Reset drag calculation members
10420 last_drag.x = x - 1, last_drag.y = y - 1;
10421 m_bChartDragging = true;
10422 m_chart_drag_total_time = 0;
10423 m_chart_drag_total_x = 0;
10424 m_chart_drag_total_y = 0;
10425 m_inertia_last_drag_x = x;
10426 m_inertia_last_drag_y = y;
10427 m_drag_vec_x.clear();
10428 m_drag_vec_y.clear();
10429 m_drag_vec_t.clear();
10430 m_last_drag_time = tnow;
10431 }
10432
10433 // Calculate and store drag dynamics.
10434 uint64_t delta_t = tnow - m_last_drag_time;
10435 double delta_tf = delta_t / 1e9;
10436
10437 m_chart_drag_total_time += delta_tf;
10438 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10439 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10440
10441 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10442 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10443 m_drag_vec_t.push_back(delta_tf);
10444
10445 m_inertia_last_drag_x = x;
10446 m_inertia_last_drag_y = y;
10447 m_last_drag_time = tnow;
10448
10449 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10450 m_bChartDragging = true;
10451 StartTimedMovement();
10452 m_pan_drag.x += last_drag.x - x;
10453 m_pan_drag.y += last_drag.y - y;
10454 last_drag.x = x, last_drag.y = y;
10455 }
10456 } else if (!g_btouch) {
10457 if ((last_drag.x != x) || (last_drag.y != y)) {
10458 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10459 // dragging on route create.
10460 // github #2994
10461 m_bChartDragging = true;
10462 StartTimedMovement();
10463 m_pan_drag.x += last_drag.x - x;
10464 m_pan_drag.y += last_drag.y - y;
10465 last_drag.x = x, last_drag.y = y;
10466 }
10467 }
10468 }
10469
10470 // Handle some special cases
10471 if (g_btouch) {
10472 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10473 // LeftUp handler uses displacement check to distinguish taps from pans.
10474 m_DoubleClickTimer->Start();
10475 singleClickEventIsValid = false;
10476 }
10477 }
10478 }
10479
10480 return true;
10481}
10482
10483void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10484 if (MouseEventOverlayWindows(event)) return;
10485
10486 if (MouseEventSetup(event)) return; // handled, no further action required
10487
10488 bool nm = MouseEventProcessObjects(event);
10489 if (!nm) MouseEventProcessCanvas(event);
10490}
10491
10492void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10493 // Switch to the appropriate cursor on mouse movement
10494
10495 wxCursor *ptarget_cursor = pCursorArrow;
10496 if (!pPlugIn_Cursor) {
10497 ptarget_cursor = pCursorArrow;
10498 if ((!m_routeState) &&
10499 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10500 if (cursor_region == MID_RIGHT) {
10501 ptarget_cursor = pCursorRight;
10502 } else if (cursor_region == MID_LEFT) {
10503 ptarget_cursor = pCursorLeft;
10504 } else if (cursor_region == MID_TOP) {
10505 ptarget_cursor = pCursorDown;
10506 } else if (cursor_region == MID_BOT) {
10507 ptarget_cursor = pCursorUp;
10508 } else {
10509 ptarget_cursor = pCursorArrow;
10510 }
10511 } else if (m_bMeasure_Active ||
10512 m_routeState) // If Measure tool use Pencil Cursor
10513 ptarget_cursor = pCursorPencil;
10514 } else {
10515 ptarget_cursor = pPlugIn_Cursor;
10516 }
10517
10518 SetCursor(*ptarget_cursor);
10519}
10520
10521void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10522 SetCursor(*pCursorArrow);
10523}
10524
10525void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10526 ChartPlugInWrapper *target_plugin_chart = NULL;
10527 s57chart *Chs57 = NULL;
10528 wxFileName file;
10529 wxArrayString files;
10530
10531 ChartBase *target_chart = GetChartAtCursor();
10532 if (target_chart) {
10533 file.Assign(target_chart->GetFullPath());
10534 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10535 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10536 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10537 else
10538 Chs57 = dynamic_cast<s57chart *>(target_chart);
10539 } else { // target_chart = null, might be mbtiles
10540 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10541 unsigned int im = stackIndexArray.size();
10542 int scale = 2147483647; // max 32b integer
10543 if (VPoint.b_quilt && im > 0) {
10544 for (unsigned int is = 0; is < im; is++) {
10545 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10546 CHART_TYPE_MBTILES) {
10547 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10548 double lat, lon;
10549 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10550 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10551 .GetBBox()
10552 .Contains(lat, lon)) {
10553 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10554 scale) {
10555 scale =
10556 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10557 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10558 }
10559 }
10560 }
10561 }
10562 }
10563 }
10564
10565 std::vector<Ais8_001_22 *> area_notices;
10566
10567 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10568 float vp_scale = GetVPScale();
10569
10570 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10571 auto target_data = target.second;
10572 if (!target_data->area_notices.empty()) {
10573 for (auto &ani : target_data->area_notices) {
10574 Ais8_001_22 &area_notice = ani.second;
10575
10576 BoundingBox bbox;
10577
10578 for (Ais8_001_22_SubAreaList::iterator sa =
10579 area_notice.sub_areas.begin();
10580 sa != area_notice.sub_areas.end(); ++sa) {
10581 switch (sa->shape) {
10582 case AIS8_001_22_SHAPE_CIRCLE: {
10583 wxPoint target_point;
10584 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10585 bbox.Expand(target_point);
10586 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10587 break;
10588 }
10589 case AIS8_001_22_SHAPE_RECT: {
10590 wxPoint target_point;
10591 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10592 bbox.Expand(target_point);
10593 if (sa->e_dim_m > sa->n_dim_m)
10594 bbox.EnLarge(sa->e_dim_m * vp_scale);
10595 else
10596 bbox.EnLarge(sa->n_dim_m * vp_scale);
10597 break;
10598 }
10599 case AIS8_001_22_SHAPE_POLYGON:
10600 case AIS8_001_22_SHAPE_POLYLINE: {
10601 for (int i = 0; i < 4; ++i) {
10602 double lat = sa->latitude;
10603 double lon = sa->longitude;
10604 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10605 &lat, &lon);
10606 wxPoint target_point;
10607 GetCanvasPointPix(lat, lon, &target_point);
10608 bbox.Expand(target_point);
10609 }
10610 break;
10611 }
10612 case AIS8_001_22_SHAPE_SECTOR: {
10613 double lat1 = sa->latitude;
10614 double lon1 = sa->longitude;
10615 double lat, lon;
10616 wxPoint target_point;
10617 GetCanvasPointPix(lat1, lon1, &target_point);
10618 bbox.Expand(target_point);
10619 for (int i = 0; i < 18; ++i) {
10620 ll_gc_ll(
10621 lat1, lon1,
10622 sa->left_bound_deg +
10623 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10624 sa->radius_m / 1852.0, &lat, &lon);
10625 GetCanvasPointPix(lat, lon, &target_point);
10626 bbox.Expand(target_point);
10627 }
10628 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10629 &lat, &lon);
10630 GetCanvasPointPix(lat, lon, &target_point);
10631 bbox.Expand(target_point);
10632 break;
10633 }
10634 }
10635 }
10636
10637 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10638 area_notices.push_back(&area_notice);
10639 }
10640 }
10641 }
10642 }
10643 }
10644
10645 if (target_chart || !area_notices.empty() || file.HasName()) {
10646 // Go get the array of all objects at the cursor lat/lon
10647 int sel_rad_pix = 5;
10648 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10649
10650 // Make sure we always get the lights from an object, even if we are
10651 // currently not displaying lights on the chart.
10652
10653 SetCursor(wxCURSOR_WAIT);
10654 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10655 if (!lightsVis) SetShowENCLights(true);
10656 ;
10657
10658 ListOfObjRazRules *rule_list = NULL;
10659 ListOfPI_S57Obj *pi_rule_list = NULL;
10660 if (Chs57)
10661 rule_list =
10662 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10663 else if (target_plugin_chart)
10664 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10665 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10666
10667 ListOfObjRazRules *overlay_rule_list = NULL;
10668 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10669 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10670
10671 if (CHs57_Overlay) {
10672 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10673 zlat, zlon, SelectRadius, &GetVP());
10674 }
10675
10676 if (!lightsVis) SetShowENCLights(false);
10677
10678 wxString objText;
10679 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10680 wxString face = dFont->GetFaceName();
10681
10682 if (NULL == g_pObjectQueryDialog) {
10684 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10685 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10686 }
10687
10688 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10689 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10690
10691#ifdef __WXOSX__
10692 // Auto Adjustment for dark mode
10693 fg = g_pObjectQueryDialog->GetForegroundColour();
10694#endif
10695
10696 objText.Printf(
10697 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10698 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10699
10700#ifdef __WXOSX__
10701 int points = dFont->GetPointSize();
10702#else
10703 int points = dFont->GetPointSize() + 1;
10704#endif
10705
10706 int sizes[7];
10707 for (int i = -2; i < 5; i++) {
10708 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10709 }
10710 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10711
10712 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10713
10714 if (overlay_rule_list && CHs57_Overlay) {
10715 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10716 objText << "<hr noshade>";
10717 }
10718
10719 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10720 an != area_notices.end(); ++an) {
10721 objText << "<b>AIS Area Notice:</b> ";
10722 objText << ais8_001_22_notice_names[(*an)->notice_type];
10723 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10724 (*an)->sub_areas.begin();
10725 sa != (*an)->sub_areas.end(); ++sa)
10726 if (!sa->text.empty()) objText << sa->text;
10727 objText << "<br>expires: " << (*an)->expiry_time.Format();
10728 objText << "<hr noshade>";
10729 }
10730
10731 if (Chs57)
10732 objText << Chs57->CreateObjDescriptions(rule_list);
10733 else if (target_plugin_chart)
10734 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10735 pi_rule_list);
10736
10737 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10738
10739 // Add the additional info files
10740 wxString AddFiles, filenameOK;
10741 int filecount = 0;
10742 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10743 // plugin
10744
10745 AddFiles = wxString::Format(
10746 "<hr noshade><br><b>Additional info files attached to: </b> "
10747 "<font "
10748 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10749 "cellpadding=3>",
10750 file.GetFullName());
10751 file.Normalize();
10752 file.Assign(file.GetPath(), "");
10753 wxDir dir(file.GetFullPath());
10754 wxString filename;
10755 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10756 while (cont) {
10757 file.Assign(dir.GetNameWithSep().append(filename));
10758 wxString FormatString =
10759 "<td valign=top><font size=-2><a "
10760 "href=\"%s\">%s</a></font></td>";
10761 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10762 filenameOK = file.GetFullPath(); // remember last valid name
10763 // we are making a 3 columns table. New row only every third file
10764 if (3 * ((int)filecount / 3) == filecount)
10765 FormatString.Prepend("<tr>"); // new row
10766 else
10767 FormatString.Prepend(
10768 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10769 // spacer column
10770
10771 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10772 file.GetFullName());
10773 filecount++;
10774 }
10775 cont = dir.GetNext(&filename);
10776 }
10777 objText << AddFiles << "</table>";
10778 }
10779 objText << "</font>";
10780 objText << "</body></html>";
10781
10782 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10783 g_pObjectQueryDialog->SetHTMLPage(objText);
10784 g_pObjectQueryDialog->Show();
10785 }
10786 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10787 // generate an event to avoid double code
10788 wxHtmlLinkInfo hli(filenameOK);
10789 wxHtmlLinkEvent hle(1, hli);
10790 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10791 }
10792
10793 if (rule_list) rule_list->Clear();
10794 delete rule_list;
10795
10796 if (overlay_rule_list) overlay_rule_list->Clear();
10797 delete overlay_rule_list;
10798
10799 if (pi_rule_list) pi_rule_list->Clear();
10800 delete pi_rule_list;
10801
10802 SetCursor(wxCURSOR_ARROW);
10803 }
10804}
10805
10806void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10807 bool bNew = false;
10808 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10809 // Dialog
10810 g_pMarkInfoDialog = new MarkInfoDlg(this);
10811 bNew = true;
10812 }
10813
10814 if (1 /*g_bresponsive*/) {
10815 wxSize canvas_size = GetSize();
10816
10817 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10818 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10819
10820 g_pMarkInfoDialog->Layout();
10821
10822 wxPoint canvas_pos = GetPosition();
10823 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10824
10825 bool newFit = false;
10826 if (canvas_size.x < fitted_size.x) {
10827 fitted_size.x = canvas_size.x - 40;
10828 if (canvas_size.y < fitted_size.y)
10829 fitted_size.y -= 40; // scrollbar added
10830 }
10831 if (canvas_size.y < fitted_size.y) {
10832 fitted_size.y = canvas_size.y - 40;
10833 if (canvas_size.x < fitted_size.x)
10834 fitted_size.x -= 40; // scrollbar added
10835 }
10836
10837 if (newFit) {
10838 g_pMarkInfoDialog->SetSize(fitted_size);
10839 g_pMarkInfoDialog->Centre();
10840 }
10841 }
10842
10843 markPoint->m_bRPIsBeingEdited = false;
10844
10845 wxString title_base = _("Mark Properties");
10846 if (markPoint->m_bIsInRoute) {
10847 title_base = _("Waypoint Properties");
10848 }
10849 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10850 g_pMarkInfoDialog->UpdateProperties();
10851 if (markPoint->m_bIsInLayer) {
10852 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10853 GetLayerName(markPoint->m_LayerID)));
10854 g_pMarkInfoDialog->SetDialogTitle(caption);
10855 } else
10856 g_pMarkInfoDialog->SetDialogTitle(title_base);
10857
10858 g_pMarkInfoDialog->Show();
10859 g_pMarkInfoDialog->Raise();
10860 g_pMarkInfoDialog->InitialFocus();
10861 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10862}
10863
10864void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10865 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10866 pRoutePropDialog->SetRouteAndUpdate(selected);
10867 // pNew->UpdateProperties();
10868 pRoutePropDialog->Show();
10869 pRoutePropDialog->Raise();
10870 return;
10871 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10872 this); // There is one global instance of the RouteProp Dialog
10873
10874 if (g_bresponsive) {
10875 wxSize canvas_size = GetSize();
10876 wxPoint canvas_pos = GetPosition();
10877 wxSize fitted_size = pRoutePropDialog->GetSize();
10878 ;
10879
10880 if (canvas_size.x < fitted_size.x) {
10881 fitted_size.x = canvas_size.x;
10882 if (canvas_size.y < fitted_size.y)
10883 fitted_size.y -= 20; // scrollbar added
10884 }
10885 if (canvas_size.y < fitted_size.y) {
10886 fitted_size.y = canvas_size.y;
10887 if (canvas_size.x < fitted_size.x)
10888 fitted_size.x -= 20; // scrollbar added
10889 }
10890
10891 pRoutePropDialog->SetSize(fitted_size);
10892 pRoutePropDialog->Centre();
10893
10894 // int xp = (canvas_size.x - fitted_size.x)/2;
10895 // int yp = (canvas_size.y - fitted_size.y)/2;
10896
10897 wxPoint xxp = ClientToScreen(canvas_pos);
10898 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10899 }
10900
10901 pRoutePropDialog->SetRouteAndUpdate(selected);
10902
10903 pRoutePropDialog->Show();
10904
10905 Refresh(false);
10906}
10907
10908void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10909 pTrackPropDialog = TrackPropDlg::getInstance(
10910 this); // There is one global instance of the RouteProp Dialog
10911
10912 pTrackPropDialog->SetTrackAndUpdate(selected);
10914
10915 pTrackPropDialog->Show();
10916
10917 Refresh(false);
10918}
10919
10920void pupHandler_PasteWaypoint() {
10921 Kml kml;
10922
10923 int pasteBuffer = kml.ParsePasteBuffer();
10924 RoutePoint *pasted = kml.GetParsedRoutePoint();
10925 if (!pasted) return;
10926
10927 double nearby_radius_meters =
10928 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10929
10930 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10931 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10932
10933 int answer = wxID_NO;
10934 if (nearPoint && !nearPoint->m_bIsInLayer) {
10935 wxString msg;
10936 msg << _(
10937 "There is an existing waypoint at the same location as the one you are "
10938 "pasting. Would you like to merge the pasted data with it?\n\n");
10939 msg << _("Answering 'No' will create a new waypoint at the same location.");
10940 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10941 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10942 }
10943
10944 if (answer == wxID_YES) {
10945 nearPoint->SetName(pasted->GetName());
10946 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10947 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10948 pRouteManagerDialog->UpdateWptListCtrl();
10949 }
10950
10951 if (answer == wxID_NO) {
10952 RoutePoint *newPoint = new RoutePoint(pasted);
10953 newPoint->m_bIsolatedMark = true;
10954 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10955 newPoint);
10956 // pConfig->AddNewWayPoint(newPoint, -1);
10957 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10958
10959 pWayPointMan->AddRoutePoint(newPoint);
10960 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10961 pRouteManagerDialog->UpdateWptListCtrl();
10962 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10963 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10964 }
10965
10966 top_frame::Get()->InvalidateAllGL();
10967 top_frame::Get()->RefreshAllCanvas(false);
10968}
10969
10970void pupHandler_PasteRoute() {
10971 Kml kml;
10972
10973 int pasteBuffer = kml.ParsePasteBuffer();
10974 Route *pasted = kml.GetParsedRoute();
10975 if (!pasted) return;
10976
10977 double nearby_radius_meters =
10978 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10979
10980 RoutePoint *curPoint;
10981 RoutePoint *nearPoint;
10982 RoutePoint *prevPoint = NULL;
10983
10984 bool mergepoints = false;
10985 bool createNewRoute = true;
10986 int existingWaypointCounter = 0;
10987
10988 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10989 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10990 nearPoint = pWayPointMan->GetNearbyWaypoint(
10991 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10992 if (nearPoint) {
10993 mergepoints = true;
10994 existingWaypointCounter++;
10995 // Small hack here to avoid both extending RoutePoint and repeating all
10996 // the GetNearbyWaypoint calculations. Use existin data field in
10997 // RoutePoint as temporary storage.
10998 curPoint->m_bPtIsSelected = true;
10999 }
11000 }
11001
11002 int answer = wxID_NO;
11003 if (mergepoints) {
11004 wxString msg;
11005 msg << _(
11006 "There are existing waypoints at the same location as some of the ones "
11007 "you are pasting. Would you like to just merge the pasted data into "
11008 "them?\n\n");
11009 msg << _("Answering 'No' will create all new waypoints for this route.");
11010 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
11011 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
11012
11013 if (answer == wxID_CANCEL) {
11014 return;
11015 }
11016 }
11017
11018 // If all waypoints exist since before, and a route with the same name, we
11019 // don't create a new route.
11020 if (mergepoints && answer == wxID_YES &&
11021 existingWaypointCounter == pasted->GetnPoints()) {
11022 for (Route *proute : *pRouteList) {
11023 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11024 createNewRoute = false;
11025 break;
11026 }
11027 }
11028 }
11029
11030 Route *newRoute = 0;
11031 RoutePoint *newPoint = 0;
11032
11033 if (createNewRoute) {
11034 newRoute = new Route();
11035 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11036 }
11037
11038 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11039 curPoint = pasted->GetPoint(i);
11040 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11041 curPoint->m_bPtIsSelected = false;
11042 newPoint = pWayPointMan->GetNearbyWaypoint(
11043 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11044 newPoint->SetName(curPoint->GetName());
11045 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11046
11047 if (createNewRoute) newRoute->AddPoint(newPoint);
11048 } else {
11049 curPoint->m_bPtIsSelected = false;
11050
11051 newPoint = new RoutePoint(curPoint);
11052 newPoint->m_bIsolatedMark = false;
11053 newPoint->SetIconName("circle");
11054 newPoint->m_bIsVisible = true;
11055 newPoint->m_bShowName = false;
11056 newPoint->SetShared(false);
11057
11058 newRoute->AddPoint(newPoint);
11059 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11060 newPoint);
11061 // pConfig->AddNewWayPoint(newPoint, -1);
11062 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11063 pWayPointMan->AddRoutePoint(newPoint);
11064 }
11065 if (i > 1 && createNewRoute)
11066 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11067 curPoint->m_lat, curPoint->m_lon,
11068 prevPoint, newPoint, newRoute);
11069 prevPoint = newPoint;
11070 }
11071
11072 if (createNewRoute) {
11073 pRouteList->push_back(newRoute);
11074 // pConfig->AddNewRoute(newRoute); // use auto next num
11075 NavObj_dB::GetInstance().InsertRoute(newRoute);
11076
11077 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11078 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11079 }
11080
11081 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11082 pRouteManagerDialog->UpdateRouteListCtrl();
11083 pRouteManagerDialog->UpdateWptListCtrl();
11084 }
11085 top_frame::Get()->InvalidateAllGL();
11086 top_frame::Get()->RefreshAllCanvas(false);
11087 }
11088 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11089 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11090}
11091
11092void pupHandler_PasteTrack() {
11093 Kml kml;
11094
11095 int pasteBuffer = kml.ParsePasteBuffer();
11096 Track *pasted = kml.GetParsedTrack();
11097 if (!pasted) return;
11098
11099 TrackPoint *curPoint;
11100
11101 Track *newTrack = new Track();
11102 TrackPoint *newPoint;
11103 TrackPoint *prevPoint = NULL;
11104
11105 newTrack->SetName(pasted->GetName());
11106
11107 for (int i = 0; i < pasted->GetnPoints(); i++) {
11108 curPoint = pasted->GetPoint(i);
11109
11110 newPoint = new TrackPoint(curPoint);
11111
11112 wxDateTime now = wxDateTime::Now();
11113 newPoint->SetCreateTime(curPoint->GetCreateTime());
11114
11115 newTrack->AddPoint(newPoint);
11116
11117 if (prevPoint)
11118 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11119 newPoint->m_lat, newPoint->m_lon,
11120 prevPoint, newPoint, newTrack);
11121
11122 prevPoint = newPoint;
11123 }
11124
11125 g_TrackList.push_back(newTrack);
11126 // pConfig->AddNewTrack(newTrack);
11127 NavObj_dB::GetInstance().InsertTrack(newTrack);
11128
11129 top_frame::Get()->InvalidateAllGL();
11130 top_frame::Get()->RefreshAllCanvas(false);
11131}
11132
11133bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11134 wxJSONValue v;
11135 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11136 v["CursorPosition_x"] = x;
11137 v["CursorPosition_y"] = y;
11138 // Send a limited set of selection types depending on what is
11139 // found under the mouse point.
11140 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11141 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11142 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11143
11144 wxJSONWriter w;
11145 wxString out;
11146 w.Write(v, out);
11147 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11148
11149 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11150
11151#if 0
11152#define SELTYPE_UNKNOWN 0x0001
11153#define SELTYPE_ROUTEPOINT 0x0002
11154#define SELTYPE_ROUTESEGMENT 0x0004
11155#define SELTYPE_TIDEPOINT 0x0008
11156#define SELTYPE_CURRENTPOINT 0x0010
11157#define SELTYPE_ROUTECREATE 0x0020
11158#define SELTYPE_AISTARGET 0x0040
11159#define SELTYPE_MARKPOINT 0x0080
11160#define SELTYPE_TRACKSEGMENT 0x0100
11161#define SELTYPE_DRAGHANDLE 0x0200
11162#endif
11163
11164 if (g_bhide_context_menus) return true;
11165 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11166 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11167 m_pIDXCandidate, m_nmea_log);
11168
11169 Connect(
11170 wxEVT_COMMAND_MENU_SELECTED,
11171 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11172
11173#ifdef __WXGTK__
11174 // Funny requirement here for gtk, to clear the menu trigger event
11175 // TODO
11176 // Causes a slight "flasH" of the menu,
11177 if (m_inLongPress) {
11178 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11179 m_inLongPress = false;
11180 }
11181#endif
11182
11183 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11184
11185 Disconnect(
11186 wxEVT_COMMAND_MENU_SELECTED,
11187 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11188
11189 delete m_canvasMenu;
11190 m_canvasMenu = NULL;
11191
11192#ifdef __WXQT__
11193 // gFrame->SurfaceToolbar();
11194 // g_MainToolbar->Raise();
11195#endif
11196
11197 return true;
11198}
11199
11200void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11201 // Pass menu events from the canvas to the menu handler
11202 // This is necessarily in ChartCanvas since that is the menu's parent.
11203 if (m_canvasMenu) {
11204 m_canvasMenu->PopupMenuHandler(event);
11205 }
11206 return;
11207}
11208
11209void ChartCanvas::StartRoute() {
11210 // Do not allow more than one canvas to create a route at one time.
11211 if (g_brouteCreating) return;
11212
11213 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11214
11215 g_brouteCreating = true;
11216 m_routeState = 1;
11217 m_bDrawingRoute = false;
11218 SetCursor(*pCursorPencil);
11219 // SetCanvasToolbarItemState(ID_ROUTE, true);
11220 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11221
11222 HideGlobalToolbar();
11223
11224#ifdef __ANDROID__
11225 androidSetRouteAnnunciator(true);
11226#endif
11227}
11228
11229wxString ChartCanvas::FinishRoute() {
11230 m_routeState = 0;
11231 m_prev_pMousePoint = NULL;
11232 m_bDrawingRoute = false;
11233 wxString rv = "";
11234 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11235
11236 // SetCanvasToolbarItemState(ID_ROUTE, false);
11237 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11238#ifdef __ANDROID__
11239 androidSetRouteAnnunciator(false);
11240#endif
11241
11242 SetCursor(*pCursorArrow);
11243
11244 if (m_pMouseRoute) {
11245 if (m_bAppendingRoute) {
11246 // pConfig->UpdateRoute(m_pMouseRoute);
11247 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11248 } else {
11249 if (m_pMouseRoute->GetnPoints() > 1) {
11250 // pConfig->AddNewRoute(m_pMouseRoute);
11251 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11252 } else {
11253 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11254 m_pMouseRoute = NULL;
11255 }
11256 }
11257 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11258
11259 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11260 (pRoutePropDialog->IsShown())) {
11261 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11262 }
11263
11264 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11265 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11266 pRouteManagerDialog->UpdateRouteListCtrl();
11267 }
11268 }
11269 m_bAppendingRoute = false;
11270 m_pMouseRoute = NULL;
11271
11272 m_pSelectedRoute = NULL;
11273
11274 undo->InvalidateUndo();
11275 top_frame::Get()->RefreshAllCanvas(true);
11276
11277 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11278
11279 ShowGlobalToolbar();
11280
11281 g_brouteCreating = false;
11282
11283 return rv;
11284}
11285
11286void ChartCanvas::HideGlobalToolbar() {
11287 if (m_canvasIndex == 0) {
11288 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11289 }
11290}
11291
11292void ChartCanvas::ShowGlobalToolbar() {
11293 if (m_canvasIndex == 0) {
11294 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11295 }
11296}
11297
11298void ChartCanvas::ShowAISTargetList() {
11299 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11300 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11301 }
11302
11303 g_pAISTargetList->UpdateAISTargetList();
11304}
11305
11306void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11307 if (!m_bShowOutlines) return;
11308
11309 if (!ChartData) return;
11310
11311 int nEntry = ChartData->GetChartTableEntries();
11312
11313 for (int i = 0; i < nEntry; i++) {
11314 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11315
11316 // Check to see if the candidate chart is in the currently active group
11317 bool b_group_draw = false;
11318 if (m_groupIndex > 0) {
11319 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11320 int index = pt->GetGroupArray()[ig];
11321 if (m_groupIndex == index) {
11322 b_group_draw = true;
11323 break;
11324 }
11325 }
11326 } else
11327 b_group_draw = true;
11328
11329 if (b_group_draw) RenderChartOutline(dc, i, vp);
11330 }
11331
11332 // On CM93 Composite Charts, draw the outlines of the next smaller
11333 // scale cell
11334 cm93compchart *pcm93 = NULL;
11335 if (VPoint.b_quilt) {
11336 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11337 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11338 pcm93 = (cm93compchart *)pch;
11339 break;
11340 }
11341 } else if (m_singleChart &&
11342 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11343 pcm93 = (cm93compchart *)m_singleChart;
11344
11345 if (pcm93) {
11346 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11347 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11348
11349 if (zoom_factor > 8.0) {
11350 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11351 dc.SetPen(mPen);
11352 } else {
11353 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11354 dc.SetPen(mPen);
11355 }
11356
11357 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11358 }
11359}
11360
11361void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11362#ifdef ocpnUSE_GL
11363 if (g_bopengl && m_glcc) {
11364 /* opengl version specially optimized */
11365 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11366 return;
11367 }
11368#endif
11369
11370 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11371 if (!ChartData->IsChartAvailable(dbIndex)) return;
11372 }
11373
11374 float plylat, plylon;
11375 float plylat1, plylon1;
11376
11377 int pixx, pixy, pixx1, pixy1;
11378
11379 LLBBox box;
11380 ChartData->GetDBBoundingBox(dbIndex, box);
11381
11382 // Don't draw an outline in the case where the chart covers the entire world
11383 // */
11384 if (box.GetLonRange() == 360) return;
11385
11386 double lon_bias = 0;
11387 // chart is outside of viewport lat/lon bounding box
11388 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11389
11390 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11391
11392 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11393 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11394
11395 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11396 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11397
11398 else
11399 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11400
11401 // Are there any aux ply entries?
11402 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11403 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11404 {
11405 wxPoint r, r1;
11406
11407 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11408 plylon += lon_bias;
11409
11410 GetCanvasPointPix(plylat, plylon, &r);
11411 pixx = r.x;
11412 pixy = r.y;
11413
11414 for (int i = 0; i < nPly - 1; i++) {
11415 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11416 plylon1 += lon_bias;
11417
11418 GetCanvasPointPix(plylat1, plylon1, &r1);
11419 pixx1 = r1.x;
11420 pixy1 = r1.y;
11421
11422 int pixxs1 = pixx1;
11423 int pixys1 = pixy1;
11424
11425 bool b_skip = false;
11426
11427 if (vp.chart_scale > 5e7) {
11428 // calculate projected distance between these two points in meters
11429 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11430 pow((double)(pixy1 - pixy), 2)) /
11431 vp.view_scale_ppm;
11432
11433 if (dist > 0.0) {
11434 // calculate GC distance between these two points in meters
11435 double distgc =
11436 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11437
11438 // If the distances are nonsense, it means that the scale is very
11439 // small and the segment wrapped the world So skip it....
11440 // TODO improve this to draw two segments
11441 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11442 b_skip = true;
11443 } else
11444 b_skip = true;
11445 }
11446
11447 ClipResult res = cohen_sutherland_line_clip_i(
11448 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11449 if (res != Invisible && !b_skip)
11450 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11451
11452 plylat = plylat1;
11453 plylon = plylon1;
11454 pixx = pixxs1;
11455 pixy = pixys1;
11456 }
11457
11458 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11459 plylon1 += lon_bias;
11460
11461 GetCanvasPointPix(plylat1, plylon1, &r1);
11462 pixx1 = r1.x;
11463 pixy1 = r1.y;
11464
11465 ClipResult res = cohen_sutherland_line_clip_i(
11466 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11467 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11468 }
11469
11470 else // Use Aux PlyPoints
11471 {
11472 wxPoint r, r1;
11473
11474 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11475 for (int j = 0; j < nAuxPlyEntries; j++) {
11476 int nAuxPly =
11477 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11478 GetCanvasPointPix(plylat, plylon, &r);
11479 pixx = r.x;
11480 pixy = r.y;
11481
11482 for (int i = 0; i < nAuxPly - 1; i++) {
11483 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11484
11485 GetCanvasPointPix(plylat1, plylon1, &r1);
11486 pixx1 = r1.x;
11487 pixy1 = r1.y;
11488
11489 int pixxs1 = pixx1;
11490 int pixys1 = pixy1;
11491
11492 bool b_skip = false;
11493
11494 if (vp.chart_scale > 5e7) {
11495 // calculate projected distance between these two points in meters
11496 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11497 ((pixy1 - pixy) * (pixy1 - pixy))) /
11498 vp.view_scale_ppm;
11499 if (dist > 0.0) {
11500 // calculate GC distance between these two points in meters
11501 double distgc =
11502 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11503
11504 // If the distances are nonsense, it means that the scale is very
11505 // small and the segment wrapped the world So skip it....
11506 // TODO improve this to draw two segments
11507 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11508 b_skip = true;
11509 } else
11510 b_skip = true;
11511 }
11512
11513 ClipResult res = cohen_sutherland_line_clip_i(
11514 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11515 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11516
11517 plylat = plylat1;
11518 plylon = plylon1;
11519 pixx = pixxs1;
11520 pixy = pixys1;
11521 }
11522
11523 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11524 GetCanvasPointPix(plylat1, plylon1, &r1);
11525 pixx1 = r1.x;
11526 pixy1 = r1.y;
11527
11528 ClipResult res = cohen_sutherland_line_clip_i(
11529 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11530 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11531 }
11532 }
11533}
11534
11535static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11536 const wxArrayString &legend) {
11537 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11538
11539 int pointsize = dFont->GetPointSize();
11540 pointsize /= OCPN_GetWinDIPScaleFactor();
11541
11542 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11543 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11544 false, dFont->GetFaceName());
11545
11546 dc.SetFont(*psRLI_font);
11547
11548 int h = 0;
11549 int w = 0;
11550 int hl, wl;
11551
11552 int xp, yp;
11553 int hilite_offset = 3;
11554
11555 for (wxString line : legend) {
11556#ifdef __WXMAC__
11557 wxScreenDC sdc;
11558 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11559#else
11560 dc.GetTextExtent(line, &wl, &hl);
11561 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11562 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11563#endif
11564 h += hl;
11565 w = wxMax(w, wl);
11566 }
11567 w += (hl / 2); // Add a little right pad
11568
11569 xp = ref_point.x - w;
11570 yp = ref_point.y;
11571 yp += hilite_offset;
11572
11573 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11574
11575 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11576 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11577
11578 for (wxString line : legend) {
11579 dc.DrawText(line, xp, yp);
11580 yp += hl;
11581 }
11582}
11583
11584void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11585 if (!g_bAllowShipToActive) return;
11586
11587 Route *rt = g_pRouteMan->GetpActiveRoute();
11588 if (!rt) return;
11589
11590 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11591 wxPoint2DDouble pa, pb;
11593 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11594
11595 // set pen
11596 int width =
11597 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11598 if (rt->m_width != wxPENSTYLE_INVALID)
11599 width = rt->m_width; // set route pen style if any
11600 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11601 g_shipToActiveStyle, 5)]; // get setting pen style
11602 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11603 wxColour color =
11604 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11605 : // set setting route pen color
11606 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11607 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11608
11609 dc.SetPen(*mypen);
11610 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11611
11612 if (!Use_Opengl)
11613 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11614 (int)pb.m_y, GetVP(), true);
11615
11616#ifdef ocpnUSE_GL
11617 else {
11618#ifdef USE_ANDROID_GLES2
11619 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11620#else
11621 if (style != wxPENSTYLE_SOLID) {
11622 if (glChartCanvas::dash_map.find(style) !=
11623 glChartCanvas::dash_map.end()) {
11624 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11625 dc.SetPen(*mypen);
11626 }
11627 }
11628 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11629#endif
11630
11631 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11632 (int)pb.m_x, (int)pb.m_y, GetVP());
11633 }
11634#endif
11635 }
11636}
11637
11638void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11639 Route *route = 0;
11640 if (m_routeState >= 2) route = m_pMouseRoute;
11641 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11642 route = m_pMeasureRoute;
11643
11644 if (!route) return;
11645
11646 // Validate route pointer
11647 if (!g_pRouteMan->IsRouteValid(route)) return;
11648
11649 double render_lat = m_cursor_lat;
11650 double render_lon = m_cursor_lon;
11651
11652 int np = route->GetnPoints();
11653 if (np) {
11654 if (g_btouch && (np > 1)) np--;
11655 RoutePoint rp = route->GetPoint(np);
11656 render_lat = rp.m_lat;
11657 render_lon = rp.m_lon;
11658 }
11659
11660 double rhumbBearing, rhumbDist;
11661 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11662 &rhumbBearing, &rhumbDist);
11663 double brg = rhumbBearing;
11664 double dist = rhumbDist;
11665
11666 // Skip GreatCircle rubberbanding on touch devices.
11667 if (!g_btouch) {
11668 double gcBearing, gcBearing2, gcDist;
11669 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11670 m_cursor_lat, &gcDist, &gcBearing,
11671 &gcBearing2);
11672 double gcDistm = gcDist / 1852.0;
11673
11674 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11675 rhumbBearing = 90.;
11676
11677 wxPoint destPoint, lastPoint;
11678
11679 route->m_NextLegGreatCircle = false;
11680 int milesDiff = rhumbDist - gcDistm;
11681 if (milesDiff > 1) {
11682 brg = gcBearing;
11683 dist = gcDistm;
11684 route->m_NextLegGreatCircle = true;
11685 }
11686
11687 // FIXME (MacOS, the first segment is rendered wrong)
11688 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11689 &lastPoint);
11690
11691 if (route->m_NextLegGreatCircle) {
11692 for (int i = 1; i <= milesDiff; i++) {
11693 double p = (double)i * (1.0 / (double)milesDiff);
11694 double pLat, pLon;
11695 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11696 &pLon, &pLat, &gcBearing2);
11697 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11698 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11699 false);
11700 lastPoint = destPoint;
11701 }
11702 } else {
11703 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11704 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11705 false);
11706 if (m_bMeasure_DistCircle) {
11707 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11708 powf((float)(r_rband.y - lastPoint.y), 2));
11709
11710 dc.SetPen(*g_pRouteMan->GetRoutePen());
11711 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11712 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11713 }
11714 }
11715 }
11716 }
11717
11718 wxString routeInfo;
11719 wxArrayString infoArray;
11720 double varBrg = 0;
11721 if (g_bShowTrue)
11722 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11723 0x00B0);
11724
11725 if (g_bShowMag) {
11726 double latAverage = (m_cursor_lat + render_lat) / 2;
11727 double lonAverage = (m_cursor_lon + render_lon) / 2;
11728 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11729
11730 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11731 (int)varBrg, 0x00B0);
11732 }
11733 routeInfo << " " << FormatDistanceAdaptive(dist);
11734 infoArray.Add(routeInfo);
11735 routeInfo.Clear();
11736
11737 // To make it easier to use a route as a bearing on a charted object add for
11738 // the first leg also the reverse bearing.
11739 if (np == 1) {
11740 routeInfo << "Reverse: ";
11741 if (g_bShowTrue)
11742 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11743 (int)(brg + 180.) % 360, 0x00B0);
11744 if (g_bShowMag)
11745 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11746 (int)(varBrg + 180.) % 360, 0x00B0);
11747 infoArray.Add(routeInfo);
11748 routeInfo.Clear();
11749 }
11750
11751 wxString s0;
11752 if (!route->m_bIsInLayer)
11753 s0.Append(_("Route") + ": ");
11754 else
11755 s0.Append(_("Layer Route: "));
11756
11757 double disp_length = route->m_route_length;
11758 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11759 s0 += FormatDistanceAdaptive(disp_length);
11760
11761 infoArray.Add(s0);
11762 routeInfo.Clear();
11763
11764 RouteLegInfo(dc, r_rband, infoArray);
11765
11766 m_brepaint_piano = true;
11767}
11768
11769void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11770 if (!m_bShowVisibleSectors) return;
11771
11772 if (g_bDeferredInitDone) {
11773 // need to re-evaluate sectors?
11774 double rhumbBearing, rhumbDist;
11775 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11776 &rhumbBearing, &rhumbDist);
11777
11778 if (rhumbDist > 0.05) // miles
11779 {
11780 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11781 m_sectorlegsVisible);
11782 m_sector_glat = gLat;
11783 m_sector_glon = gLon;
11784 }
11785 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11786 }
11787}
11788
11789void ChartCanvas::WarpPointerDeferred(int x, int y) {
11790 warp_x = x;
11791 warp_y = y;
11792 warp_flag = true;
11793}
11794
11795int s_msg;
11796
11797void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11798 if (!ps52plib) return;
11799
11800 if (VPoint.b_quilt) { // quilted
11801 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11802
11803 if (m_pQuilt->IsQuiltVector()) {
11804 if (ps52plib->GetStateHash() != m_s52StateHash) {
11805 UpdateS52State();
11806 m_s52StateHash = ps52plib->GetStateHash();
11807 }
11808 }
11809 } else {
11810 if (ps52plib->GetStateHash() != m_s52StateHash) {
11811 UpdateS52State();
11812 m_s52StateHash = ps52plib->GetStateHash();
11813 }
11814 }
11815
11816 // Plugin charts
11817 bool bSendPlibState = true;
11818 if (VPoint.b_quilt) { // quilted
11819 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11820 }
11821
11822 if (bSendPlibState) {
11823 wxJSONValue v;
11824 v["OpenCPN Version Major"] = VERSION_MAJOR;
11825 v["OpenCPN Version Minor"] = VERSION_MINOR;
11826 v["OpenCPN Version Patch"] = VERSION_PATCH;
11827 v["OpenCPN Version Date"] = VERSION_DATE;
11828 v["OpenCPN Version Full"] = VERSION_FULL;
11829
11830 // S52PLIB state
11831 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11832 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11833 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11834 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11835 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11836 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11837 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11838
11839 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11840
11841 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11842 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11843
11844 // Global S52 options
11845
11846 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11847 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11848 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11849 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11850 ps52plib->m_bShowS57ImportantTextOnly;
11851 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11852 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11853 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11854 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11855 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11856
11857 // Some global GUI parameters, for completeness
11858 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11859 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11860 v["OpenCPN Scale Factor Exp"] =
11861 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11862 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11863
11864 wxJSONWriter w;
11865 wxString out;
11866 w.Write(v, out);
11867
11868 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11869 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11870 g_lastS52PLIBPluginMessage = out;
11871 }
11872 }
11873}
11874int spaint;
11875int s_in_update;
11876void ChartCanvas::OnPaint(wxPaintEvent &event) {
11877 wxPaintDC dc(this);
11878
11879 // GetToolbar()->Show( m_bToolbarEnable );
11880
11881 // Paint updates may have been externally disabled (temporarily, to avoid
11882 // Yield() recursion performance loss) It is important that the wxPaintDC is
11883 // built, even if we elect to not process this paint message. Otherwise, the
11884 // paint message may not be removed from the message queue, esp on Windows.
11885 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11886
11887 if (!m_b_paint_enable) {
11888 return;
11889 }
11890
11891 // If necessary, reconfigure the S52 PLIB
11893
11894#ifdef ocpnUSE_GL
11895 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11896
11897 if (m_glcc && g_bopengl) {
11898 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11899 s_in_update++;
11900 m_glcc->Update();
11901 s_in_update--;
11902 }
11903
11904 return;
11905 }
11906#endif
11907
11908 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11909
11910 wxRegion ru = GetUpdateRegion();
11911
11912 int rx, ry, rwidth, rheight;
11913 ru.GetBox(rx, ry, rwidth, rheight);
11914
11915#ifdef ocpnUSE_DIBSECTION
11916 ocpnMemDC temp_dc;
11917#else
11918 wxMemoryDC temp_dc;
11919#endif
11920
11921 long height = GetVP().pix_height;
11922
11923#ifdef __WXMAC__
11924 // On OS X we have to explicitly extend the region for the piano area
11925 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11926 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11927 height += m_Piano->GetHeight();
11928#endif // __WXMAC__
11929 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11930
11931 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11932 if (pthumbwin) {
11933 int thumbx, thumby, thumbsx, thumbsy;
11934 pthumbwin->GetPosition(&thumbx, &thumby);
11935 pthumbwin->GetSize(&thumbsx, &thumbsy);
11936 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11937
11938 if (pthumbwin->IsShown()) {
11939 rgn_chart.Subtract(rgn_thumbwin);
11940 ru.Subtract(rgn_thumbwin);
11941 }
11942 }
11943
11944 // subtract the chart bar if it isn't transparent, and determine if we need to
11945 // paint it
11946 wxRegion rgn_blit = ru;
11947 if (g_bShowChartBar) {
11948 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11949 GetClientSize().x, m_Piano->GetHeight());
11950
11951 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11952 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11953 if (style->chartStatusWindowTransparent)
11954 m_brepaint_piano = true;
11955 else
11956 ru.Subtract(chart_bar_rect);
11957 }
11958 }
11959
11960 if (m_Compass && m_Compass->IsShown()) {
11961 wxRect compassRect = m_Compass->GetRect();
11962 if (ru.Contains(compassRect) != wxOutRegion) {
11963 ru.Subtract(compassRect);
11964 }
11965 }
11966
11967 if (m_notification_button) {
11968 wxRect noteRect = m_notification_button->GetRect();
11969 if (ru.Contains(noteRect) != wxOutRegion) {
11970 ru.Subtract(noteRect);
11971 }
11972 }
11973
11974 // Is this viewpoint the same as the previously painted one?
11975 bool b_newview = true;
11976
11977 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11978 (m_cache_vp.rotation == VPoint.rotation) &&
11979 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11980 m_cache_vp.IsValid()) {
11981 b_newview = false;
11982 }
11983
11984 // If the ViewPort is skewed or rotated, we may be able to use the cached
11985 // rotated bitmap.
11986 bool b_rcache_ok = false;
11987 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11988 b_rcache_ok = !b_newview;
11989
11990 // Make a special VP
11991 if (VPoint.b_MercatorProjectionOverride)
11992 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11993 ViewPort svp = VPoint;
11994
11995 svp.pix_width = svp.rv_rect.width;
11996 svp.pix_height = svp.rv_rect.height;
11997
11998 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11999 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
12000 // VPoint.rv_rect.height);
12001
12002 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
12003
12004 // If we are going to use the cached rotated image, there is no need to fetch
12005 // any chart data and this will do it...
12006 if (b_rcache_ok) chart_get_region.Clear();
12007
12008 // Blit pan acceleration
12009 if (VPoint.b_quilt) // quilted
12010 {
12011 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12012
12013 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12014
12015 bool busy = false;
12016 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12017 m_cache_vp.rotation != VPoint.rotation)) {
12018 AbstractPlatform::ShowBusySpinner();
12019 busy = true;
12020 }
12021
12022 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12023 (m_working_bm.GetHeight() != svp.pix_height))
12024 m_working_bm.Create(svp.pix_width, svp.pix_height,
12025 -1); // make sure the target is big enoug
12026
12027 if (fabs(VPoint.rotation) < 0.01) {
12028 bool b_save = true;
12029
12030 if (g_SencThreadManager) {
12031 if (g_SencThreadManager->GetJobCount()) {
12032 b_save = false;
12033 m_cache_vp.Invalidate();
12034 }
12035 }
12036
12037 // If the saved wxBitmap from last OnPaint is useable
12038 // calculate the blit parameters
12039
12040 // We can only do screen blit painting if subsequent ViewPorts differ by
12041 // whole pixels So, in small scale bFollow mode, force the full screen
12042 // render. This seems a hack....There may be better logic here.....
12043
12044 // if(m_bFollow)
12045 // b_save = false;
12046
12047 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12048 if (b_newview) {
12049 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12050 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12051
12052 int dy = c_new.y - c_old.y;
12053 int dx = c_new.x - c_old.x;
12054
12055 // printf("In OnPaint Trying Blit dx: %d
12056 // dy:%d\n\n", dx, dy);
12057
12058 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12059 if (dx || dy) {
12060 // Blit the reuseable portion of the cached wxBitmap to a working
12061 // bitmap
12062 temp_dc.SelectObject(m_working_bm);
12063
12064 wxMemoryDC cache_dc;
12065 cache_dc.SelectObject(m_cached_chart_bm);
12066
12067 if (dy > 0) {
12068 if (dx > 0) {
12069 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12070 VPoint.pix_height - dy, &cache_dc, dx, dy);
12071 } else {
12072 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12073 VPoint.pix_height - dy, &cache_dc, 0, dy);
12074 }
12075
12076 } else {
12077 if (dx > 0) {
12078 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12079 VPoint.pix_height + dy, &cache_dc, dx, 0);
12080 } else {
12081 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12082 VPoint.pix_height + dy, &cache_dc, 0, 0);
12083 }
12084 }
12085
12086 OCPNRegion update_region;
12087 if (dy) {
12088 if (dy > 0)
12089 update_region.Union(
12090 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12091 else
12092 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12093 }
12094
12095 if (dx) {
12096 if (dx > 0)
12097 update_region.Union(
12098 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12099 else
12100 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12101 }
12102
12103 // Render the new region
12104 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12105 update_region);
12106 cache_dc.SelectObject(wxNullBitmap);
12107 } else {
12108 // No sensible (dx, dy) change in the view, so use the cached
12109 // member bitmap
12110 temp_dc.SelectObject(m_cached_chart_bm);
12111 b_save = false;
12112 }
12113 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12114
12115 } else // not blitable
12116 {
12117 temp_dc.SelectObject(m_working_bm);
12118 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12119 chart_get_region);
12120 }
12121 } else {
12122 // No change in the view, so use the cached member bitmap2
12123 temp_dc.SelectObject(m_cached_chart_bm);
12124 b_save = false;
12125 }
12126 } else // cached bitmap is not yet valid
12127 {
12128 temp_dc.SelectObject(m_working_bm);
12129 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12130 chart_get_region);
12131 }
12132
12133 // Save the fully rendered quilt image as a wxBitmap member of this class
12134 if (b_save) {
12135 // if((m_cached_chart_bm.GetWidth() !=
12136 // svp.pix_width) ||
12137 // (m_cached_chart_bm.GetHeight() !=
12138 // svp.pix_height))
12139 // m_cached_chart_bm.Create(svp.pix_width,
12140 // svp.pix_height, -1); // target wxBitmap
12141 // is big enough
12142 wxMemoryDC scratch_dc_0;
12143 scratch_dc_0.SelectObject(m_cached_chart_bm);
12144 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12145
12146 scratch_dc_0.SelectObject(wxNullBitmap);
12147
12148 m_bm_cache_vp =
12149 VPoint; // save the ViewPort associated with the cached wxBitmap
12150 }
12151 }
12152
12153 else // quilted, rotated
12154 {
12155 temp_dc.SelectObject(m_working_bm);
12156 OCPNRegion chart_get_all_region(
12157 wxRect(0, 0, svp.pix_width, svp.pix_height));
12158 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12159 chart_get_all_region);
12160 }
12161
12162 AbstractPlatform::HideBusySpinner();
12163
12164 }
12165
12166 else // not quilted
12167 {
12168 if (!m_singleChart) {
12169 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12170 dc.Clear();
12171 return;
12172 }
12173
12174 if (!chart_get_region.IsEmpty()) {
12175 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12176 }
12177 }
12178
12179 if (temp_dc.IsOk()) {
12180 // Arrange to render the World Chart vector data behind the rendered
12181 // current chart so that uncovered canvas areas show at least the world
12182 // chart.
12183 OCPNRegion chartValidRegion;
12184 if (!VPoint.b_quilt) {
12185 // Make a region covering the current chart on the canvas
12186
12187 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12188 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12189 else {
12190 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12191 // require that the viewport passed here have pix_width and pix_height
12192 // set to the actual display, not the virtual (rv_rect) sizes
12193 // (the vector calculations require the virtual sizes in svp)
12194
12195 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12196 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12197 }
12198 } else
12199 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12200
12201 temp_dc.DestroyClippingRegion();
12202
12203 // Copy current chart region
12204 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12205
12206 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12207
12208 if (!backgroundRegion.IsEmpty()) {
12209 // Draw the Background Chart only in the areas NOT covered by the
12210 // current chart view
12211
12212 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12213 clipping regions with more than 1 rectangle so... */
12214 wxColour water = pWorldBackgroundChart->water;
12215 if (water.IsOk()) {
12216 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12217 temp_dc.SetBrush(wxBrush(water));
12218 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12219 while (upd.HaveRects()) {
12220 wxRect rect = upd.GetRect();
12221 temp_dc.DrawRectangle(rect);
12222 upd.NextRect();
12223 }
12224 }
12225 // Associate with temp_dc
12226 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12227 temp_dc.SetDeviceClippingRegion(*clip_region);
12228 delete clip_region;
12229
12230 ocpnDC bgdc(temp_dc);
12231 double r = VPoint.rotation;
12232 SetVPRotation(VPoint.skew);
12233
12234 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12235 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12236
12237 SetVPRotation(r);
12238 }
12239 } // temp_dc.IsOk();
12240
12241 wxMemoryDC *pChartDC = &temp_dc;
12242 wxMemoryDC rotd_dc;
12243
12244 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12245 // Can we use the current rotated image cache?
12246 if (!b_rcache_ok) {
12247#ifdef __WXMSW__
12248 wxMemoryDC tbase_dc;
12249 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12250 tbase_dc.SelectObject(bm_base);
12251 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12252 tbase_dc.SelectObject(wxNullBitmap);
12253#else
12254 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12255#endif
12256
12257 wxImage base_image;
12258 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12259
12260 // Use a local static image rotator to improve wxWidgets code profile
12261 // Especially, on GTK the wxRound and wxRealPoint functions are very
12262 // expensive.....
12263
12264 double angle = GetVP().skew - GetVP().rotation;
12265 wxImage ri;
12266 bool b_rot_ok = false;
12267 if (base_image.IsOk()) {
12268 ViewPort rot_vp = GetVP();
12269
12270 m_b_rot_hidef = false;
12271
12272 ri = Image_Rotate(
12273 base_image, angle,
12274 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12275 m_b_rot_hidef, &m_roffset);
12276
12277 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12278 (rot_vp.rotation == VPoint.rotation) &&
12279 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12280 rot_vp.IsValid() && (ri.IsOk())) {
12281 b_rot_ok = true;
12282 }
12283 }
12284
12285 if (b_rot_ok) {
12286 delete m_prot_bm;
12287 m_prot_bm = new wxBitmap(ri);
12288 }
12289
12290 m_roffset.x += VPoint.rv_rect.x;
12291 m_roffset.y += VPoint.rv_rect.y;
12292 }
12293
12294 if (m_prot_bm && m_prot_bm->IsOk()) {
12295 rotd_dc.SelectObject(*m_prot_bm);
12296 pChartDC = &rotd_dc;
12297 } else {
12298 pChartDC = &temp_dc;
12299 m_roffset = wxPoint(0, 0);
12300 }
12301 } else { // unrotated
12302 pChartDC = &temp_dc;
12303 m_roffset = wxPoint(0, 0);
12304 }
12305
12306 wxPoint offset = m_roffset;
12307
12308 // Save the PixelCache viewpoint for next time
12309 m_cache_vp = VPoint;
12310
12311 // Set up a scratch DC for overlay objects
12312 wxMemoryDC mscratch_dc;
12313 mscratch_dc.SelectObject(*pscratch_bm);
12314
12315 mscratch_dc.ResetBoundingBox();
12316 mscratch_dc.DestroyClippingRegion();
12317 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12318
12319 // Blit the externally invalidated areas of the chart onto the scratch dc
12320 wxRegionIterator upd(rgn_blit); // get the update rect list
12321 while (upd) {
12322 wxRect rect = upd.GetRect();
12323
12324 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12325 rect.x - offset.x, rect.y - offset.y);
12326 upd++;
12327 }
12328
12329 // If multi-canvas, indicate which canvas has keyboard focus
12330 // by drawing a simple blue bar at the top.
12331 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12332 if (this == wxWindow::FindFocus()) {
12333 g_focusCanvas = this;
12334
12335 wxColour colour = GetGlobalColor("BLUE4");
12336 mscratch_dc.SetPen(wxPen(colour));
12337 mscratch_dc.SetBrush(wxBrush(colour));
12338
12339 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12340 mscratch_dc.DrawRectangle(activeRect);
12341 }
12342 }
12343
12344 // Any MBtiles?
12345 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12346 unsigned int im = stackIndexArray.size();
12347 if (VPoint.b_quilt && im > 0) {
12348 std::vector<int> tiles_to_show;
12349 for (unsigned int is = 0; is < im; is++) {
12350 const ChartTableEntry &cte =
12351 ChartData->GetChartTableEntry(stackIndexArray[is]);
12352 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12353 continue;
12354 }
12355 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12356 tiles_to_show.push_back(stackIndexArray[is]);
12357 }
12358 }
12359
12360 if (tiles_to_show.size())
12361 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12362 }
12363
12364 // May get an unexpected OnPaint call while switching display modes
12365 // Guard for that.
12366 if (!g_bopengl) {
12367 ocpnDC scratch_dc(mscratch_dc);
12368 RenderAlertMessage(mscratch_dc, GetVP());
12369 }
12370
12371#if 0
12372 // quiting?
12373 if (g_bquiting) {
12374#ifdef ocpnUSE_DIBSECTION
12375 ocpnMemDC q_dc;
12376#else
12377 wxMemoryDC q_dc;
12378#endif
12379 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12380 q_dc.SelectObject(qbm);
12381
12382 // Get a copy of the screen
12383 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12384
12385 // Draw a rectangle over the screen with a stipple brush
12386 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12387 q_dc.SetBrush(qbr);
12388 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12389
12390 // Blit back into source
12391 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12392 wxCOPY);
12393
12394 q_dc.SelectObject(wxNullBitmap);
12395 }
12396#endif
12397
12398#if 0
12399 // It is possible that this two-step method may be reuired for some platforms.
12400 // So, retain in the code base to aid recovery if necessary
12401
12402 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12403 if( VPoint.b_quilt ) {
12404 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12405 ChartBase *chart = m_pQuilt->GetRefChart();
12406 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12407
12408 // Clear the text Global declutter list
12409 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12410 if(ChPI)
12411 ChPI->ClearPLIBTextList();
12412 else{
12413 if(ps52plib)
12414 ps52plib->ClearTextList();
12415 }
12416
12417 wxMemoryDC t_dc;
12418 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12419
12420 wxColor maskBackground = wxColour(1,0,0);
12421 t_dc.SelectObject( qbm );
12422 t_dc.SetBackground(wxBrush(maskBackground));
12423 t_dc.Clear();
12424
12425 // Copy the scratch DC into the new bitmap
12426 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12427
12428 // Render the text to the new bitmap
12429 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12430 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12431
12432 // Copy the new bitmap back to the scratch dc
12433 wxRegionIterator upd_final( ru );
12434 while( upd_final ) {
12435 wxRect rect = upd_final.GetRect();
12436 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12437 upd_final++;
12438 }
12439
12440 t_dc.SelectObject( wxNullBitmap );
12441 }
12442 }
12443 }
12444#endif
12445 // Direct rendering model...
12446 if (VPoint.b_quilt) {
12447 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12448 ChartBase *chart = m_pQuilt->GetRefChart();
12449 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12450 // Clear the text Global declutter list
12451 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12452 if (ChPI)
12453 ChPI->ClearPLIBTextList();
12454 else {
12455 if (ps52plib) ps52plib->ClearTextList();
12456 }
12457
12458 // Render the text directly to the scratch bitmap
12459 OCPNRegion chart_all_text_region(
12460 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12461
12462 if (g_bShowChartBar && m_Piano) {
12463 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12464 GetVP().pix_width, m_Piano->GetHeight());
12465
12466 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12467 if (!style->chartStatusWindowTransparent)
12468 chart_all_text_region.Subtract(chart_bar_rect);
12469 }
12470
12471 if (m_Compass && m_Compass->IsShown()) {
12472 wxRect compassRect = m_Compass->GetRect();
12473 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12474 chart_all_text_region.Subtract(compassRect);
12475 }
12476 }
12477
12478 mscratch_dc.DestroyClippingRegion();
12479
12480 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12481 chart_all_text_region);
12482 }
12483 }
12484 }
12485
12486 // Now that charts are fully rendered, apply the overlay objects as decals.
12487 ocpnDC scratch_dc(mscratch_dc);
12488 DrawOverlayObjects(scratch_dc, ru);
12489
12490 // And finally, blit the scratch dc onto the physical dc
12491 wxRegionIterator upd_final(rgn_blit);
12492 while (upd_final) {
12493 wxRect rect = upd_final.GetRect();
12494 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12495 rect.y);
12496 upd_final++;
12497 }
12498
12499 // Deselect the chart bitmap from the temp_dc, so that it will not be
12500 // destroyed in the temp_dc dtor
12501 temp_dc.SelectObject(wxNullBitmap);
12502 // And for the scratch bitmap
12503 mscratch_dc.SelectObject(wxNullBitmap);
12504
12505 dc.DestroyClippingRegion();
12506
12507 PaintCleanup();
12508}
12509
12510void ChartCanvas::PaintCleanup() {
12511 // Handle the current graphic window, if present
12512 if (m_inPinch) return;
12513
12514 if (pCwin) {
12515 pCwin->Show();
12516 if (m_bTCupdate) {
12517 pCwin->Refresh();
12518 pCwin->Update();
12519 }
12520 }
12521
12522 // And set flags for next time
12523 m_bTCupdate = false;
12524
12525 // Handle deferred WarpPointer
12526 if (warp_flag) {
12527 WarpPointer(warp_x, warp_y);
12528 warp_flag = false;
12529 }
12530
12531 // Start movement timers, this runs nearly immediately.
12532 // the reason we cannot simply call it directly is the
12533 // refresh events it emits may be blocked from this paint event
12534 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12535 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12536}
12537
12538#if 0
12539wxColour GetErrorGraphicColor(double val)
12540{
12541 /*
12542 double valm = wxMin(val_max, val);
12543
12544 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12545 unsigned char red = (unsigned char)(255 * (valm/val_max));
12546
12547 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12548
12549 hv.saturation = 1.0;
12550 hv.value = 1.0;
12551
12552 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12553 return wxColour(rv.red, rv.green, rv.blue);
12554 */
12555
12556 // HTML colors taken from NOAA WW3 Web representation
12557 wxColour c;
12558 if((val > 0) && (val < 1)) c.Set("#002ad9");
12559 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12560 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12561 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12562 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12563 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12564 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12565 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12566 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12567 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12568 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12569 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12570 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12571 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12572 else if((val >= 30) && (val < 36)) c.Set("#870000");
12573 else if((val >= 36) && (val < 42)) c.Set("#690000");
12574 else if((val >= 42) && (val < 48)) c.Set("#550000");
12575 else if( val >= 48) c.Set("#410000");
12576
12577 return c;
12578}
12579
12580void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12581{
12582 wxImage gr_image(vp->pix_width, vp->pix_height);
12583 gr_image.InitAlpha();
12584
12585 double maxval = -10000;
12586 double minval = 10000;
12587
12588 double rlat, rlon;
12589 double glat, glon;
12590
12591 GetCanvasPixPoint(0, 0, rlat, rlon);
12592
12593 for(int i=1; i < vp->pix_height-1; i++)
12594 {
12595 for(int j=0; j < vp->pix_width; j++)
12596 {
12597 // Reference mercator value
12598// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12599
12600 // Georef value
12601 GetCanvasPixPoint(j, i, glat, glon);
12602
12603 maxval = wxMax(maxval, (glat - rlat));
12604 minval = wxMin(minval, (glat - rlat));
12605
12606 }
12607 rlat = glat;
12608 }
12609
12610 GetCanvasPixPoint(0, 0, rlat, rlon);
12611 for(int i=1; i < vp->pix_height-1; i++)
12612 {
12613 for(int j=0; j < vp->pix_width; j++)
12614 {
12615 // Reference mercator value
12616// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12617
12618 // Georef value
12619 GetCanvasPixPoint(j, i, glat, glon);
12620
12621 double f = ((glat - rlat)-minval)/(maxval - minval);
12622
12623 double dy = (f * 40);
12624
12625 wxColour c = GetErrorGraphicColor(dy);
12626 unsigned char r = c.Red();
12627 unsigned char g = c.Green();
12628 unsigned char b = c.Blue();
12629
12630 gr_image.SetRGB(j, i, r,g,b);
12631 if((glat - rlat )!= 0)
12632 gr_image.SetAlpha(j, i, 128);
12633 else
12634 gr_image.SetAlpha(j, i, 255);
12635
12636 }
12637 rlat = glat;
12638 }
12639
12640 // Create a Bitmap
12641 wxBitmap *pbm = new wxBitmap(gr_image);
12642 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12643 pbm->SetMask(gr_mask);
12644
12645 pmdc->DrawBitmap(*pbm, 0,0);
12646
12647 delete pbm;
12648
12649}
12650
12651#endif
12652
12653void ChartCanvas::CancelMouseRoute() {
12654 m_routeState = 0;
12655 m_pMouseRoute = NULL;
12656 m_bDrawingRoute = false;
12657}
12658
12659int ChartCanvas::GetNextContextMenuId() {
12660 return CanvasMenuHandler::GetNextContextMenuId();
12661}
12662
12663bool ChartCanvas::SetCursor(const wxCursor &c) {
12664#ifdef ocpnUSE_GL
12665 if (g_bopengl && m_glcc)
12666 return m_glcc->SetCursor(c);
12667 else
12668#endif
12669 return wxWindow::SetCursor(c);
12670}
12671
12672void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12673 if (g_bquiting) return;
12674 // Keep the mouse position members up to date
12675 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12676
12677 // Retrigger the route leg popup timer
12678 // This handles the case when the chart is moving in auto-follow mode,
12679 // but no user mouse input is made. The timer handler may Hide() the
12680 // popup if the chart moved enough n.b. We use slightly longer oneshot
12681 // value to allow this method's Refresh() to complete before potentially
12682 // getting another Refresh() in the popup timer handler.
12683 if (!m_RolloverPopupTimer.IsRunning() &&
12684 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12685 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12686 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12687 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12688
12689#ifdef ocpnUSE_GL
12690 if (m_glcc && g_bopengl) {
12691 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12692 // overlay objects.
12693 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12694
12695 m_glcc->Refresh(eraseBackground,
12696 NULL); // We always are going to render the entire screen
12697 // anyway, so make
12698 // sure that the window managers understand the invalid area
12699 // is actually the entire client area.
12700
12701 // We need to selectively Refresh some child windows, if they are visible.
12702 // Note that some children are refreshed elsewhere on timer ticks, so don't
12703 // need attention here.
12704
12705 // Thumbnail chart
12706 if (pthumbwin && pthumbwin->IsShown()) {
12707 pthumbwin->Raise();
12708 pthumbwin->Refresh(false);
12709 }
12710
12711 // ChartInfo window
12712 if (m_pCIWin && m_pCIWin->IsShown()) {
12713 m_pCIWin->Raise();
12714 m_pCIWin->Refresh(false);
12715 }
12716
12717 // if(g_MainToolbar)
12718 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12719
12720 } else
12721#endif
12722 wxWindow::Refresh(eraseBackground, rect);
12723}
12724
12725void ChartCanvas::Update() {
12726 if (m_glcc && g_bopengl) {
12727#ifdef ocpnUSE_GL
12728 m_glcc->Update();
12729#endif
12730 } else
12731 wxWindow::Update();
12732}
12733
12734void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12735 if (!pemboss) return;
12736 int x = pemboss->x, y = pemboss->y;
12737 const double factor = 200;
12738
12739 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12740 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12741 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12742
12743 // Grab a snipped image out of the chart
12744 wxMemoryDC snip_dc;
12745 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12746 snip_dc.SelectObject(snip_bmp);
12747
12748 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12749 snip_dc.SelectObject(wxNullBitmap);
12750
12751 wxImage snip_img = snip_bmp.ConvertToImage();
12752
12753 // Apply Emboss map to the snip image
12754 unsigned char *pdata = snip_img.GetData();
12755 if (pdata) {
12756 for (int y = 0; y < pemboss->height; y++) {
12757 int map_index = (y * pemboss->width);
12758 for (int x = 0; x < pemboss->width; x++) {
12759 double val = (pemboss->pmap[map_index] * factor) / 256.;
12760
12761 int nred = (int)((*pdata) + val);
12762 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12763 *pdata++ = (unsigned char)nred;
12764
12765 int ngreen = (int)((*pdata) + val);
12766 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12767 *pdata++ = (unsigned char)ngreen;
12768
12769 int nblue = (int)((*pdata) + val);
12770 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12771 *pdata++ = (unsigned char)nblue;
12772
12773 map_index++;
12774 }
12775 }
12776 }
12777
12778 // Convert embossed snip to a bitmap
12779 wxBitmap emb_bmp(snip_img);
12780
12781 // Map to another memoryDC
12782 wxMemoryDC result_dc;
12783 result_dc.SelectObject(emb_bmp);
12784
12785 // Blit to target
12786 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12787
12788 result_dc.SelectObject(wxNullBitmap);
12789}
12790
12791emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12792 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12793
12794 if (GetQuiltMode()) {
12795 // disable Overzoom indicator for MBTiles
12796 int refIndex = GetQuiltRefChartdbIndex();
12797 if (refIndex >= 0) {
12798 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12799 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12800 if (current_type == CHART_TYPE_MBTILES) {
12801 ChartBase *pChart = m_pQuilt->GetRefChart();
12802 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12803 if (ptc) {
12804 zoom_factor = ptc->GetZoomFactor();
12805 }
12806 }
12807 }
12808
12809 if (zoom_factor <= 3.9) return NULL;
12810 } else {
12811 if (m_singleChart) {
12812 if (zoom_factor <= 3.9) return NULL;
12813 } else
12814 return NULL;
12815 }
12816
12817 if (m_pEM_OverZoom) {
12818 m_pEM_OverZoom->x = 4;
12819 m_pEM_OverZoom->y = 0;
12820 if (g_MainToolbar && IsPrimaryCanvas()) {
12821 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12822 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12823 }
12824 }
12825 return m_pEM_OverZoom;
12826}
12827
12828void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12829 GridDraw(dc);
12830
12831 // bool pluginOverlayRender = true;
12832 //
12833 // if(g_canvasConfig > 0){ // Multi canvas
12834 // if(IsPrimaryCanvas())
12835 // pluginOverlayRender = false;
12836 // }
12837
12838 g_overlayCanvas = this;
12839
12840 if (g_pi_manager) {
12841 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12842 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12844 }
12845
12846 AISDrawAreaNotices(dc, GetVP(), this);
12847
12848 wxDC *pdc = dc.GetDC();
12849 if (pdc) {
12850 pdc->DestroyClippingRegion();
12851 wxDCClipper(*pdc, ru);
12852 }
12853
12854 if (m_bShowNavobjects) {
12855 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12856 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12857 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12858 DrawAnchorWatchPoints(dc);
12859 } else {
12860 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12861 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12862 }
12863
12864 AISDraw(dc, GetVP(), this);
12865 ShipDraw(dc);
12866 AlertDraw(dc);
12867
12868 RenderVisibleSectorLights(dc);
12869
12870 RenderAllChartOutlines(dc, GetVP());
12871 RenderRouteLegs(dc);
12872 RenderShipToActive(dc, false);
12873 ScaleBarDraw(dc);
12874 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12875 if (g_pi_manager) {
12876 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12878 }
12879
12880 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12881 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12882
12883 if (g_pi_manager) {
12884 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12886 }
12887
12888 if (m_bShowTide) {
12889 RebuildTideSelectList(GetVP().GetBBox());
12890 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12891 }
12892
12893 if (m_bShowCurrent) {
12894 RebuildCurrentSelectList(GetVP().GetBBox());
12895 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12896 }
12897
12898 if (!g_PrintingInProgress) {
12899 if (IsPrimaryCanvas()) {
12900 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12901 }
12902
12903 if (IsPrimaryCanvas()) {
12904 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12905 }
12906
12907 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12908
12909 if (m_pTrackRolloverWin) {
12910 m_pTrackRolloverWin->Draw(dc);
12911 m_brepaint_piano = true;
12912 }
12913
12914 if (m_pRouteRolloverWin) {
12915 m_pRouteRolloverWin->Draw(dc);
12916 m_brepaint_piano = true;
12917 }
12918
12919 if (m_pAISRolloverWin) {
12920 m_pAISRolloverWin->Draw(dc);
12921 m_brepaint_piano = true;
12922 }
12923 if (m_brepaint_piano && g_bShowChartBar) {
12924 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12925 }
12926
12927 if (m_Compass) m_Compass->Paint(dc);
12928
12929 if (!g_CanvasHideNotificationIcon) {
12930 if (IsPrimaryCanvas()) {
12931 auto &noteman = NotificationManager::GetInstance();
12932 if (m_notification_button) {
12933 if (noteman.GetNotificationCount()) {
12934 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12935 if (m_notification_button->UpdateStatus()) Refresh();
12936 m_notification_button->Show(true);
12937 m_notification_button->Paint(dc);
12938 } else {
12939 m_notification_button->Show(false);
12940 }
12941 }
12942 }
12943 }
12944 }
12945 if (g_pi_manager) {
12946 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12948 }
12949}
12950
12951emboss_data *ChartCanvas::EmbossDepthScale() {
12952 if (!m_bShowDepthUnits) return NULL;
12953
12954 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12955
12956 if (GetQuiltMode()) {
12957 wxString s = m_pQuilt->GetQuiltDepthUnit();
12958 s.MakeUpper();
12959 if (s == "FEET")
12960 depth_unit_type = DEPTH_UNIT_FEET;
12961 else if (s.StartsWith("FATHOMS"))
12962 depth_unit_type = DEPTH_UNIT_FATHOMS;
12963 else if (s.StartsWith("METERS"))
12964 depth_unit_type = DEPTH_UNIT_METERS;
12965 else if (s.StartsWith("METRES"))
12966 depth_unit_type = DEPTH_UNIT_METERS;
12967 else if (s.StartsWith("METRIC"))
12968 depth_unit_type = DEPTH_UNIT_METERS;
12969 else if (s.StartsWith("METER"))
12970 depth_unit_type = DEPTH_UNIT_METERS;
12971
12972 } else {
12973 if (m_singleChart) {
12974 depth_unit_type = m_singleChart->GetDepthUnitType();
12975 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12976 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12977 }
12978 }
12979
12980 emboss_data *ped = NULL;
12981 switch (depth_unit_type) {
12982 case DEPTH_UNIT_FEET:
12983 ped = m_pEM_Feet;
12984 break;
12985 case DEPTH_UNIT_METERS:
12986 ped = m_pEM_Meters;
12987 break;
12988 case DEPTH_UNIT_FATHOMS:
12989 ped = m_pEM_Fathoms;
12990 break;
12991 default:
12992 return NULL;
12993 }
12994
12995 ped->x = (GetVP().pix_width - ped->width);
12996
12997 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12998 wxRect r = m_Compass->GetRect();
12999 ped->y = r.y + r.height;
13000 } else {
13001 ped->y = 40;
13002 }
13003 return ped;
13004}
13005
13006void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
13007 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13008 wxFont font;
13009 if (style->embossFont == wxEmptyString) {
13010 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13011 font = *dFont;
13012 font.SetPointSize(60);
13013 font.SetWeight(wxFONTWEIGHT_BOLD);
13014 } else
13015 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13016 wxFONTWEIGHT_BOLD, false, style->embossFont);
13017
13018 int emboss_width = 500;
13019 int emboss_height = 200;
13020
13021 // Free any existing emboss maps
13022 delete m_pEM_Feet;
13023 delete m_pEM_Meters;
13024 delete m_pEM_Fathoms;
13025
13026 // Create the 3 DepthUnit emboss map structures
13027 m_pEM_Feet =
13028 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13029 m_pEM_Meters =
13030 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13031 m_pEM_Fathoms =
13032 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13033}
13034
13035#define OVERZOOM_TEXT _("OverZoom")
13036
13037void ChartCanvas::SetOverzoomFont() {
13038 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13039 int w, h;
13040
13041 wxFont font;
13042 if (style->embossFont == wxEmptyString) {
13043 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13044 font = *dFont;
13045 font.SetPointSize(40);
13046 font.SetWeight(wxFONTWEIGHT_BOLD);
13047 } else
13048 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13049 wxFONTWEIGHT_BOLD, false, style->embossFont);
13050
13051 wxClientDC dc(this);
13052 dc.SetFont(font);
13053 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13054
13055 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13056 font.SetPointSize(font.GetPointSize() - 1);
13057 dc.SetFont(font);
13058 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13059 }
13060 m_overzoomFont = font;
13061 m_overzoomTextWidth = w;
13062 m_overzoomTextHeight = h;
13063}
13064
13065void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13066 delete m_pEM_OverZoom;
13067
13068 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13069 m_pEM_OverZoom =
13070 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13071 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13072}
13073
13074emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13075 int height, const wxString &str,
13076 ColorScheme cs) {
13077 int *pmap;
13078
13079 // Create a temporary bitmap
13080 wxBitmap bmp(width, height, -1);
13081
13082 // Create a memory DC
13083 wxMemoryDC temp_dc;
13084 temp_dc.SelectObject(bmp);
13085
13086 // Paint on it
13087 temp_dc.SetBackground(*wxWHITE_BRUSH);
13088 temp_dc.SetTextBackground(*wxWHITE);
13089 temp_dc.SetTextForeground(*wxBLACK);
13090
13091 temp_dc.Clear();
13092
13093 temp_dc.SetFont(font);
13094
13095 int str_w, str_h;
13096 temp_dc.GetTextExtent(str, &str_w, &str_h);
13097 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13098 temp_dc.DrawText(str, 1, 1);
13099
13100 // Deselect the bitmap
13101 temp_dc.SelectObject(wxNullBitmap);
13102
13103 // Convert bitmap the wxImage for manipulation
13104 wxImage img = bmp.ConvertToImage();
13105
13106 int image_width = str_w * 105 / 100;
13107 int image_height = str_h * 105 / 100;
13108 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13109 wxMin(image_height, img.GetHeight()));
13110 wxImage imgs = img.GetSubImage(r);
13111
13112 double val_factor;
13113 switch (cs) {
13114 case GLOBAL_COLOR_SCHEME_DAY:
13115 default:
13116 val_factor = 1;
13117 break;
13118 case GLOBAL_COLOR_SCHEME_DUSK:
13119 val_factor = .5;
13120 break;
13121 case GLOBAL_COLOR_SCHEME_NIGHT:
13122 val_factor = .25;
13123 break;
13124 }
13125
13126 int val;
13127 int index;
13128 const int w = imgs.GetWidth();
13129 const int h = imgs.GetHeight();
13130 pmap = (int *)calloc(w * h * sizeof(int), 1);
13131 // Create emboss map by differentiating the emboss image
13132 // and storing integer results in pmap
13133 // n.b. since the image is B/W, it is sufficient to check
13134 // one channel (i.e. red) only
13135 for (int y = 1; y < h - 1; y++) {
13136 for (int x = 1; x < w - 1; x++) {
13137 val =
13138 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13139 val = (int)(val * val_factor);
13140 index = (y * w) + x;
13141 pmap[index] = val;
13142 }
13143 }
13144
13145 emboss_data *pret = new emboss_data;
13146 pret->pmap = pmap;
13147 pret->width = w;
13148 pret->height = h;
13149
13150 return pret;
13151}
13152
13153void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13154 Track *active_track = NULL;
13155 for (Track *pTrackDraw : g_TrackList) {
13156 if (g_pActiveTrack == pTrackDraw) {
13157 active_track = pTrackDraw;
13158 continue;
13159 }
13160
13161 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13162 }
13163
13164 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13165}
13166
13167void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13168 Track *active_track = NULL;
13169 for (Track *pTrackDraw : g_TrackList) {
13170 if (g_pActiveTrack == pTrackDraw) {
13171 active_track = pTrackDraw;
13172 break;
13173 }
13174 }
13175 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13176}
13177
13178void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13179 Route *active_route = NULL;
13180 for (Route *pRouteDraw : *pRouteList) {
13181 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13182 active_route = pRouteDraw;
13183 continue;
13184 }
13185
13186 // if(m_canvasIndex == 1)
13187 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13188 }
13189
13190 // Draw any active or selected route (or track) last, so that is is always on
13191 // top
13192 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13193}
13194
13195void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13196 Route *active_route = NULL;
13197
13198 for (Route *pRouteDraw : *pRouteList) {
13199 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13200 active_route = pRouteDraw;
13201 break;
13202 }
13203 }
13204 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13205}
13206
13207void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13208 if (!pWayPointMan) return;
13209
13210 auto node = pWayPointMan->GetWaypointList()->begin();
13211
13212 while (node != pWayPointMan->GetWaypointList()->end()) {
13213 RoutePoint *pWP = *node;
13214 if (pWP) {
13215 if (pWP->m_bIsInRoute) {
13216 ++node;
13217 continue;
13218 }
13219
13220 /* technically incorrect... waypoint has bounding box */
13221 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13222 RoutePointGui(*pWP).Draw(dc, this, NULL);
13223 else {
13224 // Are Range Rings enabled?
13225 if (pWP->GetShowWaypointRangeRings() &&
13226 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13227 double factor = 1.00;
13228 if (pWP->GetWaypointRangeRingsStepUnits() ==
13229 1) // convert kilometers to NMi
13230 factor = 1 / 1.852;
13231
13232 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13233 pWP->GetWaypointRangeRingsStep() / 60.;
13234 radius *= 2; // Fudge factor
13235
13236 LLBBox radar_box;
13237 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13238 pWP->m_lat + radius, pWP->m_lon + radius);
13239 if (!BltBBox.IntersectOut(radar_box)) {
13240 RoutePointGui(*pWP).Draw(dc, this, NULL);
13241 }
13242 }
13243 }
13244 }
13245
13246 ++node;
13247 }
13248}
13249
13250void ChartCanvas::DrawBlinkObjects() {
13251 // All RoutePoints
13252 wxRect update_rect;
13253
13254 if (!pWayPointMan) return;
13255
13256 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13257 if (pWP) {
13258 if (pWP->m_bBlink) {
13259 update_rect.Union(pWP->CurrentRect_in_DC);
13260 }
13261 }
13262 }
13263 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13264}
13265
13266void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13267 // draw anchor watch rings, if activated
13268
13270 wxPoint r1, r2;
13271 wxPoint lAnchorPoint1, lAnchorPoint2;
13272 double lpp1 = 0.0;
13273 double lpp2 = 0.0;
13274 if (pAnchorWatchPoint1) {
13275 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13277 &lAnchorPoint1);
13278 }
13279 if (pAnchorWatchPoint2) {
13280 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13282 &lAnchorPoint2);
13283 }
13284
13285 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13286 wxPen ppPenr(GetGlobalColor("URED"), 2);
13287
13288 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13289 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13290 dc.SetBrush(*ppBrush);
13291
13292 if (lpp1 > 0) {
13293 dc.SetPen(ppPeng);
13294 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13295 }
13296
13297 if (lpp2 > 0) {
13298 dc.SetPen(ppPeng);
13299 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13300 }
13301
13302 if (lpp1 < 0) {
13303 dc.SetPen(ppPenr);
13304 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13305 }
13306
13307 if (lpp2 < 0) {
13308 dc.SetPen(ppPenr);
13309 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13310 }
13311 }
13312}
13313
13314double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13315 double lpp = 0.;
13316 wxPoint r1;
13317 wxPoint lAnchorPoint;
13318 double d1 = 0.0;
13319 double dabs;
13320 double tlat1, tlon1;
13321
13322 if (pAnchorWatchPoint) {
13323 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13324 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13325 dabs = fabs(d1 / 1852.);
13326 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13327 &tlat1, &tlon1);
13328 GetCanvasPointPix(tlat1, tlon1, &r1);
13329 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13330 &lAnchorPoint);
13331 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13332 pow((double)(lAnchorPoint.y - r1.y), 2));
13333
13334 // This is an entry watch
13335 if (d1 < 0) lpp = -lpp;
13336 }
13337 return lpp;
13338}
13339
13340//------------------------------------------------------------------------------------------
13341// Tides Support
13342//------------------------------------------------------------------------------------------
13343void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13344 if (!ptcmgr) return;
13345
13346 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13347
13348 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13349 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13350 double lon = pIDX->IDX_lon;
13351 double lat = pIDX->IDX_lat;
13352
13353 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13354 if ((type == 't') || (type == 'T')) {
13355 if (BBox.Contains(lat, lon)) {
13356 // Manage the point selection list
13357 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13358 }
13359 }
13360 }
13361}
13362
13363void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13364 if (!ptcmgr) return;
13365
13366 wxDateTime this_now = gTimeSource;
13367 bool cur_time = !gTimeSource.IsValid();
13368 if (cur_time) this_now = wxDateTime::Now();
13369 time_t t_this_now = this_now.GetTicks();
13370
13371 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13372 wxPENSTYLE_SOLID);
13373 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13374 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13375 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13376 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13377
13378 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13379 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13380 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13381 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13382 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13383 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13384
13385 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13386 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13387 int font_size = wxMax(10, dFont->GetPointSize());
13388 font_size /= g_Platform->GetDisplayDIPMult(this);
13389 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13390 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13391 false, dFont->GetFaceName());
13392
13393 dc.SetPen(*pblack_pen);
13394 dc.SetBrush(*pgreen_brush);
13395
13396 wxBitmap bm;
13397 switch (m_cs) {
13398 case GLOBAL_COLOR_SCHEME_DAY:
13399 bm = m_bmTideDay;
13400 break;
13401 case GLOBAL_COLOR_SCHEME_DUSK:
13402 bm = m_bmTideDusk;
13403 break;
13404 case GLOBAL_COLOR_SCHEME_NIGHT:
13405 bm = m_bmTideNight;
13406 break;
13407 default:
13408 bm = m_bmTideDay;
13409 break;
13410 }
13411
13412 int bmw = bm.GetWidth();
13413 int bmh = bm.GetHeight();
13414
13415 float scale_factor = 1.0;
13416
13417 // Set the onscreen size of the symbol
13418 // Compensate for various display resolutions
13419 float icon_pixelRefDim = 45;
13420
13421 // Tidal report graphic is scaled by the text size of the label in use
13422 wxScreenDC sdc;
13423 int height;
13424 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13425 height *= g_Platform->GetDisplayDIPMult(this);
13426 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13427
13428 scale_factor *= pix_factor;
13429
13430 float user_scale_factor = g_ChartScaleFactorExp;
13431 if (g_ChartScaleFactorExp > 1.0)
13432 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13433 1.2; // soften the scale factor a bit
13434
13435 scale_factor *= user_scale_factor;
13436 scale_factor *= GetContentScaleFactor();
13437
13438 {
13439 double marge = 0.05;
13440 std::vector<LLBBox> drawn_boxes;
13441 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13442 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13443
13444 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13445 if ((type == 't') || (type == 'T')) // only Tides
13446 {
13447 double lon = pIDX->IDX_lon;
13448 double lat = pIDX->IDX_lat;
13449
13450 if (BBox.ContainsMarge(lat, lon, marge)) {
13451 // Avoid drawing detailed graphic for duplicate tide stations
13452 if (GetVP().chart_scale < 500000) {
13453 bool bdrawn = false;
13454 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13455 if (drawn_boxes[i].Contains(lat, lon)) {
13456 bdrawn = true;
13457 break;
13458 }
13459 }
13460 if (bdrawn) continue; // the station loop
13461
13462 LLBBox this_box;
13463 this_box.Set(lat, lon, lat, lon);
13464 this_box.EnLarge(.005);
13465 drawn_boxes.push_back(this_box);
13466 }
13467
13468 wxPoint r;
13469 GetCanvasPointPix(lat, lon, &r);
13470 // draw standard icons
13471 if (GetVP().chart_scale > 500000) {
13472 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13473 }
13474 // draw "extended" icons
13475 else {
13476 dc.SetFont(*plabelFont);
13477 {
13478 {
13479 float val, nowlev;
13480 float ltleve = 0.;
13481 float htleve = 0.;
13482 time_t tctime;
13483 time_t lttime = 0;
13484 time_t httime = 0;
13485 bool wt;
13486 // define if flood or ebb in the last ten minutes and verify if
13487 // data are useable
13488 if (ptcmgr->GetTideFlowSens(
13489 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13490 pIDX->IDX_rec_num, nowlev, val, wt)) {
13491 // search forward the first HW or LW near "now" ( starting at
13492 // "now" - ten minutes )
13493 ptcmgr->GetHightOrLowTide(
13494 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13495 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13496 wt, pIDX->IDX_rec_num, val, tctime);
13497 if (wt) {
13498 httime = tctime;
13499 htleve = val;
13500 } else {
13501 lttime = tctime;
13502 ltleve = val;
13503 }
13504 wt = !wt;
13505
13506 // then search opposite tide near "now"
13507 if (tctime > t_this_now) // search backward
13508 ptcmgr->GetHightOrLowTide(
13509 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13510 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13511 pIDX->IDX_rec_num, val, tctime);
13512 else
13513 // or search forward
13514 ptcmgr->GetHightOrLowTide(
13515 t_this_now, FORWARD_TEN_MINUTES_STEP,
13516 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13517 val, tctime);
13518 if (wt) {
13519 httime = tctime;
13520 htleve = val;
13521 } else {
13522 lttime = tctime;
13523 ltleve = val;
13524 }
13525
13526 // draw the tide rectangle:
13527
13528 // tide icon rectangle has default pre-scaled width = 12 ,
13529 // height = 45
13530 int width = (int)(12 * scale_factor + 0.5);
13531 int height = (int)(45 * scale_factor + 0.5);
13532 int linew = wxMax(1, (int)(scale_factor));
13533 int xDraw = r.x - (width / 2);
13534 int yDraw = r.y - (height / 2);
13535
13536 // process tide state ( %height and flow sens )
13537 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13538 int hs = (httime > lttime) ? -4 : 4;
13539 hs *= (int)(scale_factor + 0.5);
13540 if (ts > 0.995 || ts < 0.005) hs = 0;
13541 int ht_y = (int)(height * ts);
13542
13543 // draw yellow tide rectangle outlined in black
13544 pblack_pen->SetWidth(linew);
13545 dc.SetPen(*pblack_pen);
13546 dc.SetBrush(*pyelo_brush);
13547 dc.DrawRectangle(xDraw, yDraw, width, height);
13548
13549 // draw blue rectangle as water height, smaller in width than
13550 // yellow rectangle
13551 dc.SetPen(*pblue_pen);
13552 dc.SetBrush(*pblue_brush);
13553 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13554 (width - (4 * linew)), height - ht_y);
13555
13556 // draw sens arrows (ensure they are not "under-drawn" by top
13557 // line of blue rectangle )
13558 int hl;
13559 wxPoint arrow[3];
13560 arrow[0].x = xDraw + 2 * linew;
13561 arrow[1].x = xDraw + width / 2;
13562 arrow[2].x = xDraw + width - 2 * linew;
13563 pyelo_pen->SetWidth(linew);
13564 pblue_pen->SetWidth(linew);
13565 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13566 {
13567 hl = (int)(height * 0.25) + yDraw;
13568 arrow[0].y = hl;
13569 arrow[1].y = hl + hs;
13570 arrow[2].y = hl;
13571 if (ts < 0.15)
13572 dc.SetPen(*pyelo_pen);
13573 else
13574 dc.SetPen(*pblue_pen);
13575 dc.DrawLines(3, arrow);
13576 }
13577 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13578 {
13579 hl = (int)(height * 0.5) + yDraw;
13580 arrow[0].y = hl;
13581 arrow[1].y = hl + hs;
13582 arrow[2].y = hl;
13583 if (ts < 0.40)
13584 dc.SetPen(*pyelo_pen);
13585 else
13586 dc.SetPen(*pblue_pen);
13587 dc.DrawLines(3, arrow);
13588 }
13589 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13590 {
13591 hl = (int)(height * 0.75) + yDraw;
13592 arrow[0].y = hl;
13593 arrow[1].y = hl + hs;
13594 arrow[2].y = hl;
13595 if (ts < 0.65)
13596 dc.SetPen(*pyelo_pen);
13597 else
13598 dc.SetPen(*pblue_pen);
13599 dc.DrawLines(3, arrow);
13600 }
13601 // draw tide level text
13602 wxString s;
13603 s.Printf("%3.1f", nowlev);
13604 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13605 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13606 int wx1;
13607 dc.GetTextExtent(s, &wx1, NULL);
13608 wx1 *= g_Platform->GetDisplayDIPMult(this);
13609 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13610 }
13611 }
13612 }
13613 }
13614 }
13615 }
13616 }
13617 }
13618}
13619
13620//------------------------------------------------------------------------------------------
13621// Currents Support
13622//------------------------------------------------------------------------------------------
13623
13624void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13625 if (!ptcmgr) return;
13626
13627 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13628
13629 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13630 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13631 double lon = pIDX->IDX_lon;
13632 double lat = pIDX->IDX_lat;
13633
13634 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13635 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13636 if ((BBox.Contains(lat, lon))) {
13637 // Manage the point selection list
13638 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13639 }
13640 }
13641 }
13642}
13643
13644void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13645 if (!ptcmgr) return;
13646
13647 float tcvalue, dir;
13648 bool bnew_val;
13649 char sbuf[20];
13650 wxFont *pTCFont;
13651 double lon_last = 0.;
13652 double lat_last = 0.;
13653 // arrow size for Raz Blanchard : 12 knots north
13654 double marge = 0.2;
13655 bool cur_time = !gTimeSource.IsValid();
13656
13657 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13658 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13659
13660 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13661 wxPENSTYLE_SOLID);
13662 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13663 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13664 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13665 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13666 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13667 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13668 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13669 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13670
13671 double skew_angle = GetVPRotation();
13672
13673 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13674 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13675 int font_size = wxMax(10, dFont->GetPointSize());
13676 font_size /= g_Platform->GetDisplayDIPMult(this);
13677 pTCFont = FontMgr::Get().FindOrCreateFont(
13678 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13679 false, dFont->GetFaceName());
13680
13681 float scale_factor = 1.0;
13682
13683 // Set the onscreen size of the symbol
13684 // Current report graphic is scaled by the text size of the label in use
13685 wxScreenDC sdc;
13686 int height;
13687 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13688 height *= g_Platform->GetDisplayDIPMult(this);
13689 float nominal_icon_size_pixels = 15;
13690 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13691
13692 scale_factor *= pix_factor;
13693
13694 float user_scale_factor = g_ChartScaleFactorExp;
13695 if (g_ChartScaleFactorExp > 1.0)
13696 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13697 1.2; // soften the scale factor a bit
13698
13699 scale_factor *= user_scale_factor;
13700
13701 scale_factor *= GetContentScaleFactor();
13702
13703 {
13704 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13705 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13706 double lon = pIDX->IDX_lon;
13707 double lat = pIDX->IDX_lat;
13708
13709 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13710 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13711 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13712 wxPoint r;
13713 GetCanvasPointPix(lat, lon, &r);
13714
13715 wxPoint d[4]; // points of a diamond at the current station location
13716 int dd = (int)(5.0 * scale_factor + 0.5);
13717 d[0].x = r.x;
13718 d[0].y = r.y + dd;
13719 d[1].x = r.x + dd;
13720 d[1].y = r.y;
13721 d[2].x = r.x;
13722 d[2].y = r.y - dd;
13723 d[3].x = r.x - dd;
13724 d[3].y = r.y;
13725
13726 if (1) {
13727 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13728 dc.SetPen(*pblack_pen);
13729 dc.SetBrush(*porange_brush);
13730 dc.DrawPolygon(4, d);
13731
13732 if (type == 'C') {
13733 dc.SetBrush(*pblack_brush);
13734 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13735 }
13736
13737 if (GetVP().chart_scale < 1000000) {
13738 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13739 continue;
13740 } else
13741 continue;
13742
13743 if (1 /*type == 'c'*/) {
13744 {
13745 // Get the display pixel location of the current station
13746 int pixxc, pixyc;
13747 pixxc = r.x;
13748 pixyc = r.y;
13749
13750 // Adjust drawing size using logarithmic scale. tcvalue is
13751 // current in knots
13752 double a1 = fabs(tcvalue) * 10.;
13753 // Current values <= 0.1 knot will have no arrow
13754 a1 = wxMax(1.0, a1);
13755 double a2 = log10(a1);
13756
13757 float cscale = scale_factor * a2 * 0.3;
13758
13759 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13760 dc.SetPen(*porange_pen);
13761 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13762 cscale);
13763 // Draw text, if enabled
13764
13765 if (bDrawCurrentValues) {
13766 dc.SetFont(*pTCFont);
13767 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13768 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13769 }
13770 }
13771 } // scale
13772 }
13773 /* This is useful for debugging the TC database
13774 else
13775 {
13776 dc.SetPen ( *porange_pen );
13777 dc.SetBrush ( *pgray_brush );
13778 dc.DrawPolygon ( 4, d );
13779 }
13780 */
13781 }
13782 lon_last = lon;
13783 lat_last = lat;
13784 }
13785 }
13786 }
13787}
13788
13789void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13790 ShowSingleTideDialog(x, y, pvIDX);
13791}
13792
13793void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13794 if (!pvIDX) return; // Validate input
13795
13796 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13797
13798 // Check if a tide dialog is already open and visible
13799 if (pCwin && pCwin->IsShown()) {
13800 // Same tide station: bring existing dialog to front (preserves user
13801 // context)
13802 if (pCwin->GetCurrentIDX() == pNewIDX) {
13803 pCwin->Raise();
13804 pCwin->SetFocus();
13805
13806 // Provide subtle visual feedback that dialog is already open
13807 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13808 return;
13809 }
13810
13811 // Different tide station: close current dialog before opening new one
13812 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13813 }
13814
13815 if (pCwin) {
13816 // This shouldn't happen but ensures clean state
13817 pCwin->Destroy();
13818 pCwin = NULL;
13819 }
13820
13821 // Create and display new tide dialog
13822 pCwin = new TCWin(this, x, y, pvIDX);
13823
13824 // Ensure the dialog is properly shown and focused
13825 if (pCwin) {
13826 pCwin->Show();
13827 pCwin->Raise();
13828 pCwin->SetFocus();
13829 }
13830}
13831
13832bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13833
13835 if (pCwin) {
13836 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13837 }
13838}
13839
13840#define NUM_CURRENT_ARROW_POINTS 9
13841static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13842 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13843 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13844 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13845
13846void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13847 double scale) {
13848 if (scale > 1e-2) {
13849 float sin_rot = sin(rot_angle * PI / 180.);
13850 float cos_rot = cos(rot_angle * PI / 180.);
13851
13852 // Move to the first point
13853
13854 float xt = CurrentArrowArray[0].x;
13855 float yt = CurrentArrowArray[0].y;
13856
13857 float xp = (xt * cos_rot) - (yt * sin_rot);
13858 float yp = (xt * sin_rot) + (yt * cos_rot);
13859 int x1 = (int)(xp * scale);
13860 int y1 = (int)(yp * scale);
13861
13862 // Walk thru the point list
13863 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13864 xt = CurrentArrowArray[ip].x;
13865 yt = CurrentArrowArray[ip].y;
13866
13867 float xp = (xt * cos_rot) - (yt * sin_rot);
13868 float yp = (xt * sin_rot) + (yt * cos_rot);
13869 int x2 = (int)(xp * scale);
13870 int y2 = (int)(yp * scale);
13871
13872 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13873
13874 x1 = x2;
13875 y1 = y2;
13876 }
13877 }
13878}
13879
13880wxString ChartCanvas::FindValidUploadPort() {
13881 wxString port;
13882 // Try to use the saved persistent upload port first
13883 if (!g_uploadConnection.IsEmpty() &&
13884 g_uploadConnection.StartsWith("Serial")) {
13885 port = g_uploadConnection;
13886 }
13887
13888 else {
13889 // If there is no persistent upload port recorded (yet)
13890 // then use the first available serial connection which has output defined.
13891 for (auto *cp : TheConnectionParams()) {
13892 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13893 port << "Serial:" << cp->Port;
13894 }
13895 }
13896 return port;
13897}
13898
13899void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13900 if (!win) return;
13901
13902 if (NULL == g_pais_query_dialog_active) {
13903 int pos_x = g_ais_query_dialog_x;
13904 int pos_y = g_ais_query_dialog_y;
13905
13906 if (g_pais_query_dialog_active) {
13907 g_pais_query_dialog_active->Destroy();
13908 g_pais_query_dialog_active = new AISTargetQueryDialog();
13909 } else {
13910 g_pais_query_dialog_active = new AISTargetQueryDialog();
13911 }
13912
13913 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13914 wxPoint(pos_x, pos_y));
13915
13916 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13917 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13918 g_pais_query_dialog_active->SetMMSI(mmsi);
13919 g_pais_query_dialog_active->UpdateText();
13920 wxSize sz = g_pais_query_dialog_active->GetSize();
13921
13922 bool b_reset_pos = false;
13923#ifdef __WXMSW__
13924 // Support MultiMonitor setups which an allow negative window positions.
13925 // If the requested window title bar does not intersect any installed
13926 // monitor, then default to simple primary monitor positioning.
13927 RECT frame_title_rect;
13928 frame_title_rect.left = pos_x;
13929 frame_title_rect.top = pos_y;
13930 frame_title_rect.right = pos_x + sz.x;
13931 frame_title_rect.bottom = pos_y + 30;
13932
13933 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13934 b_reset_pos = true;
13935#else
13936
13937 // Make sure drag bar (title bar) of window intersects wxClient Area of
13938 // screen, with a little slop...
13939 wxRect window_title_rect; // conservative estimate
13940 window_title_rect.x = pos_x;
13941 window_title_rect.y = pos_y;
13942 window_title_rect.width = sz.x;
13943 window_title_rect.height = 30;
13944
13945 wxRect ClientRect = wxGetClientDisplayRect();
13946 ClientRect.Deflate(
13947 60, 60); // Prevent the new window from being too close to the edge
13948 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13949
13950#endif
13951
13952 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13953
13954 } else {
13955 g_pais_query_dialog_active->SetMMSI(mmsi);
13956 g_pais_query_dialog_active->UpdateText();
13957 }
13958
13959 g_pais_query_dialog_active->Show();
13960}
13961
13962void ChartCanvas::ToggleCanvasQuiltMode() {
13963 bool cur_mode = GetQuiltMode();
13964
13965 if (!GetQuiltMode())
13966 SetQuiltMode(true);
13967 else if (GetQuiltMode()) {
13968 SetQuiltMode(false);
13969 g_sticky_chart = GetQuiltReferenceChartIndex();
13970 }
13971
13972 if (cur_mode != GetQuiltMode()) {
13973 SetupCanvasQuiltMode();
13974 DoCanvasUpdate();
13975 InvalidateGL();
13976 Refresh();
13977 }
13978 // TODO What to do about this?
13979 // g_bQuiltEnable = GetQuiltMode();
13980
13981 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13982 if (ps52plib) ps52plib->GenerateStateHash();
13983
13984 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13985 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13986}
13987
13988void ChartCanvas::DoCanvasStackDelta(int direction) {
13989 if (!GetQuiltMode()) {
13990 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13991 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13992 if ((current_stack_index + direction) < 0) return;
13993
13994 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13995 int new_dbIndex =
13996 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13997
13998 if (IsChartQuiltableRef(new_dbIndex)) {
13999 ToggleCanvasQuiltMode();
14000 SelectQuiltRefdbChart(new_dbIndex);
14001 m_bpersistent_quilt = false;
14002 }
14003 } else {
14004 SelectChartFromStack(current_stack_index + direction);
14005 }
14006 } else {
14007 std::vector<int> piano_chart_index_array =
14008 GetQuiltExtendedStackdbIndexArray();
14009 int refdb = GetQuiltRefChartdbIndex();
14010
14011 // Find the ref chart in the stack
14012 int current_index = -1;
14013 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14014 if (refdb == piano_chart_index_array[i]) {
14015 current_index = i;
14016 break;
14017 }
14018 }
14019 if (current_index == -1) return;
14020
14021 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14022 int target_family = ctet.GetChartFamily();
14023
14024 int new_index = -1;
14025 int check_index = current_index + direction;
14026 bool found = false;
14027 int check_dbIndex = -1;
14028 int new_dbIndex = -1;
14029
14030 // When quilted. switch within the same chart family
14031 while (!found &&
14032 (unsigned int)check_index < piano_chart_index_array.size() &&
14033 (check_index >= 0)) {
14034 check_dbIndex = piano_chart_index_array[check_index];
14035 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14036 if (target_family == cte.GetChartFamily()) {
14037 found = true;
14038 new_index = check_index;
14039 new_dbIndex = check_dbIndex;
14040 break;
14041 }
14042
14043 check_index += direction;
14044 }
14045
14046 if (!found) return;
14047
14048 if (!IsChartQuiltableRef(new_dbIndex)) {
14049 ToggleCanvasQuiltMode();
14050 SelectdbChart(new_dbIndex);
14051 m_bpersistent_quilt = true;
14052 } else {
14053 SelectQuiltRefChart(new_index);
14054 }
14055 }
14056
14057 // update the state of the menu items (checkmarks etc)
14058 top_frame::Get()->UpdateGlobalMenuItems();
14059 SetQuiltChartHiLiteIndex(-1);
14060
14061 ReloadVP();
14062}
14063
14064//--------------------------------------------------------------------------------------------------------
14065//
14066// Toolbar support
14067//
14068//--------------------------------------------------------------------------------------------------------
14069
14070void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14071 // Handle the per-canvas toolbar clicks here
14072
14073 switch (event.GetId()) {
14074 case ID_ZOOMIN: {
14075 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14076 break;
14077 }
14078
14079 case ID_ZOOMOUT: {
14080 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14081 break;
14082 }
14083
14084 case ID_STKUP:
14085 DoCanvasStackDelta(1);
14086 DoCanvasUpdate();
14087 break;
14088
14089 case ID_STKDN:
14090 DoCanvasStackDelta(-1);
14091 DoCanvasUpdate();
14092 break;
14093
14094 case ID_FOLLOW: {
14095 TogglebFollow();
14096 break;
14097 }
14098
14099 case ID_CURRENT: {
14100 ShowCurrents(!GetbShowCurrent());
14101 ReloadVP();
14102 Refresh(false);
14103 break;
14104 }
14105
14106 case ID_TIDE: {
14107 ShowTides(!GetbShowTide());
14108 ReloadVP();
14109 Refresh(false);
14110 break;
14111 }
14112
14113 case ID_ROUTE: {
14114 if (0 == m_routeState) {
14115 StartRoute();
14116 } else {
14117 FinishRoute();
14118 }
14119
14120#ifdef __ANDROID__
14121 androidSetRouteAnnunciator(m_routeState == 1);
14122#endif
14123 break;
14124 }
14125
14126 case ID_AIS: {
14127 SetAISCanvasDisplayStyle(-1);
14128 break;
14129 }
14130
14131 default:
14132 break;
14133 }
14134
14135 // And then let gFrame handle the rest....
14136 event.Skip();
14137}
14138
14139void ChartCanvas::SetShowAIS(bool show) {
14140 m_bShowAIS = show;
14141 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14142 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14143}
14144
14145void ChartCanvas::SetAttenAIS(bool show) {
14146 m_bShowAISScaled = show;
14147 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14148 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14149}
14150
14151void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14152 // make some arrays to hold the dfferences between cycle steps
14153 // show all, scaled, hide all
14154 bool bShowAIS_Array[3] = {true, true, false};
14155 bool bShowScaled_Array[3] = {false, true, true};
14156 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14157 _("Attenuate less critical AIS targets"),
14158 _("Hide AIS Targets")};
14159 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14160 int ArraySize = 3;
14161 int AIS_Toolbar_Switch = 0;
14162 if (StyleIndx == -1) { // -1 means coming from toolbar button
14163 // find current state of switch
14164 for (int i = 1; i < ArraySize; i++) {
14165 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14166 (bShowScaled_Array[i] == m_bShowAISScaled))
14167 AIS_Toolbar_Switch = i;
14168 }
14169 AIS_Toolbar_Switch++; // we did click so continu with next item
14170 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14171 AIS_Toolbar_Switch++;
14172
14173 } else { // coming from menu bar.
14174 AIS_Toolbar_Switch = StyleIndx;
14175 }
14176 // make sure we are not above array
14177 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14178
14179 int AIS_Toolbar_Switch_Next =
14180 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14181 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14182 AIS_Toolbar_Switch_Next++;
14183 if (AIS_Toolbar_Switch_Next >= ArraySize)
14184 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14185
14186 // Set found values to global and member variables
14187 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14188 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14189}
14190
14191void ChartCanvas::TouchAISToolActive() {}
14192
14193void ChartCanvas::UpdateAISTBTool() {}
14194
14195//---------------------------------------------------------------------------------
14196//
14197// Compass/GPS status icon support
14198//
14199//---------------------------------------------------------------------------------
14200
14201void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14202 // Look for change in overlap or positions
14203 bool b_update = false;
14204 int cc1_edge_comp = 2;
14205 wxRect rect = m_Compass->GetRect();
14206 wxSize parent_size = GetSize();
14207
14208 parent_size *= m_displayScale;
14209
14210 // check to see if it would overlap if it was in its home position (upper
14211 // right)
14212 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14213 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14214 wxRect compass_rect(compass_pt, rect.GetSize());
14215
14216 m_Compass->Move(compass_pt);
14217
14218 if (m_Compass && m_Compass->IsShown())
14219 m_Compass->UpdateStatus(b_force_new | b_update);
14220
14221 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14222 scaler = wxMax(scaler, 1.0);
14223 wxPoint note_point = wxPoint(
14224 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14225 if (m_notification_button) {
14226 m_notification_button->Move(note_point);
14227 m_notification_button->UpdateStatus();
14228 }
14229
14230 if (b_force_new | b_update) Refresh();
14231}
14232
14233void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14234 ChartTypeEnum New_Type,
14235 ChartFamilyEnum New_Family) {
14236 if (!GetpCurrentStack()) return;
14237 if (!ChartData) return;
14238
14239 if (index < GetpCurrentStack()->nEntry) {
14240 // Open the new chart
14241 ChartBase *pTentative_Chart;
14242 pTentative_Chart = ChartData->OpenStackChartConditional(
14243 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14244
14245 if (pTentative_Chart) {
14246 if (m_singleChart) m_singleChart->Deactivate();
14247
14248 m_singleChart = pTentative_Chart;
14249 m_singleChart->Activate();
14250
14251 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14252 GetpCurrentStack(), m_singleChart->GetFullPath());
14253 }
14254
14255 // Setup the view
14256 double zLat, zLon;
14257 if (m_bFollow) {
14258 zLat = gLat;
14259 zLon = gLon;
14260 } else {
14261 zLat = m_vLat;
14262 zLon = m_vLon;
14263 }
14264
14265 double best_scale_ppm = GetBestVPScale(m_singleChart);
14266 double rotation = GetVPRotation();
14267 double oldskew = GetVPSkew();
14268 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14269
14270 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14271 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14272 if (fabs(newskew) > 0.0001) rotation = newskew;
14273 }
14274
14275 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14276
14277 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14278 }
14279
14280 // refresh Piano
14281 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14282 if (idx < 0) return;
14283
14284 std::vector<int> piano_active_chart_index_array;
14285 piano_active_chart_index_array.push_back(
14286 GetpCurrentStack()->GetCurrentEntrydbIndex());
14287 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14288}
14289
14290void ChartCanvas::SelectdbChart(int dbindex) {
14291 if (!GetpCurrentStack()) return;
14292 if (!ChartData) return;
14293
14294 if (dbindex >= 0) {
14295 // Open the new chart
14296 ChartBase *pTentative_Chart;
14297 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14298
14299 if (pTentative_Chart) {
14300 if (m_singleChart) m_singleChart->Deactivate();
14301
14302 m_singleChart = pTentative_Chart;
14303 m_singleChart->Activate();
14304
14305 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14306 GetpCurrentStack(), m_singleChart->GetFullPath());
14307 }
14308
14309 // Setup the view
14310 double zLat, zLon;
14311 if (m_bFollow) {
14312 zLat = gLat;
14313 zLon = gLon;
14314 } else {
14315 zLat = m_vLat;
14316 zLon = m_vLon;
14317 }
14318
14319 double best_scale_ppm = GetBestVPScale(m_singleChart);
14320
14321 if (m_singleChart)
14322 SetViewPoint(zLat, zLon, best_scale_ppm,
14323 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14324
14325 // SetChartUpdatePeriod( );
14326
14327 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14328 }
14329
14330 // TODO refresh_Piano();
14331}
14332
14333void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14334 double target_scale = GetVP().view_scale_ppm;
14335
14336 if (!GetQuiltMode()) {
14337 if (GetpCurrentStack()) {
14338 int stack_index = -1;
14339 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14340 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14341 if (check_dbIndex < 0) continue;
14342 const ChartTableEntry &cte =
14343 ChartData->GetChartTableEntry(check_dbIndex);
14344 if (type == cte.GetChartType()) {
14345 stack_index = i;
14346 break;
14347 } else if (family == cte.GetChartFamily()) {
14348 stack_index = i;
14349 break;
14350 }
14351 }
14352
14353 if (stack_index >= 0) {
14354 SelectChartFromStack(stack_index);
14355 }
14356 }
14357 } else {
14358 int sel_dbIndex = -1;
14359 std::vector<int> piano_chart_index_array =
14360 GetQuiltExtendedStackdbIndexArray();
14361 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14362 int check_dbIndex = piano_chart_index_array[i];
14363 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14364 if (type == cte.GetChartType()) {
14365 if (IsChartQuiltableRef(check_dbIndex)) {
14366 sel_dbIndex = check_dbIndex;
14367 break;
14368 }
14369 } else if (family == cte.GetChartFamily()) {
14370 if (IsChartQuiltableRef(check_dbIndex)) {
14371 sel_dbIndex = check_dbIndex;
14372 break;
14373 }
14374 }
14375 }
14376
14377 if (sel_dbIndex >= 0) {
14378 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14379 // Re-qualify the quilt reference chart selection
14380 AdjustQuiltRefChart();
14381 }
14382
14383 // Now reset the scale to the target...
14384 SetVPScale(target_scale);
14385 }
14386
14387 SetQuiltChartHiLiteIndex(-1);
14388
14389 ReloadVP();
14390}
14391
14392bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14393 return std::find(m_tile_yesshow_index_array.begin(),
14394 m_tile_yesshow_index_array.end(),
14395 index) != m_tile_yesshow_index_array.end();
14396}
14397
14398bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14399 return std::find(m_tile_noshow_index_array.begin(),
14400 m_tile_noshow_index_array.end(),
14401 index) != m_tile_noshow_index_array.end();
14402}
14403
14404void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14405 if (std::find(m_tile_noshow_index_array.begin(),
14406 m_tile_noshow_index_array.end(),
14407 index) == m_tile_noshow_index_array.end()) {
14408 m_tile_noshow_index_array.push_back(index);
14409 }
14410}
14411
14412//-------------------------------------------------------------------------------------------------------
14413//
14414// Piano support
14415//
14416//-------------------------------------------------------------------------------------------------------
14417
14418void ChartCanvas::HandlePianoClick(
14419 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14420 if (g_options && g_options->IsShown())
14421 return; // Piano might be invalid due to chartset updates.
14422 if (!m_pCurrentStack) return;
14423 if (!ChartData) return;
14424
14425 // stop movement or on slow computer we may get something like :
14426 // zoom out with the wheel (timer is set)
14427 // quickly click and display a chart, which may zoom in
14428 // but the delayed timer fires first and it zooms out again!
14429 StopMovement();
14430
14431 // When switching by piano key click, we may appoint the new target chart to
14432 // be any chart in the composite array.
14433 // As an improvement to UX, find the chart that is "closest" to the current
14434 // vp,
14435 // and select that chart. This will cause a jump to the centroid of that
14436 // chart
14437
14438 double distance = 25000; // RTW
14439 int closest_index = -1;
14440 for (int chart_index : selected_dbIndex_array) {
14441 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14442 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14443 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14444
14445 // measure distance as Manhattan style
14446 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14447 if (test_distance < distance) {
14448 distance = test_distance;
14449 closest_index = chart_index;
14450 }
14451 }
14452
14453 int selected_dbIndex = selected_dbIndex_array[0];
14454 if (closest_index >= 0) selected_dbIndex = closest_index;
14455
14456 if (!GetQuiltMode()) {
14457 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14458 if (IsChartQuiltableRef(selected_dbIndex)) {
14459 ToggleCanvasQuiltMode();
14460 SelectQuiltRefdbChart(selected_dbIndex);
14461 m_bpersistent_quilt = false;
14462 } else {
14463 SelectChartFromStack(selected_index);
14464 }
14465 } else {
14466 SelectChartFromStack(selected_index);
14467 g_sticky_chart = selected_dbIndex;
14468 }
14469
14470 if (m_singleChart)
14471 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14472 } else {
14473 // Handle MBTiles overlays first
14474 // Left click simply toggles the noshow array index entry
14475 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14476 bool bfound = false;
14477 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14478 if (m_tile_noshow_index_array[i] ==
14479 selected_dbIndex) { // chart is in the noshow list
14480 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14481 i); // erase it
14482 bfound = true;
14483 break;
14484 }
14485 }
14486 if (!bfound) {
14487 m_tile_noshow_index_array.push_back(selected_dbIndex);
14488 }
14489
14490 // If not already present, add this tileset to the "yes_show" array.
14491 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14492 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14493 }
14494
14495 else {
14496 if (IsChartQuiltableRef(selected_dbIndex)) {
14497 // if( ChartData ) ChartData->PurgeCache();
14498
14499 // If the chart is a vector chart, and of very large scale,
14500 // then we had better set the new scale directly to avoid excessive
14501 // underzoom on, eg, Inland ENCs
14502 bool set_scale = false;
14503 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14504 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14505 set_scale = true;
14506 }
14507 }
14508
14509 if (!set_scale) {
14510 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14511 } else {
14512 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14513
14514 // Adjust scale so that the selected chart is underzoomed/overzoomed
14515 // by a controlled amount
14516 ChartBase *pc =
14517 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14518 if (pc) {
14519 double proposed_scale_onscreen =
14521
14522 if (g_bPreserveScaleOnX) {
14523 proposed_scale_onscreen =
14524 wxMin(proposed_scale_onscreen,
14525 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14526 GetCanvasWidth()));
14527 } else {
14528 proposed_scale_onscreen =
14529 wxMin(proposed_scale_onscreen,
14530 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14531 GetCanvasWidth()));
14532
14533 proposed_scale_onscreen =
14534 wxMax(proposed_scale_onscreen,
14535 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14537 }
14538
14539 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14540 }
14541 }
14542 } else {
14543 ToggleCanvasQuiltMode();
14544 SelectdbChart(selected_dbIndex);
14545 m_bpersistent_quilt = true;
14546 }
14547 }
14548 }
14549
14550 SetQuiltChartHiLiteIndex(-1);
14551 // update the state of the menu items (checkmarks etc)
14552 top_frame::Get()->UpdateGlobalMenuItems();
14553 HideChartInfoWindow();
14554 DoCanvasUpdate();
14555 ReloadVP(); // Pick up the new selections
14556}
14557
14558void ChartCanvas::HandlePianoRClick(
14559 int x, int y, int selected_index,
14560 const std::vector<int> &selected_dbIndex_array) {
14561 if (g_options && g_options->IsShown())
14562 return; // Piano might be invalid due to chartset updates.
14563 if (!GetpCurrentStack()) return;
14564
14565 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14566 UpdateCanvasControlBar();
14567
14568 SetQuiltChartHiLiteIndex(-1);
14569}
14570
14571void ChartCanvas::HandlePianoRollover(
14572 int selected_index, const std::vector<int> &selected_dbIndex_array,
14573 int n_charts, int scale) {
14574 if (g_options && g_options->IsShown())
14575 return; // Piano might be invalid due to chartset updates.
14576 if (!GetpCurrentStack()) return;
14577 if (!ChartData) return;
14578
14579 if (ChartData->IsBusy()) return;
14580
14581 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14582
14583 if (!GetQuiltMode()) {
14584 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14585 } else {
14586 // Select the correct vector
14587 std::vector<int> piano_chart_index_array;
14588 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14589 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14590 if ((GetpCurrentStack()->nEntry > 1) ||
14591 (piano_chart_index_array.size() >= 1)) {
14592 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14593
14594 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14595 ReloadVP(false); // no VP adjustment allowed
14596 } else if (GetpCurrentStack()->nEntry == 1) {
14597 const ChartTableEntry &cte =
14598 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14599 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14600 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14601 ReloadVP(false);
14602 } else if ((-1 == selected_index) &&
14603 (0 == selected_dbIndex_array.size())) {
14604 ShowChartInfoWindow(key_location.x, -1);
14605 }
14606 }
14607 } else {
14608 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14609
14610 if ((GetpCurrentStack()->nEntry > 1) ||
14611 (piano_chart_index_array.size() >= 1)) {
14612 if (n_charts > 1)
14613 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14614 selected_dbIndex_array);
14615 else if (n_charts == 1)
14616 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14617
14618 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14619 ReloadVP(false); // no VP adjustment allowed
14620 }
14621 }
14622 }
14623}
14624
14625void ChartCanvas::ClearPianoRollover() {
14626 ClearQuiltChartHiLiteIndexArray();
14627 ShowChartInfoWindow(0, -1);
14628 std::vector<int> vec;
14629 ShowCompositeInfoWindow(0, 0, 0, vec);
14630 ReloadVP(false);
14631}
14632
14633void ChartCanvas::UpdateCanvasControlBar() {
14634 if (m_pianoFrozen) return;
14635
14636 if (!GetpCurrentStack()) return;
14637 if (!ChartData) return;
14638 if (!g_bShowChartBar) return;
14639
14640 int sel_type = -1;
14641 int sel_family = -1;
14642
14643 std::vector<int> piano_chart_index_array;
14644 std::vector<int> empty_piano_chart_index_array;
14645
14646 wxString old_hash = m_Piano->GetStoredHash();
14647
14648 if (GetQuiltMode()) {
14649 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14650 GetQuiltFullScreendbIndexArray());
14651
14652 std::vector<int> piano_active_chart_index_array =
14653 GetQuiltCandidatedbIndexArray();
14654 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14655
14656 std::vector<int> piano_eclipsed_chart_index_array =
14657 GetQuiltEclipsedStackdbIndexArray();
14658 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14659
14660 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14661 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14662
14663 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14664 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14665 } else {
14666 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14667 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14668 // TODO refresh_Piano();
14669
14670 if (m_singleChart) {
14671 sel_type = m_singleChart->GetChartType();
14672 sel_family = m_singleChart->GetChartFamily();
14673 }
14674 }
14675
14676 // Set up the TMerc and Skew arrays
14677 std::vector<int> piano_skew_chart_index_array;
14678 std::vector<int> piano_tmerc_chart_index_array;
14679 std::vector<int> piano_poly_chart_index_array;
14680
14681 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14682 const ChartTableEntry &ctei =
14683 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14684 double skew_norm = ctei.GetChartSkew();
14685 if (skew_norm > 180.) skew_norm -= 360.;
14686
14687 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14688 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14689
14690 // Polyconic skewed charts should show as skewed
14691 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14692 if (fabs(skew_norm) > 1.)
14693 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14694 else
14695 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14696 } else if (fabs(skew_norm) > 1.)
14697 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14698 }
14699 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14700 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14701 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14702
14703 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14704 if (new_hash != old_hash) {
14705 m_Piano->FormatKeys();
14706 HideChartInfoWindow();
14707 m_Piano->ResetRollover();
14708 SetQuiltChartHiLiteIndex(-1);
14709 m_brepaint_piano = true;
14710 }
14711
14712 // Create a bitmask int that describes what Family/Type of charts are shown in
14713 // the bar, and notify the platform.
14714 int mask = 0;
14715 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14716 const ChartTableEntry &ctei =
14717 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14718 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14719 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14720 if (e == CHART_FAMILY_RASTER) mask |= 1;
14721 if (e == CHART_FAMILY_VECTOR) {
14722 if (t == CHART_TYPE_CM93COMP)
14723 mask |= 4;
14724 else
14725 mask |= 2;
14726 }
14727 }
14728
14729 wxString s_indicated;
14730 if (sel_type == CHART_TYPE_CM93COMP)
14731 s_indicated = "cm93";
14732 else {
14733 if (sel_family == CHART_FAMILY_RASTER)
14734 s_indicated = "raster";
14735 else if (sel_family == CHART_FAMILY_VECTOR)
14736 s_indicated = "vector";
14737 }
14738
14739 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14740}
14741
14742void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14743
14744void ChartCanvas::PianoPopupMenu(
14745 int x, int y, int selected_index,
14746 const std::vector<int> &selected_dbIndex_array) {
14747 if (!GetpCurrentStack()) return;
14748
14749 // No context menu if quilting is disabled
14750 if (!GetQuiltMode()) return;
14751
14752 m_piano_ctx_menu = new wxMenu();
14753
14754 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14755 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14756 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14757 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14758 } else {
14759 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14760 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14761 // wxEVT_COMMAND_MENU_SELECTED,
14762 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14763
14764 menu_selected_dbIndex = selected_dbIndex_array[0];
14765 menu_selected_index = selected_index;
14766
14767 // Search the no-show array
14768 bool b_is_in_noshow = false;
14769 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14770 if (m_quilt_noshow_index_array[i] ==
14771 menu_selected_dbIndex) // chart is in the noshow list
14772 {
14773 b_is_in_noshow = true;
14774 break;
14775 }
14776 }
14777
14778 if (b_is_in_noshow) {
14779 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14780 _("Show This Chart"));
14781 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14782 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14783 } else if (GetpCurrentStack()->nEntry > 1) {
14784 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14785 _("Hide This Chart"));
14786 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14787 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14788 }
14789 }
14790
14791 wxPoint pos = wxPoint(x, y - 30);
14792
14793 // Invoke the drop-down menu
14794 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14795 PopupMenu(m_piano_ctx_menu, pos);
14796
14797 delete m_piano_ctx_menu;
14798 m_piano_ctx_menu = NULL;
14799
14800 HideChartInfoWindow();
14801 m_Piano->ResetRollover();
14802
14803 SetQuiltChartHiLiteIndex(-1);
14804 ClearQuiltChartHiLiteIndexArray();
14805
14806 ReloadVP();
14807}
14808
14809void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14810 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14811 if (m_quilt_noshow_index_array[i] ==
14812 menu_selected_dbIndex) // chart is in the noshow list
14813 {
14814 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14815 break;
14816 }
14817 }
14818}
14819
14820void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14821 if (!GetpCurrentStack()) return;
14822 if (!ChartData) return;
14823
14824 RemoveChartFromQuilt(menu_selected_dbIndex);
14825
14826 // It could happen that the chart being disabled is the reference
14827 // chart....
14828 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14829 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14830
14831 int i = menu_selected_index + 1; // select next smaller scale chart
14832 bool b_success = false;
14833 while (i < GetpCurrentStack()->nEntry - 1) {
14834 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14835 if (type == ChartData->GetDBChartType(dbIndex)) {
14836 SelectQuiltRefChart(i);
14837 b_success = true;
14838 break;
14839 }
14840 i++;
14841 }
14842
14843 // If that did not work, try to select the next larger scale compatible
14844 // chart
14845 if (!b_success) {
14846 i = menu_selected_index - 1;
14847 while (i > 0) {
14848 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14849 if (type == ChartData->GetDBChartType(dbIndex)) {
14850 SelectQuiltRefChart(i);
14851 b_success = true;
14852 break;
14853 }
14854 i--;
14855 }
14856 }
14857 }
14858}
14859
14860void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14861 // Remove the item from the list (if it appears) to avoid multiple addition
14862 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14863 if (m_quilt_noshow_index_array[i] ==
14864 dbIndex) // chart is already in the noshow list
14865 {
14866 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14867 break;
14868 }
14869 }
14870
14871 m_quilt_noshow_index_array.push_back(dbIndex);
14872}
14873
14874bool ChartCanvas::UpdateS52State() {
14875 bool retval = false;
14876
14877 if (ps52plib) {
14878 ps52plib->SetShowS57Text(m_encShowText);
14879 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14880 ps52plib->m_bShowSoundg = m_encShowDepth;
14881 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14882 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14883
14884 // Lights
14885 if (!m_encShowLights) // On, going off
14886 ps52plib->AddObjNoshow("LIGHTS");
14887 else // Off, going on
14888 ps52plib->RemoveObjNoshow("LIGHTS");
14889 ps52plib->SetLightsOff(!m_encShowLights);
14890 ps52plib->m_bExtendLightSectors = true;
14891
14892 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14893 ps52plib->SetAnchorOn(m_encShowAnchor);
14894 ps52plib->SetQualityOfData(m_encShowDataQual);
14895 }
14896
14897 return retval;
14898}
14899
14900void ChartCanvas::SetShowENCDataQual(bool show) {
14901 m_encShowDataQual = show;
14902 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14903 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14904
14905 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14906}
14907
14908void ChartCanvas::SetShowENCText(bool show) {
14909 m_encShowText = show;
14910 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14911 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14912
14913 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14914}
14915
14916void ChartCanvas::SetENCDisplayCategory(int category) {
14917 m_encDisplayCategory = category;
14918 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14919}
14920
14921void ChartCanvas::SetShowENCDepth(bool show) {
14922 m_encShowDepth = show;
14923 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14924 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14925
14926 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14927}
14928
14929void ChartCanvas::SetShowENCLightDesc(bool show) {
14930 m_encShowLightDesc = show;
14931 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14932 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14933
14934 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14935}
14936
14937void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14938 m_encShowBuoyLabels = show;
14939 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14940}
14941
14942void ChartCanvas::SetShowENCLights(bool show) {
14943 m_encShowLights = show;
14944 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14945 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14946
14947 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14948}
14949
14950void ChartCanvas::SetShowENCAnchor(bool show) {
14951 m_encShowAnchor = show;
14952 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14953 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14954
14955 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14956}
14957
14958wxRect ChartCanvas::GetMUIBarRect() {
14959 wxRect rv;
14960 if (m_muiBar) {
14961 rv = m_muiBar->GetRect();
14962 }
14963
14964 return rv;
14965}
14966
14967void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14968 if (!GetAlertString().IsEmpty()) {
14969 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14970 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14971
14972 dc.SetFont(*pfont);
14973 dc.SetPen(*wxTRANSPARENT_PEN);
14974
14975 dc.SetBrush(wxColour(243, 229, 47));
14976 int w, h;
14977 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14978 h += 2;
14979 // int yp = vp.pix_height - 20 - h;
14980
14981 wxRect sbr = GetScaleBarRect();
14982 int xp = sbr.x + sbr.width + 10;
14983 int yp = (sbr.y + sbr.height) - h;
14984
14985 int wdraw = w + 10;
14986 dc.DrawRectangle(xp, yp, wdraw, h);
14987 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14988 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14989 }
14990}
14991
14992//--------------------------------------------------------------------------------------------------------
14993// Screen Brightness Control Support Routines
14994//
14995//--------------------------------------------------------------------------------------------------------
14996
14997#ifdef __UNIX__
14998#define BRIGHT_XCALIB
14999#define __OPCPN_USEICC__
15000#endif
15001
15002#ifdef __OPCPN_USEICC__
15003int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15004 double co_green, double co_blue);
15005
15006wxString temp_file_name;
15007#endif
15008
15009#if 0
15010class ocpnCurtain: public wxDialog
15011{
15012 DECLARE_CLASS( ocpnCurtain )
15013 DECLARE_EVENT_TABLE()
15014
15015public:
15016 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15017 ~ocpnCurtain( );
15018 bool ProcessEvent(wxEvent& event);
15019
15020};
15021
15022IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15023
15024BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15025END_EVENT_TABLE()
15026
15027ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15028{
15029 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15030}
15031
15032ocpnCurtain::~ocpnCurtain()
15033{
15034}
15035
15036bool ocpnCurtain::ProcessEvent(wxEvent& event)
15037{
15038 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15039 return GetParent()->GetEventHandler()->ProcessEvent(event);
15040}
15041#endif
15042
15043#ifdef _WIN32
15044#include <windows.h>
15045
15046HMODULE hGDI32DLL;
15047typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15048typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15049SetDeviceGammaRamp_ptr_type
15050 g_pSetDeviceGammaRamp; // the API entry points in the dll
15051GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15052
15053WORD *g_pSavedGammaMap;
15054
15055#endif
15056
15057int InitScreenBrightness() {
15058#ifdef _WIN32
15059#ifdef ocpnUSE_GL
15060 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15061 HDC hDC;
15062 BOOL bbr;
15063
15064 if (NULL == hGDI32DLL) {
15065 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15066
15067 if (NULL != hGDI32DLL) {
15068 // Get the entry points of the required functions
15069 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15070 hGDI32DLL, "SetDeviceGammaRamp");
15071 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15072 hGDI32DLL, "GetDeviceGammaRamp");
15073
15074 // If the functions are not found, unload the DLL and return false
15075 if ((NULL == g_pSetDeviceGammaRamp) ||
15076 (NULL == g_pGetDeviceGammaRamp)) {
15077 FreeLibrary(hGDI32DLL);
15078 hGDI32DLL = NULL;
15079 return 0;
15080 }
15081 }
15082 }
15083
15084 // Interface is ready, so....
15085 // Get some storage
15086 if (!g_pSavedGammaMap) {
15087 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15088
15089 hDC = GetDC(NULL); // Get the full screen DC
15090 bbr = g_pGetDeviceGammaRamp(
15091 hDC, g_pSavedGammaMap); // Get the existing ramp table
15092 ReleaseDC(NULL, hDC); // Release the DC
15093 }
15094
15095 // On Windows hosts, try to adjust the registry to allow full range
15096 // setting of Gamma table This is an undocumented Windows hack.....
15097 wxRegKey *pRegKey = new wxRegKey(
15098 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15099 "NT\\CurrentVersion\\ICM");
15100 if (!pRegKey->Exists()) pRegKey->Create();
15101 pRegKey->SetValue("GdiIcmGammaRange", 256);
15102
15103 g_brightness_init = true;
15104 return 1;
15105 }
15106#endif
15107
15108 {
15109 if (NULL == g_pcurtain) {
15110 if (top_frame::Get()->CanSetTransparent()) {
15111 // Build the curtain window
15112 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15113 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15114 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15115 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15116
15117 // g_pcurtain = new ocpnCurtain(gFrame,
15118 // wxPoint(0,0),::wxGetDisplaySize(),
15119 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15120 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15121
15122 g_pcurtain->Hide();
15123
15124 HWND hWnd = GetHwndOf(g_pcurtain);
15125 SetWindowLong(hWnd, GWL_EXSTYLE,
15126 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15127 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15128 g_pcurtain->SetTransparent(0);
15129
15130 g_pcurtain->Maximize();
15131 g_pcurtain->Show();
15132
15133 // All of this is obtuse, but necessary for Windows...
15134 g_pcurtain->Enable();
15135 g_pcurtain->Disable();
15136
15137 top_frame::Get()->Disable();
15138 top_frame::Get()->Enable();
15139 // SetFocus();
15140 }
15141 }
15142 g_brightness_init = true;
15143
15144 return 1;
15145 }
15146#else
15147 // Look for "xcalib" application
15148 wxString cmd("xcalib -version");
15149
15150 wxArrayString output;
15151 long r = wxExecute(cmd, output);
15152 if (0 != r)
15153 wxLogMessage(
15154 " External application \"xcalib\" not found. Screen brightness "
15155 "not changed.");
15156
15157 g_brightness_init = true;
15158 return 0;
15159#endif
15160}
15161
15162int RestoreScreenBrightness() {
15163#ifdef _WIN32
15164
15165 if (g_pSavedGammaMap) {
15166 HDC hDC = GetDC(NULL); // Get the full screen DC
15167 g_pSetDeviceGammaRamp(hDC,
15168 g_pSavedGammaMap); // Restore the saved ramp table
15169 ReleaseDC(NULL, hDC); // Release the DC
15170
15171 free(g_pSavedGammaMap);
15172 g_pSavedGammaMap = NULL;
15173 }
15174
15175 if (g_pcurtain) {
15176 g_pcurtain->Close();
15177 g_pcurtain->Destroy();
15178 g_pcurtain = NULL;
15179 }
15180
15181 g_brightness_init = false;
15182 return 1;
15183
15184#endif
15185
15186#ifdef BRIGHT_XCALIB
15187 if (g_brightness_init) {
15188 wxString cmd;
15189 cmd = "xcalib -clear";
15190 wxExecute(cmd, wxEXEC_ASYNC);
15191 g_brightness_init = false;
15192 }
15193
15194 return 1;
15195#endif
15196
15197 return 0;
15198}
15199
15200// Set brightness. [0..100]
15201int SetScreenBrightness(int brightness) {
15202#ifdef _WIN32
15203
15204 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15205 // some (most modern?) versions of gdi32.dll Load the required library dll,
15206 // if not already in place
15207#ifdef ocpnUSE_GL
15208 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15209 if (g_pcurtain) {
15210 g_pcurtain->Close();
15211 g_pcurtain->Destroy();
15212 g_pcurtain = NULL;
15213 }
15214
15215 InitScreenBrightness();
15216
15217 if (NULL == hGDI32DLL) {
15218 // Unicode stuff.....
15219 wchar_t wdll_name[80];
15220 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15221 LPCWSTR cstr = wdll_name;
15222
15223 hGDI32DLL = LoadLibrary(cstr);
15224
15225 if (NULL != hGDI32DLL) {
15226 // Get the entry points of the required functions
15227 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15228 hGDI32DLL, "SetDeviceGammaRamp");
15229 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15230 hGDI32DLL, "GetDeviceGammaRamp");
15231
15232 // If the functions are not found, unload the DLL and return false
15233 if ((NULL == g_pSetDeviceGammaRamp) ||
15234 (NULL == g_pGetDeviceGammaRamp)) {
15235 FreeLibrary(hGDI32DLL);
15236 hGDI32DLL = NULL;
15237 return 0;
15238 }
15239 }
15240 }
15241
15242 HDC hDC = GetDC(NULL); // Get the full screen DC
15243
15244 /*
15245 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15246 if (cmcap != CM_GAMMA_RAMP)
15247 {
15248 wxLogMessage(" Video hardware does not support brightness control by
15249 gamma ramp adjustment."); return false;
15250 }
15251 */
15252
15253 int increment = brightness * 256 / 100;
15254
15255 // Build the Gamma Ramp table
15256 WORD GammaTable[3][256];
15257
15258 int table_val = 0;
15259 for (int i = 0; i < 256; i++) {
15260 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15261 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15262 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15263
15264 table_val += increment;
15265
15266 if (table_val > 65535) table_val = 65535;
15267 }
15268
15269 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15270 ReleaseDC(NULL, hDC); // Release the DC
15271
15272 return 1;
15273 }
15274#endif
15275
15276 {
15277 if (g_pSavedGammaMap) {
15278 HDC hDC = GetDC(NULL); // Get the full screen DC
15279 g_pSetDeviceGammaRamp(hDC,
15280 g_pSavedGammaMap); // Restore the saved ramp table
15281 ReleaseDC(NULL, hDC); // Release the DC
15282 }
15283
15284 if (brightness < 100) {
15285 if (NULL == g_pcurtain) InitScreenBrightness();
15286
15287 if (g_pcurtain) {
15288 int sbrite = wxMax(1, brightness);
15289 sbrite = wxMin(100, sbrite);
15290
15291 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15292 }
15293 } else {
15294 if (g_pcurtain) {
15295 g_pcurtain->Close();
15296 g_pcurtain->Destroy();
15297 g_pcurtain = NULL;
15298 }
15299 }
15300
15301 return 1;
15302 }
15303
15304#endif
15305
15306#ifdef BRIGHT_XCALIB
15307
15308 if (!g_brightness_init) {
15309 last_brightness = 100;
15310 g_brightness_init = true;
15311 temp_file_name = wxFileName::CreateTempFileName("");
15312 InitScreenBrightness();
15313 }
15314
15315#ifdef __OPCPN_USEICC__
15316 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15317 // desired, and then activate this temporary profile using xcalib <filename>
15318 if (!CreateSimpleICCProfileFile(
15319 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15320 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15321 wxString cmd("xcalib ");
15322 cmd += temp_file_name;
15323
15324 wxExecute(cmd, wxEXEC_ASYNC);
15325 }
15326
15327#else
15328 // Or, use "xcalib -co" to set overall contrast value
15329 // This is not as nice, since the -co parameter wants to be a fraction of
15330 // the current contrast, and values greater than 100 are not allowed. As a
15331 // result, increases of contrast must do a "-clear" step first, which
15332 // produces objectionable flashing.
15333 if (brightness > last_brightness) {
15334 wxString cmd;
15335 cmd = "xcalib -clear";
15336 wxExecute(cmd, wxEXEC_ASYNC);
15337
15338 ::wxMilliSleep(10);
15339
15340 int brite_adj = wxMax(1, brightness);
15341 cmd.Printf("xcalib -co %2d -a", brite_adj);
15342 wxExecute(cmd, wxEXEC_ASYNC);
15343 } else {
15344 int brite_adj = wxMax(1, brightness);
15345 int factor = (brite_adj * 100) / last_brightness;
15346 factor = wxMax(1, factor);
15347 wxString cmd;
15348 cmd.Printf("xcalib -co %2d -a", factor);
15349 wxExecute(cmd, wxEXEC_ASYNC);
15350 }
15351
15352#endif
15353
15354 last_brightness = brightness;
15355
15356#endif
15357
15358 return 0;
15359}
15360
15361#ifdef __OPCPN_USEICC__
15362
15363#define MLUT_TAG 0x6d4c5554L
15364#define VCGT_TAG 0x76636774L
15365
15366int GetIntEndian(unsigned char *s) {
15367 int ret;
15368 unsigned char *p;
15369 int i;
15370
15371 p = (unsigned char *)&ret;
15372
15373 if (1)
15374 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15375 else
15376 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15377
15378 return ret;
15379}
15380
15381unsigned short GetShortEndian(unsigned char *s) {
15382 unsigned short ret;
15383 unsigned char *p;
15384 int i;
15385
15386 p = (unsigned char *)&ret;
15387
15388 if (1)
15389 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15390 else
15391 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15392
15393 return ret;
15394}
15395
15396// Create a very simple Gamma correction file readable by xcalib
15397int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15398 double co_green, double co_blue) {
15399 FILE *fp;
15400
15401 if (file_name) {
15402 fp = fopen(file_name, "wb");
15403 if (!fp) return -1; /* file can not be created */
15404 } else
15405 return -1; /* filename char pointer not valid */
15406
15407 // Write header
15408 char header[128];
15409 for (int i = 0; i < 128; i++) header[i] = 0;
15410
15411 fwrite(header, 128, 1, fp);
15412
15413 // Num tags
15414 int numTags0 = 1;
15415 int numTags = GetIntEndian((unsigned char *)&numTags0);
15416 fwrite(&numTags, 1, 4, fp);
15417
15418 int tagName0 = VCGT_TAG;
15419 int tagName = GetIntEndian((unsigned char *)&tagName0);
15420 fwrite(&tagName, 1, 4, fp);
15421
15422 int tagOffset0 = 128 + 4 * sizeof(int);
15423 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15424 fwrite(&tagOffset, 1, 4, fp);
15425
15426 int tagSize0 = 1;
15427 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15428 fwrite(&tagSize, 1, 4, fp);
15429
15430 fwrite(&tagName, 1, 4, fp); // another copy of tag
15431
15432 fwrite(&tagName, 1, 4, fp); // dummy
15433
15434 // Table type
15435
15436 /* VideoCardGammaTable (The simplest type) */
15437 int gammatype0 = 0;
15438 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15439 fwrite(&gammatype, 1, 4, fp);
15440
15441 int numChannels0 = 3;
15442 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15443 fwrite(&numChannels, 1, 2, fp);
15444
15445 int numEntries0 = 256;
15446 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15447 fwrite(&numEntries, 1, 2, fp);
15448
15449 int entrySize0 = 1;
15450 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15451 fwrite(&entrySize, 1, 2, fp);
15452
15453 unsigned char ramp[256];
15454
15455 // Red ramp
15456 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15457 fwrite(ramp, 256, 1, fp);
15458
15459 // Green ramp
15460 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15461 fwrite(ramp, 256, 1, fp);
15462
15463 // Blue ramp
15464 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15465 fwrite(ramp, 256, 1, fp);
15466
15467 fclose(fp);
15468
15469 return 0;
15470}
15471#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:871
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:61
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1314
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
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.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:173
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13834
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:4581
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11876
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4577
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3673
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13793
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:4527
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:805
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2358
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8033
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7842
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5109
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:896
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4658
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5389
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:183
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:789
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4602
bool IsTideDialogOpen() const
Definition chcanv.cpp:13832
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:4664
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13789
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4522
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5408
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10285
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:486
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
char IDX_station_name[MAXNAMELEN]
Name of the tidal or current station.
Definition idx_entry.h:62
int station_tz_offset
Offset in seconds to convert from harmonic data (epochs) to the station time zone.
Definition idx_entry.h:93
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:60
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:201
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:59
Data for a loaded plugin, including dl-loaded library.
int m_cap_flag
PlugIn Capabilities descriptor.
PluginLoader is a backend module without any direct GUI functionality.
const ArrayOfPlugIns * GetPlugInArray()
Return list of currently loaded plugins.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1775
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:765
Dialog for displaying query results of S57 objects.
Definition tc_win.h:46
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:77
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:36
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Display utilities.
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:76
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Miscellaneous utilities, many of which string related.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:187
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.