OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/ocpn_utils.h"
58#include "model/own_ship.h"
59#include "model/plugin_comm.h"
60#include "model/route.h"
61#include "model/routeman.h"
62#include "model/select.h"
63#include "model/select_item.h"
64#include "model/track.h"
65#include "model/ocpn_utils.h"
66
67#include "ais.h"
70#include "canvas_config.h"
71#include "canvas_menu.h"
72#include "canvas_options.h"
73#include "chartdb.h"
74#include "chartimg.h"
75#include "chcanv.h"
76#include "ch_info_win.h"
77#include "cm93.h" // for chart outline draw
78#include "compass.h"
79#include "concanv.h"
80#include "detail_slider.h"
81#include "displays.h"
82#include "hotkeys_dlg.h"
83#include "font_mgr.h"
84#include "gl_texture_descr.h"
85#include "go_to_position_dlg.h"
86#include "gshhs.h"
87#include "ienc_toolbar.h"
88#include "kml.h"
89#include "line_clip.h"
90#include "mark_info.h"
91#include "mbtiles.h"
92#include "mui_bar.h"
93#include "navutil.h"
94#include "ocpn_aui_manager.h"
95#include "ocpndc.h"
96#include "ocpn_pixel.h"
97#include "ocpn_region.h"
98#include "options.h"
99#include "piano.h"
100#include "pluginmanager.h"
101#include "quilt.h"
102#include "route_gui.h"
103#include "routemanagerdialog.h"
104#include "route_point_gui.h"
105#include "route_prop_dlg_impl.h"
106#include "s52plib.h"
107#include "s52utils.h"
108#include "s57_query_dlg.h"
109#include "s57chart.h" // for ArrayOfS57Obj
110#include "senc_manager.h"
111#include "shapefile_basemap.h"
112#include "styles.h"
113#include "tcmgr.h"
114#include "tc_win.h"
115#include "thumbwin.h"
116#include "tide_time.h"
117#include "timers.h"
118#include "toolbar.h"
119#include "top_frame.h"
120#include "track_gui.h"
121#include "track_prop_dlg.h"
122#include "undo.h"
123#include "user_colors.h"
124
125#include "s57_ocpn_utils.h"
126
127#ifdef __ANDROID__
128#include "androidUTIL.h"
129#endif
130
131#ifdef ocpnUSE_GL
132#include "gl_chart_canvas.h"
135#endif
136
137#ifdef __VISUALC__
138#include <wx/msw/msvcrt.h>
139#endif
140
141#ifndef __WXMSW__
142#include <signal.h>
143#include <setjmp.h>
144#endif
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173arrayofCanvasPtr g_canvasArray;
175static bool g_bSmoothRecenter = true;
176static bool bDrawCurrentValues;
186static int mouse_x;
196static int mouse_y;
197static bool mouse_leftisdown;
198static bool g_brouteCreating;
199static int r_gamma_mult;
200static int g_gamma_mult;
201static int b_gamma_mult;
202static int gamma_state;
203static bool g_brightness_init;
204static int last_brightness;
205static wxGLContext *g_pGLcontext; // shared common context
206
207// "Curtain" mode parameters
208static wxDialog *g_pcurtain;
209
210static wxString g_lastS52PLIBPluginMessage;
211
212#define MIN_BRIGHT 10
213#define MAX_BRIGHT 100
214
215//------------------------------------------------------------------------------
216// ChartCanvas Implementation
217//------------------------------------------------------------------------------
218BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
219EVT_PAINT(ChartCanvas::OnPaint)
220EVT_ACTIVATE(ChartCanvas::OnActivate)
221EVT_SIZE(ChartCanvas::OnSize)
222#ifndef HAVE_WX_GESTURE_EVENTS
223EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
224#endif
225EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
226EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
227EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
228EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
229EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
230EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
231EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
232EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
233EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
234EVT_KEY_UP(ChartCanvas::OnKeyUp)
235EVT_CHAR(ChartCanvas::OnKeyChar)
236EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
237EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
238EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
239EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
240EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
241EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
242EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
243EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
244EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
245EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
246
247END_EVENT_TABLE()
248
249// Define a constructor for my canvas
250ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
251 : AbstractChartCanvas(frame, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
252 m_nmea_log(nmea_log) {
253 parent_frame = frame; // save a pointer to parent
254 m_canvasIndex = canvasIndex;
255
256 pscratch_bm = NULL;
257
258 SetBackgroundColour(wxColour(0, 0, 0));
259 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
260 // color scheme change
261
262 m_groupIndex = 0;
263 m_bDrawingRoute = false;
264 m_bRouteEditing = false;
265 m_bMarkEditing = false;
266 m_bRoutePoinDragging = false;
267 m_bIsInRadius = false;
268 m_bMayToggleMenuBar = true;
269
270 m_bFollow = false;
271 m_bShowNavobjects = true;
272 m_bTCupdate = false;
273 m_bAppendingRoute = false; // was true in MSW, why??
274 pThumbDIBShow = NULL;
275 m_bShowCurrent = false;
276 m_bShowTide = false;
277 bShowingCurrent = false;
278 pCwin = NULL;
279 warp_flag = false;
280 m_bzooming = false;
281 m_b_paint_enable = true;
282 m_routeState = 0;
283
284 pss_overlay_bmp = NULL;
285 pss_overlay_mask = NULL;
286 m_bChartDragging = false;
287 m_bMeasure_Active = false;
288 m_bMeasure_DistCircle = false;
289 m_pMeasureRoute = NULL;
290 m_pTrackRolloverWin = NULL;
291 m_pRouteRolloverWin = NULL;
292 m_pAISRolloverWin = NULL;
293 m_bedge_pan = false;
294 m_disable_edge_pan = false;
295 m_dragoffsetSet = false;
296 m_bautofind = false;
297 m_bFirstAuto = true;
298 m_groupIndex = 0;
299 m_singleChart = NULL;
300 m_upMode = NORTH_UP_MODE;
301 m_bShowAIS = true;
302 m_bShowAISScaled = false;
303 m_timed_move_vp_active = false;
304 m_inPinch = false;
305 m_disable_adjust_on_zoom = false;
306
307 m_vLat = 0.;
308 m_vLon = 0.;
309
310 m_pCIWin = NULL;
311
312 m_pSelectedRoute = NULL;
313 m_pSelectedTrack = NULL;
314 m_pRoutePointEditTarget = NULL;
315 m_pFoundPoint = NULL;
316 m_pMouseRoute = NULL;
317 m_prev_pMousePoint = NULL;
318 m_pEditRouteArray = NULL;
319 m_pFoundRoutePoint = NULL;
320 m_FinishRouteOnKillFocus = true;
321
322 m_pRolloverRouteSeg = NULL;
323 m_pRolloverTrackSeg = NULL;
324 m_bsectors_shown = false;
325
326 m_bbrightdir = false;
327 r_gamma_mult = 1;
328 g_gamma_mult = 1;
329 b_gamma_mult = 1;
330
331 m_pos_image_user_day = NULL;
332 m_pos_image_user_dusk = NULL;
333 m_pos_image_user_night = NULL;
334 m_pos_image_user_grey_day = NULL;
335 m_pos_image_user_grey_dusk = NULL;
336 m_pos_image_user_grey_night = NULL;
337
338 m_zoom_factor = 1;
339 m_rotation_speed = 0;
340 m_mustmove = 0;
341
342 m_OSoffsetx = 0.;
343 m_OSoffsety = 0.;
344
345 m_pos_image_user_yellow_day = NULL;
346 m_pos_image_user_yellow_dusk = NULL;
347 m_pos_image_user_yellow_night = NULL;
348
349 SetOwnShipState(SHIP_INVALID);
350
351 undo = new Undo(this);
352
353 VPoint.Invalidate();
354
355 m_glcc = NULL;
356
357 m_focus_indicator_pix = 1;
358
359 m_pCurrentStack = NULL;
360 m_bpersistent_quilt = false;
361 m_piano_ctx_menu = NULL;
362 m_Compass = NULL;
363 m_NotificationsList = NULL;
364 m_notification_button = NULL;
365
366 g_ChartNotRenderScaleFactor = 2.0;
367 m_bShowScaleInStatusBar = true;
368
369 m_muiBar = NULL;
370 m_bShowScaleInStatusBar = false;
371 m_show_focus_bar = true;
372
373 m_bShowOutlines = false;
374 m_bDisplayGrid = false;
375 m_bShowDepthUnits = true;
376 m_encDisplayCategory = (int)STANDARD;
377
378 m_encShowLights = true;
379 m_encShowAnchor = true;
380 m_encShowDataQual = false;
381 m_bShowGPS = true;
382 m_pQuilt = new Quilt(this);
383 SetQuiltMode(true);
384 SetAlertString("");
385 m_sector_glat = 0;
386 m_sector_glon = 0;
387 g_PrintingInProgress = false;
388
389#ifdef HAVE_WX_GESTURE_EVENTS
390 m_oldVPSScale = -1.0;
391 m_popupWanted = false;
392 m_leftdown = false;
393#endif /* HAVE_WX_GESTURE_EVENTS */
394 m_inLongPress = false;
395 m_sw_down_time = 0;
396 m_sw_up_time = 0;
397 m_sw_left_down.Start();
398 m_sw_left_up.Start();
399
400 SetupGlCanvas();
401
402 singleClickEventIsValid = false;
403
404 // Build the cursors
405
406 pCursorLeft = NULL;
407 pCursorRight = NULL;
408 pCursorUp = NULL;
409 pCursorDown = NULL;
410 pCursorArrow = NULL;
411 pCursorPencil = NULL;
412 pCursorCross = NULL;
413
414 RebuildCursors();
415
416 SetCursor(*pCursorArrow);
417
418 pPanTimer = new wxTimer(this, m_MouseDragging);
419 pPanTimer->Stop();
420
421 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
422 pMovementTimer->Stop();
423
424 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
425 pMovementStopTimer->Stop();
426
427 pRotDefTimer = new wxTimer(this, ROT_TIMER);
428 pRotDefTimer->Stop();
429
430 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
431 m_DoubleClickTimer->Stop();
432
433 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
434 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
435 m_chart_drag_inertia_active = false;
436
437 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
438 m_animationActive = false;
439 m_menuTimer.SetOwner(this, MENU_TIMER);
440 m_tap_timer.SetOwner(this, TAP_TIMER);
441
442 m_panx = m_pany = 0;
443 m_panspeed = 0;
444 m_panx_target_final = m_pany_target_final = 0;
445 m_panx_target_now = m_pany_target_now = 0;
446 m_DragTrigger = -1;
447
448 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
449 pCurTrackTimer->Stop();
450 m_curtrack_timer_msec = 10;
451
452 m_wheelzoom_stop_oneshot = 0;
453 m_last_wheel_dir = 0;
454
455 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
456
457 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
458
459 m_rollover_popup_timer_msec = 20;
460
461 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
462
463 m_b_rot_hidef = true;
464
465 proute_bm = NULL;
466 m_prot_bm = NULL;
467
468 m_upMode = NORTH_UP_MODE;
469 m_bLookAhead = false;
470
471 // Set some benign initial values
472
473 m_cs = GLOBAL_COLOR_SCHEME_DAY;
474 VPoint.clat = 0;
475 VPoint.clon = 0;
476 VPoint.view_scale_ppm = 1;
477 VPoint.Invalidate();
478 m_nMeasureState = 0;
479 m_ignore_next_leftup = false;
480
481 m_canvas_scale_factor = 1.;
482
483 m_canvas_width = 1000;
484
485 m_overzoomTextWidth = 0;
486 m_overzoomTextHeight = 0;
487
488 // Create the default world chart
489 pWorldBackgroundChart = new GSHHSChart;
490 gShapeBasemap.Reset();
491
492 // Create the default depth unit emboss maps
493 m_pEM_Feet = NULL;
494 m_pEM_Meters = NULL;
495 m_pEM_Fathoms = NULL;
496
497 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
498
499 m_pEM_OverZoom = NULL;
500 SetOverzoomFont();
501 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
502
503 // Build icons for tide/current points
504 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
505 m_bmTideDay =
506 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
507
508 // Dusk
509 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
510
511 // Night
512 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
513
514 // Build Dusk/Night ownship icons
515 double factor_dusk = 0.5;
516 double factor_night = 0.25;
517
518 // Red
519 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
520
521 int rimg_width = m_os_image_red_day.GetWidth();
522 int rimg_height = m_os_image_red_day.GetHeight();
523
524 m_os_image_red_dusk = m_os_image_red_day.Copy();
525 m_os_image_red_night = m_os_image_red_day.Copy();
526
527 for (int iy = 0; iy < rimg_height; iy++) {
528 for (int ix = 0; ix < rimg_width; ix++) {
529 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
530 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
531 m_os_image_red_day.GetGreen(ix, iy),
532 m_os_image_red_day.GetBlue(ix, iy));
533 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
534 hsv.value = hsv.value * factor_dusk;
535 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
536 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
537
538 hsv = wxImage::RGBtoHSV(rgb);
539 hsv.value = hsv.value * factor_night;
540 nrgb = wxImage::HSVtoRGB(hsv);
541 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
542 }
543 }
544 }
545
546 // Grey
547 m_os_image_grey_day =
548 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
549
550 int gimg_width = m_os_image_grey_day.GetWidth();
551 int gimg_height = m_os_image_grey_day.GetHeight();
552
553 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
554 m_os_image_grey_night = m_os_image_grey_day.Copy();
555
556 for (int iy = 0; iy < gimg_height; iy++) {
557 for (int ix = 0; ix < gimg_width; ix++) {
558 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
559 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
560 m_os_image_grey_day.GetGreen(ix, iy),
561 m_os_image_grey_day.GetBlue(ix, iy));
562 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
563 hsv.value = hsv.value * factor_dusk;
564 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
565 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
566
567 hsv = wxImage::RGBtoHSV(rgb);
568 hsv.value = hsv.value * factor_night;
569 nrgb = wxImage::HSVtoRGB(hsv);
570 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
571 }
572 }
573 }
574
575 // Yellow
576 m_os_image_yellow_day = m_os_image_red_day.Copy();
577
578 gimg_width = m_os_image_yellow_day.GetWidth();
579 gimg_height = m_os_image_yellow_day.GetHeight();
580
581 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
582 m_os_image_yellow_night = m_os_image_red_day.Copy();
583
584 for (int iy = 0; iy < gimg_height; iy++) {
585 for (int ix = 0; ix < gimg_width; ix++) {
586 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
587 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
588 m_os_image_yellow_day.GetGreen(ix, iy),
589 m_os_image_yellow_day.GetBlue(ix, iy));
590 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
591 hsv.hue += 60. / 360.; // shift to yellow
592 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
593 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
594
595 hsv = wxImage::RGBtoHSV(rgb);
596 hsv.value = hsv.value * factor_dusk;
597 hsv.hue += 60. / 360.; // shift to yellow
598 nrgb = wxImage::HSVtoRGB(hsv);
599 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
600
601 hsv = wxImage::RGBtoHSV(rgb);
602 hsv.hue += 60. / 360.; // shift to yellow
603 hsv.value = hsv.value * factor_night;
604 nrgb = wxImage::HSVtoRGB(hsv);
605 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
606 }
607 }
608 }
609
610 // Set initial pointers to ownship images
611 m_pos_image_red = &m_os_image_red_day;
612 m_pos_image_yellow = &m_os_image_yellow_day;
613 m_pos_image_grey = &m_os_image_grey_day;
614
615 SetUserOwnship();
616
617 m_pBrightPopup = NULL;
618
619#ifdef ocpnUSE_GL
620 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
621#endif
622
623 SetupGridFont();
624
625 m_Piano = new Piano(this);
626
627 m_bShowCompassWin = true;
628 m_Compass = new ocpnCompass(this);
629 m_Compass->SetScaleFactor(g_compass_scalefactor);
630 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
631
632 if (IsPrimaryCanvas()) {
633 m_notification_button = new NotificationButton(this);
634 m_notification_button->SetScaleFactor(g_compass_scalefactor);
635 m_notification_button->Show(true);
636 }
637
638 m_pianoFrozen = false;
639
640 SetMinSize(wxSize(200, 200));
641
642 m_displayScale = 1.0;
643#if defined(__WXOSX__) || defined(__WXGTK3__)
644 // Support scaled HDPI displays.
645 m_displayScale = GetContentScaleFactor();
646#endif
647 VPoint.SetPixelScale(m_displayScale);
648
649#ifdef HAVE_WX_GESTURE_EVENTS
650 // if (!m_glcc)
651 {
652 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
653 wxLogError("Failed to enable touch events");
654 }
655
656 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
657
658 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
659 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
660
661 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
662 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
663
664 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
665 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
666
667 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
668 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
669 }
670#endif
671
672 // Listen for notification events
673 auto &noteman = NotificationManager::GetInstance();
674
675 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
676 evt_notificationlist_change_listener.Listen(
677 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
678 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
679 if (m_NotificationsList && m_NotificationsList->IsShown()) {
680 m_NotificationsList->ReloadNotificationList();
681 }
682 Refresh();
683 });
684}
685
686ChartCanvas::~ChartCanvas() {
687 delete pThumbDIBShow;
688
689 // Delete Cursors
690 delete pCursorLeft;
691 delete pCursorRight;
692 delete pCursorUp;
693 delete pCursorDown;
694 delete pCursorArrow;
695 delete pCursorPencil;
696 delete pCursorCross;
697
698 delete pPanTimer;
699 delete pMovementTimer;
700 delete pMovementStopTimer;
701 delete pCurTrackTimer;
702 delete pRotDefTimer;
703 delete m_DoubleClickTimer;
704
705 delete m_pTrackRolloverWin;
706 delete m_pRouteRolloverWin;
707 delete m_pAISRolloverWin;
708 delete m_pBrightPopup;
709
710 delete m_pCIWin;
711
712 delete pscratch_bm;
713
714 m_dc_route.SelectObject(wxNullBitmap);
715 delete proute_bm;
716
717 delete pWorldBackgroundChart;
718 delete pss_overlay_bmp;
719
720 delete m_pEM_Feet;
721 delete m_pEM_Meters;
722 delete m_pEM_Fathoms;
723
724 delete m_pEM_OverZoom;
725 // delete m_pEM_CM93Offset;
726
727 delete m_prot_bm;
728
729 delete m_pos_image_user_day;
730 delete m_pos_image_user_dusk;
731 delete m_pos_image_user_night;
732 delete m_pos_image_user_grey_day;
733 delete m_pos_image_user_grey_dusk;
734 delete m_pos_image_user_grey_night;
735 delete m_pos_image_user_yellow_day;
736 delete m_pos_image_user_yellow_dusk;
737 delete m_pos_image_user_yellow_night;
738
739 delete undo;
740#ifdef ocpnUSE_GL
741 if (!g_bdisable_opengl) {
742 delete m_glcc;
743
744#if wxCHECK_VERSION(2, 9, 0)
745 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
746#endif
747 }
748#endif
749
750 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
751 // wx tries to deliver events to this canvas during destroy.
752 MUIBar *muiBar = m_muiBar;
753 m_muiBar = 0;
754 delete muiBar;
755 delete m_pQuilt;
756 delete m_pCurrentStack;
757 delete m_Compass;
758 delete m_Piano;
759 delete m_notification_button;
760}
761
762void ChartCanvas::SetupGridFont() {
763 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
764 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
765 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
766 m_pgridFont = FontMgr::Get().FindOrCreateFont(
767 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
768 FALSE, wxString("Arial"));
769}
770
771void ChartCanvas::RebuildCursors() {
772 delete pCursorLeft;
773 delete pCursorRight;
774 delete pCursorUp;
775 delete pCursorDown;
776 delete pCursorArrow;
777 delete pCursorPencil;
778 delete pCursorCross;
779
780 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
781 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
782
783 double pencilScale =
784 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
785
786 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
787 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
788 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
789 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
790 wxImage ICursorPencil =
791 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
792 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
793
794#if !defined(__WXMSW__) && !defined(__WXQT__)
795 ICursorLeft.ConvertAlphaToMask(128);
796 ICursorRight.ConvertAlphaToMask(128);
797 ICursorUp.ConvertAlphaToMask(128);
798 ICursorDown.ConvertAlphaToMask(128);
799 ICursorPencil.ConvertAlphaToMask(10);
800 ICursorCross.ConvertAlphaToMask(10);
801#endif
802
803 if (ICursorLeft.Ok()) {
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
805 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
806 pCursorLeft = new wxCursor(ICursorLeft);
807 } else
808 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
809
810 if (ICursorRight.Ok()) {
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
812 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
813 pCursorRight = new wxCursor(ICursorRight);
814 } else
815 pCursorRight = new wxCursor(wxCURSOR_ARROW);
816
817 if (ICursorUp.Ok()) {
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
819 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
820 pCursorUp = new wxCursor(ICursorUp);
821 } else
822 pCursorUp = new wxCursor(wxCURSOR_ARROW);
823
824 if (ICursorDown.Ok()) {
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
826 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
827 pCursorDown = new wxCursor(ICursorDown);
828 } else
829 pCursorDown = new wxCursor(wxCURSOR_ARROW);
830
831 if (ICursorPencil.Ok()) {
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
833 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
834 pCursorPencil = new wxCursor(ICursorPencil);
835 } else
836 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
837
838 if (ICursorCross.Ok()) {
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
840 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
841 pCursorCross = new wxCursor(ICursorCross);
842 } else
843 pCursorCross = new wxCursor(wxCURSOR_ARROW);
844
845 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
846 pPlugIn_Cursor = NULL;
847}
848
849void ChartCanvas::CanvasApplyLocale() {
850 CreateDepthUnitEmbossMaps(m_cs);
851 CreateOZEmbossMapData(m_cs);
852}
853
854void ChartCanvas::SetupGlCanvas() {
855#ifndef __ANDROID__
856#ifdef ocpnUSE_GL
857 if (!g_bdisable_opengl) {
858 if (g_bopengl) {
859 wxLogMessage("Creating glChartCanvas");
860 m_glcc = new glChartCanvas(this);
861
862 // We use one context for all GL windows, so that textures etc will be
863 // automatically shared
864 if (IsPrimaryCanvas()) {
865 // qDebug() << "Creating Primary Context";
866
867 // wxGLContextAttrs ctxAttr;
868 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
869 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
870 // NULL, &ctxAttr);
871 wxGLContext *pctx = new wxGLContext(m_glcc);
872 m_glcc->SetContext(pctx);
873 g_pGLcontext = pctx; // Save a copy of the common context
874 } else {
875#ifdef __WXOSX__
876 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
877#else
878 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
879 // saved common context
880#endif
881 }
882 }
883 }
884#endif
885#endif
886
887#ifdef __ANDROID__ // ocpnUSE_GL
888 if (!g_bdisable_opengl) {
889 if (g_bopengl) {
890 // qDebug() << "SetupGlCanvas";
891 wxLogMessage("Creating glChartCanvas");
892
893 // We use one context for all GL windows, so that textures etc will be
894 // automatically shared
895 if (IsPrimaryCanvas()) {
896 qDebug() << "Creating Primary glChartCanvas";
897
898 // wxGLContextAttrs ctxAttr;
899 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
900 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
901 // NULL, &ctxAttr);
902 m_glcc = new glChartCanvas(this);
903
904 wxGLContext *pctx = new wxGLContext(m_glcc);
905 m_glcc->SetContext(pctx);
906 g_pGLcontext = pctx; // Save a copy of the common context
907 m_glcc->m_pParentCanvas = this;
908 // m_glcc->Reparent(this);
909 } else {
910 qDebug() << "Creating Secondary glChartCanvas";
911 // QGLContext *pctx =
912 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
913 // << "pctx: " << pctx;
914
915 m_glcc =
916 new glChartCanvas(wxTheApp->GetTopWindow(),
917 top_frame::Get()->GetWxGlCanvas()); // Shared
918 // m_glcc = new glChartCanvas(this, pctx); //Shared
919 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
920 wxGLContext *pwxctx = new wxGLContext(m_glcc);
921 m_glcc->SetContext(pwxctx);
922 m_glcc->m_pParentCanvas = this;
923 // m_glcc->Reparent(this);
924 }
925 }
926 }
927#endif
928}
929
930void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
931 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
932
933 // On Android, we get a KillFocus on just about every keystroke.
934 // Why?
935#ifdef __ANDROID__
936 return;
937#endif
938
939 // Special logic:
940 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
941 // canvas focus. Why??? Who knows... So, we provide for this case by
942 // starting a timer if required to actually Finish() a route on a legitimate
943 // focus change, but not if the focus is quickly regained ( <20 msec.) on
944 // this canvas.
945#ifdef __WXOSX__
946 if (m_routeState && m_FinishRouteOnKillFocus)
947 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
948#else
949 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
950#endif
951}
952
953void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
954 m_routeFinishTimer.Stop();
955
956 // Try to keep the global top-line menubar selections up to date with the
957 // current "focus" canvas
958 top_frame::Get()->UpdateGlobalMenuItems(this);
959
960 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
961}
962
963void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
964 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
965}
966
967#ifdef HAVE_WX_GESTURE_EVENTS
968void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
969#ifdef __ANDROID__
970 /* we defer the popup menu call upon the leftup event
971 else the menu disappears immediately,
972 (see
973 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
974 */
975 m_popupWanted = true;
976#else
977 m_inLongPress = !g_bhide_context_menus;
978
979 // Send a synthetic mouse left-up event to sync the mouse pan logic.
980 m_menuPos = event.GetPosition();
981 wxMouseEvent ev(wxEVT_LEFT_UP);
982 ev.m_x = m_menuPos.x;
983 ev.m_y = m_menuPos.y;
984 wxPostEvent(this, ev);
985
986 // In touch mode, send a "RIGHT CLICK" event, for plugins
987 if (g_btouch) {
988 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
989 ev_right_click.m_x = m_menuPos.x;
990 ev_right_click.m_y = m_menuPos.y;
991 MouseEvent(ev_right_click);
992 }
993#endif
994}
995
996void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
997 // not implemented yet
998}
999
1000void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1001
1002void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1003
1004void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1005#ifdef __WXGTK__
1006 long dt = m_sw_left_up.Time() - m_sw_up_time;
1007 m_sw_up_time = m_sw_left_up.Time();
1008
1009 // printf(" dt %ld\n",dt);
1010 if (dt < 5) {
1011 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1012 // ignore it.
1013 return;
1014 }
1015#endif
1016 // printf("Left_UP\n");
1017
1018 wxPoint pos = event.GetPosition();
1019
1020 m_leftdown = false;
1021
1022 if (!m_popupWanted) {
1023 wxMouseEvent ev(wxEVT_LEFT_UP);
1024 ev.m_x = pos.x;
1025 ev.m_y = pos.y;
1026 MouseEvent(ev);
1027 return;
1028 }
1029
1030 m_popupWanted = false;
1031
1032 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1033 ev.m_x = pos.x;
1034 ev.m_y = pos.y;
1035
1036 MouseEvent(ev);
1037}
1038
1039void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1040 m_leftdown = true;
1041
1042 // Detect and manage multiple left-downs coming from GTK mouse emulation
1043#ifdef __WXGTK__
1044 long dt = m_sw_left_down.Time() - m_sw_down_time;
1045 m_sw_down_time = m_sw_left_down.Time();
1046
1047 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1048
1049 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1050 // The timing between the two events is dependent upon the wxWidgets
1051 // message queue status, and the processing time required for intervening
1052 // events.
1053 // We detect and remove the duplicate events by measuring the elapsed time
1054 // between arrival of events.
1055 // Choose a duplicate detection time long enough to catch worst case time lag
1056 // between duplicating events, but considerably shorter than the nominal
1057 // "intentional double-click" time interval defined generally as 350 msec.
1058 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1059 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1060 // ignore it.
1061 return;
1062 }
1063#endif
1064
1065 // printf("Left_DOWN\n");
1066
1067 // detect and manage double-tap
1068#ifdef __WXGTK__
1069 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1070 2; // Use system setting for distance
1071 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1072 m_lastTapPos.y - max_double_click_distance,
1073 max_double_click_distance * 2, max_double_click_distance * 2);
1074
1075 // A new tap has started, check if it's close enough and in time
1076 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1077 // printf(" TapBump 1\n");
1078 m_tap_count += 1;
1079 } else {
1080 // printf(" TapSet 1\n");
1081 m_tap_count = 1;
1082 m_lastTapPos = event.GetPosition();
1083 m_tap_timer.StartOnce(
1084 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1085 }
1086
1087 if (m_tap_count == 2) {
1088 // printf(" Doubletap detected\n");
1089 m_tap_count = 0; // Reset after a double-tap
1090
1091 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1092 ev.m_x = event.m_x;
1093 ev.m_y = event.m_y;
1094 // wxPostEvent(this, ev);
1095 MouseEvent(ev);
1096 return;
1097 }
1098
1099#endif
1100
1101 MouseEvent(event);
1102}
1103
1104void ChartCanvas::OnMotion(wxMouseEvent &event) {
1105 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1106 dragging, upon simple click, and without the OnLeftDown event before Thus,
1107 this consists in skiping it, and setting the leftdown bit according to a
1108 status that we trust */
1109 event.m_leftDown = m_leftdown;
1110 MouseEvent(event);
1111}
1112
1113void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1114 /* there are spurious end zoom events upon right-click */
1115 if (event.IsGestureEnd()) return;
1116
1117 double factor = event.GetZoomFactor();
1118
1119 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1120 m_oldVPSScale = GetVPScale();
1121 }
1122
1123 double current_vps = GetVPScale();
1124 double wanted_factor = m_oldVPSScale / current_vps * factor;
1125
1126 ZoomCanvas(wanted_factor, true, false);
1127
1128 // Allow combined zoom/pan operation
1129 if (event.IsGestureStart()) {
1130 m_zoomStartPoint = event.GetPosition();
1131 } else {
1132 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1133 PanCanvas(-delta.x, -delta.y);
1134 m_zoomStartPoint = event.GetPosition();
1135 }
1136}
1137
1138void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1139
1140void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1141 DoRotateCanvas(0.0);
1142}
1143#endif /* HAVE_WX_GESTURE_EVENTS */
1144
1145void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1146 // printf("tap timer %d\n", m_tap_count);
1147 m_tap_count = 0;
1148}
1149
1150void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1151 m_FinishRouteOnKillFocus = false;
1152 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1153 m_FinishRouteOnKillFocus = true;
1154}
1155
1156void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1157 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1158 m_vLat = pcc->iLat;
1159 m_vLon = pcc->iLon;
1160
1161 m_restore_dbindex = pcc->DBindex;
1162 m_bFollow = pcc->bFollow;
1163 if (pcc->GroupID < 0) pcc->GroupID = 0;
1164
1165 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1166 m_groupIndex = 0;
1167 else
1168 m_groupIndex = pcc->GroupID;
1169
1170 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1171
1172 ShowTides(pcc->bShowTides);
1173 ShowCurrents(pcc->bShowCurrents);
1174
1175 SetShowDepthUnits(pcc->bShowDepthUnits);
1176 SetShowGrid(pcc->bShowGrid);
1177 SetShowOutlines(pcc->bShowOutlines);
1178
1179 SetShowAIS(pcc->bShowAIS);
1180 SetAttenAIS(pcc->bAttenAIS);
1181
1182 // ENC options
1183 SetShowENCText(pcc->bShowENCText);
1184 m_encDisplayCategory = pcc->nENCDisplayCategory;
1185 m_encShowDepth = pcc->bShowENCDepths;
1186 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1187 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1188 m_encShowLights = pcc->bShowENCLights;
1189 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1190 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1191 m_encShowDataQual = pcc->bShowENCDataQuality;
1192
1193 bool courseUp = pcc->bCourseUp;
1194 bool headUp = pcc->bHeadUp;
1195 m_upMode = NORTH_UP_MODE;
1196 if (courseUp)
1197 m_upMode = COURSE_UP_MODE;
1198 else if (headUp)
1199 m_upMode = HEAD_UP_MODE;
1200
1201 m_bLookAhead = pcc->bLookahead;
1202
1203 m_singleChart = NULL;
1204}
1205
1206void ChartCanvas::ApplyGlobalSettings() {
1207 // GPS compas window
1208 if (m_Compass) {
1209 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1210 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1211 }
1212 if (m_notification_button) m_notification_button->UpdateStatus();
1213}
1214
1215void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1216 bool groupOK = CheckGroup(m_groupIndex);
1217
1218 if (!groupOK) {
1219 SetGroupIndex(m_groupIndex, true);
1220 }
1221}
1222
1223void ChartCanvas::SetShowGPS(bool bshow) {
1224 if (m_bShowGPS != bshow) {
1225 delete m_Compass;
1226 m_Compass = new ocpnCompass(this, bshow);
1227 m_Compass->SetScaleFactor(g_compass_scalefactor);
1228 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1229 }
1230 m_bShowGPS = bshow;
1231}
1232
1233void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1234 m_bShowCompassWin = bshow;
1235 if (m_Compass) {
1236 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1237 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1238 }
1239}
1240
1241int ChartCanvas::GetPianoHeight() {
1242 int height = 0;
1243 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1244
1245 return height;
1246}
1247
1248void ChartCanvas::ConfigureChartBar() {
1249 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1250
1251 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1252 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1253
1254 if (GetQuiltMode()) {
1255 m_Piano->SetRoundedRectangles(true);
1256 }
1257 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1258 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1259 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1260}
1261
1262void ChartCanvas::ShowTides(bool bShow) {
1263 top_frame::Get()->LoadHarmonics();
1264
1265 if (ptcmgr->IsReady()) {
1266 SetbShowTide(bShow);
1267
1268 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1269 } else {
1270 wxLogMessage("Chart1::Event...TCMgr Not Available");
1271 SetbShowTide(false);
1272 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1273 }
1274
1275 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1276 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1277
1278 // TODO
1279 // if( GetbShowTide() ) {
1280 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1281 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1282 // update
1283 // } else
1284 // FrameTCTimer.Stop();
1285}
1286
1287void ChartCanvas::ShowCurrents(bool bShow) {
1288 top_frame::Get()->LoadHarmonics();
1289
1290 if (ptcmgr->IsReady()) {
1291 SetbShowCurrent(bShow);
1292 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1293 } else {
1294 wxLogMessage("Chart1::Event...TCMgr Not Available");
1295 SetbShowCurrent(false);
1296 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1297 }
1298
1299 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1300 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1301
1302 // TODO
1303 // if( GetbShowCurrent() ) {
1304 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1305 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1306 // update
1307 // } else
1308 // FrameTCTimer.Stop();
1309}
1310
1311// TODO
1312static ChartDummy *pDummyChart;
1313
1316
1317void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1318
1319void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1320 SetAlertString("");
1321
1322 int new_index = index;
1323 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1324
1325 bool bgroup_override = false;
1326 int old_group_index = new_index;
1327
1328 if (!CheckGroup(new_index)) {
1329 new_index = 0;
1330 bgroup_override = true;
1331 }
1332
1333 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1334 new_index = index;
1335
1336 // Get the currently displayed chart native scale, and the current ViewPort
1337 int current_chart_native_scale = GetCanvasChartNativeScale();
1338 ViewPort vp = GetVP();
1339
1340 m_groupIndex = new_index;
1341
1342 // Are there ENCs in this group
1343 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1344
1345 // Update the MUIBar for ENC availability
1346 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1347
1348 // Allow the chart database to pre-calculate the MBTile inclusion test
1349 // boolean...
1350 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1351
1352 // Invalidate the "sticky" chart on group change, since it might not be in
1353 // the new group
1354 g_sticky_chart = -1;
1355
1356 // We need a chartstack and quilt to figure out which chart to open in the
1357 // new group
1358 UpdateCanvasOnGroupChange();
1359
1360 int dbi_now = -1;
1361 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1362
1363 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1364
1365 // If a new reference chart is indicated, set a good scale for it.
1366 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1367 double best_scale = GetBestStartScale(dbi_hint, vp);
1368 SetVPScale(best_scale);
1369 }
1370
1371 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1372
1373 // Refresh the canvas, selecting the "best" chart,
1374 // applying the prior ViewPort exactly
1375 canvasChartsRefresh(dbi_hint);
1376
1377 UpdateCanvasControlBar();
1378
1379 if (!autoSwitch && bgroup_override) {
1380 // show a short timed message box
1381 wxString msg(_("Group \""));
1382
1383 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1384 msg += pGroup->m_group_name;
1385
1386 msg += _("\" is empty.");
1387
1388 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1389
1390 return;
1391 }
1392
1393 // Message box is deferred so that canvas refresh occurs properly before
1394 // dialog
1395 if (bgroup_override) {
1396 wxString msg(_("Group \""));
1397
1398 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1399 msg += pGroup->m_group_name;
1400
1401 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1402
1403 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1404 }
1405}
1406
1407bool ChartCanvas::CheckGroup(int igroup) {
1408 if (!ChartData) return true; // Not known yet...
1409
1410 if (igroup == 0) return true; // "all charts" is always OK
1411
1412 if (igroup < 0) // negative group is an error
1413 return false;
1414
1415 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1416
1417 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1418 // and auto-shift to group 0
1419 return false;
1420
1421 for (const auto &elem : pGroup->m_element_array) {
1422 for (unsigned int ic = 0;
1423 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1424 auto &cte = ChartData->GetChartTableEntry(ic);
1425 wxString chart_full_path(cte.GetpFullPath(), wxConvUTF8);
1426
1427 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1428 }
1429 }
1430
1431 // If necessary, check for GSHHS
1432 for (const auto &elem : pGroup->m_element_array) {
1433 const wxString &element_root = elem.m_element_name;
1434 wxString test_string = "GSHH";
1435 if (element_root.Upper().Contains(test_string)) return true;
1436 }
1437
1438 return false;
1439}
1440
1441void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1442 if (!ChartData) return;
1443
1444 AbstractPlatform::ShowBusySpinner();
1445
1446 double old_scale = GetVPScale();
1447 InvalidateQuilt();
1448 SetQuiltRefChart(-1);
1449
1450 m_singleChart = NULL;
1451
1452 // delete m_pCurrentStack;
1453 // m_pCurrentStack = NULL;
1454
1455 // Build a new ChartStack
1456 if (!m_pCurrentStack) {
1457 m_pCurrentStack = new ChartStack;
1458 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1459 }
1460
1461 if (-1 != dbi_hint) {
1462 if (GetQuiltMode()) {
1463 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1464 SetQuiltRefChart(dbi_hint);
1465 } else {
1466 // Open the saved chart
1467 ChartBase *pTentative_Chart;
1468 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1469
1470 if (pTentative_Chart) {
1471 /* m_singleChart is always NULL here, (set above) should this go before
1472 * that? */
1473 if (m_singleChart) m_singleChart->Deactivate();
1474
1475 m_singleChart = pTentative_Chart;
1476 m_singleChart->Activate();
1477
1478 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1479 GetpCurrentStack(), m_singleChart->GetFullPath());
1480 }
1481 }
1482
1483 // refresh_Piano();
1484 } else {
1485 // Select reference chart from the stack, as though clicked by user
1486 // Make it the smallest scale chart on the stack
1487 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1488 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1489 SetQuiltRefChart(selected_index);
1490 }
1491
1492 // Validate the correct single chart, or set the quilt mode as appropriate
1493 SetupCanvasQuiltMode();
1494 if (!GetQuiltMode() && m_singleChart == 0) {
1495 // use a dummy like in DoChartUpdate
1496 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1497 m_singleChart = pDummyChart;
1498 SetVPScale(old_scale);
1499 }
1500
1501 ReloadVP();
1502
1503 UpdateCanvasControlBar();
1504 UpdateGPSCompassStatusBox(true);
1505
1506 SetCursor(wxCURSOR_ARROW);
1507
1508 AbstractPlatform::HideBusySpinner();
1509}
1510
1511bool ChartCanvas::DoCanvasUpdate() {
1512 double tLat, tLon; // Chart Stack location
1513 double vpLat, vpLon; // ViewPort location
1514 bool blong_jump = false;
1515 meters_to_shift = 0;
1516 dir_to_shift = 0;
1517
1518 bool bNewChart = false;
1519 bool bNewView = false;
1520 bool bCanvasChartAutoOpen = true; // debugging
1521
1522 bool bNewPiano = false;
1523 bool bOpenSpecified;
1524 ChartStack LastStack;
1525 ChartBase *pLast_Ch;
1526
1527 ChartStack WorkStack;
1528
1529 if (!GetVP().IsValid()) return false;
1530 if (bDBUpdateInProgress) return false;
1531 if (!ChartData) return false;
1532
1533 if (ChartData->IsBusy()) return false;
1534 // Do not disturb any existing animations
1535 if (m_chart_drag_inertia_active) return false;
1536 if (m_animationActive) return false;
1537
1538 // Startup case:
1539 // Quilting is enabled, but the last chart seen was not quiltable
1540 // In this case, drop to single chart mode, set persistence flag,
1541 // And open the specified chart
1542 // TODO implement this
1543 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1544 // if( GetQuiltMode() ) {
1545 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1546 // gFrame->ToggleQuiltMode();
1547 // m_bpersistent_quilt = true;
1548 // m_singleChart = NULL;
1549 // }
1550 // }
1551 // }
1552
1553 // If in auto-follow mode, use the current glat,glon to build chart
1554 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1555 // other means
1556
1557 if (m_bFollow) {
1558 tLat = gLat;
1559 tLon = gLon;
1560
1561 // Set the ViewPort center based on the OWNSHIP offset
1562 double dx = m_OSoffsetx;
1563 double dy = m_OSoffsety;
1564 double d_east = dx / GetVP().view_scale_ppm;
1565 double d_north = dy / GetVP().view_scale_ppm;
1566
1567 if (GetUpMode() == NORTH_UP_MODE) {
1568 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1569 } else {
1570 double offset_angle = atan2(d_north, d_east);
1571 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1572 double chart_angle = GetVPRotation();
1573 double target_angle = chart_angle + offset_angle;
1574 double d_east_mod = offset_distance * cos(target_angle);
1575 double d_north_mod = offset_distance * sin(target_angle);
1576 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1577 }
1578
1579 // on lookahead mode, adjust the vp center point
1580 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1581 double cog_to_use = gCog;
1582 if (g_btenhertz &&
1583 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1584 cog_to_use = gCog_gt;
1585 blong_jump = true;
1586 }
1587 if (!g_btenhertz) cog_to_use = g_COGAvg;
1588
1589 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1590
1591 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1592 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1593
1594 double pixel_delta_tent =
1595 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1596
1597 double pixel_delta = 0;
1598
1599 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1600 // avoid jumping of the vp center point during slow maneuvering, or at
1601 // anchor....
1602 if (!std::isnan(gSog)) {
1603 if (gSog < 2.0)
1604 pixel_delta = 0.;
1605 else
1606 pixel_delta = pixel_delta_tent;
1607 }
1608
1609 meters_to_shift = 0;
1610 dir_to_shift = 0;
1611 if (!std::isnan(gCog)) {
1612 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1613 dir_to_shift = cog_to_use;
1614 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1615 &vpLon);
1616 } else {
1617 vpLat = gLat;
1618 vpLon = gLon;
1619 }
1620 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1621 m_OSoffsetx = 0; // center ownship on loss of GPS
1622 m_OSoffsety = 0;
1623 vpLat = gLat;
1624 vpLon = gLon;
1625 }
1626
1627 } else {
1628 tLat = m_vLat;
1629 tLon = m_vLon;
1630 vpLat = m_vLat;
1631 vpLon = m_vLon;
1632 }
1633
1634 if (GetQuiltMode()) {
1635 int current_db_index = -1;
1636 if (m_pCurrentStack)
1637 current_db_index =
1638 m_pCurrentStack
1639 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1640 // chart dbIndex
1641 else
1642 m_pCurrentStack = new ChartStack;
1643
1644 // This logic added to enable opening a chart when there is no
1645 // previous chart indication, either from inital startup, or from adding
1646 // new chart directory
1647 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1648 m_pCurrentStack) {
1649 if (m_pCurrentStack->nEntry) {
1650 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1651 1); // smallest scale
1652 SelectQuiltRefdbChart(new_dbIndex, true);
1653 m_bautofind = false;
1654 }
1655 }
1656
1657 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1658 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1659
1660 if (m_bFirstAuto) {
1661 // Allow the chart database to pre-calculate the MBTile inclusion test
1662 // boolean...
1663 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1664
1665 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1666 // physical pixels. On standard DPI displays where logical = physical
1667 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1668 // logical pixels, this ratio would be 0.5.
1669 double proposed_scale_onscreen =
1670 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1671
1672 int initial_db_index = m_restore_dbindex;
1673 if (initial_db_index < 0) {
1674 if (m_pCurrentStack->nEntry) {
1675 initial_db_index =
1676 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1677 } else
1678 m_bautofind = true; // initial_db_index = 0;
1679 }
1680
1681 if (m_pCurrentStack->nEntry) {
1682 int initial_type = ChartData->GetDBChartType(initial_db_index);
1683
1684 // Check to see if the target new chart is quiltable as a reference
1685 // chart
1686
1687 if (!IsChartQuiltableRef(initial_db_index)) {
1688 // If it is not quiltable, then walk the stack up looking for a
1689 // satisfactory chart i.e. one that is quiltable and of the same type
1690 // XXX if there's none?
1691 int stack_index = 0;
1692
1693 if (stack_index >= 0) {
1694 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1695 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1696 if (IsChartQuiltableRef(test_db_index) &&
1697 (initial_type ==
1698 ChartData->GetDBChartType(initial_db_index))) {
1699 initial_db_index = test_db_index;
1700 break;
1701 }
1702 stack_index++;
1703 }
1704 }
1705 }
1706
1707 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1708 if (pc) {
1709 SetQuiltRefChart(initial_db_index);
1710 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1711 }
1712 }
1713 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1714 // just GetVPScale(), so I'm not sure why it's necessary to define the
1715 // proposed_scale_onscreen variable.
1716 bNewView |= SetViewPoint(vpLat, vpLon,
1717 GetCanvasScaleFactor() / proposed_scale_onscreen,
1718 0, GetVPRotation());
1719 }
1720 // Measure rough jump distance if in bfollow mode
1721 // No good reason to do smooth pan for
1722 // jump distance more than one screen width at scale.
1723 bool super_jump = false;
1724 if (m_bFollow) {
1725 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1726 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1727 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1728 }
1729 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1730 goto update_finish;
1731 }
1732
1733 // Single Chart Mode from here....
1734 pLast_Ch = m_singleChart;
1735 ChartTypeEnum new_open_type;
1736 ChartFamilyEnum new_open_family;
1737 if (pLast_Ch) {
1738 new_open_type = pLast_Ch->GetChartType();
1739 new_open_family = pLast_Ch->GetChartFamily();
1740 } else {
1741 new_open_type = CHART_TYPE_KAP;
1742 new_open_family = CHART_FAMILY_RASTER;
1743 }
1744
1745 bOpenSpecified = m_bFirstAuto;
1746
1747 // Make sure the target stack is valid
1748 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1749
1750 // Build a chart stack based on tLat, tLon
1751 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1752 m_groupIndex)) { // Bogus Lat, Lon?
1753 if (NULL == pDummyChart) {
1754 pDummyChart = new ChartDummy;
1755 bNewChart = true;
1756 }
1757
1758 if (m_singleChart)
1759 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1760
1761 m_singleChart = pDummyChart;
1762
1763 // If the current viewpoint is invalid, set the default scale to
1764 // something reasonable.
1765 double set_scale = GetVPScale();
1766 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1767
1768 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1769
1770 // If the chart stack has just changed, there is new status
1771 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1772 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1773 bNewPiano = true;
1774 bNewChart = true;
1775 }
1776 }
1777
1778 // Copy the new (by definition empty) stack into the target stack
1779 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1780
1781 goto update_finish;
1782 }
1783
1784 // Check to see if Chart Stack has changed
1785 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1786 // New chart stack, so...
1787 bNewPiano = true;
1788
1789 // Save a copy of the current stack
1790 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1791
1792 // Copy the new stack into the target stack
1793 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1794
1795 // Is Current Chart in new stack?
1796
1797 int tEntry = -1;
1798 if (NULL != m_singleChart) // this handles startup case
1799 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1800 m_singleChart->GetFullPath());
1801
1802 if (tEntry != -1) { // m_singleChart is in the new stack
1803 m_pCurrentStack->CurrentStackEntry = tEntry;
1804 bNewChart = false;
1805 }
1806
1807 else // m_singleChart is NOT in new stack, or m_singlechart not yet set
1808 { // So, need to open a new chart
1809 // Find the largest scale raster chart that opens OK
1810
1811 ChartBase *pProposed = NULL;
1812
1813 if (bCanvasChartAutoOpen) {
1814 bool search_direction =
1815 false; // default is to search from lowest to highest
1816 int start_index = 0;
1817
1818 // A special case: If panning at high scale, open largest scale
1819 // chart first
1820 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1821 (LastStack.nEntry == 0)) {
1822 search_direction = true;
1823 start_index = m_pCurrentStack->nEntry - 1;
1824 }
1825
1826 // Another special case, open specified db index on program start
1827 if (bOpenSpecified) {
1828 if (m_restore_dbindex >= 0) {
1829 pProposed =
1830 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
1831 std::vector<int> one_array;
1832 one_array.push_back(m_restore_dbindex);
1833 m_Piano->SetActiveKeyArray(one_array);
1834 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
1835 m_restore_dbindex = -1; // Mark as used...
1836 }
1837
1838 if (!pProposed) {
1839 search_direction = false;
1840 start_index = m_restore_dbindex;
1841 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1842 start_index = 0;
1843
1844 new_open_type = CHART_TYPE_DONTCARE;
1845 }
1846 }
1847
1848 if (!pProposed) {
1849 pProposed = ChartData->OpenStackChartConditional(
1850 m_pCurrentStack, start_index, search_direction, new_open_type,
1851 new_open_family);
1852
1853 // Try to open other types/families of chart in some priority
1854 if (NULL == pProposed)
1855 pProposed = ChartData->OpenStackChartConditional(
1856 m_pCurrentStack, start_index, search_direction,
1857 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1858
1859 if (NULL == pProposed)
1860 pProposed = ChartData->OpenStackChartConditional(
1861 m_pCurrentStack, start_index, search_direction,
1862 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1863
1864 bNewChart = true;
1865 }
1866 } // bCanvasChartAutoOpen
1867
1868 else
1869 pProposed = NULL;
1870
1871 // If no go, then
1872 // Open a Dummy Chart
1873 if (NULL == pProposed) {
1874 if (NULL == pDummyChart) {
1875 pDummyChart = new ChartDummy;
1876 bNewChart = true;
1877 }
1878
1879 if (pLast_Ch)
1880 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1881
1882 pProposed = pDummyChart;
1883 }
1884
1885 // Arriving here, pProposed points to an opened chart, or NULL.
1886 if (m_singleChart) m_singleChart->Deactivate();
1887 m_singleChart = pProposed;
1888
1889 if (m_singleChart) {
1890 m_singleChart->Activate();
1891 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1892 m_pCurrentStack, m_singleChart->GetFullPath());
1893 }
1894 } // need new chart
1895
1896 // Arriving here, m_singleChart is opened and OK, or NULL
1897 if (NULL != m_singleChart) {
1898 // Setup the view using the current scale
1899 double set_scale = GetVPScale();
1900
1901 double proposed_scale_onscreen;
1902
1903 if (m_bFollow) { // autoset the scale only if in autofollow
1904 double new_scale_ppm =
1905 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1906 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1907 } else
1908 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1909
1910 // This logic will bring a new chart onscreen at roughly twice the true
1911 // paper scale equivalent. Note that first chart opened on application
1912 // startup (bOpenSpecified = true) will open at the config saved scale
1913 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1914 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1915 double equivalent_vp_scale =
1916 GetCanvasScaleFactor() / proposed_scale_onscreen;
1917 double new_scale_ppm =
1918 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1919 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1920 }
1921
1922 if (m_bFollow) { // bounds-check the scale only if in autofollow
1923 proposed_scale_onscreen =
1924 wxMin(proposed_scale_onscreen,
1925 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1926 GetCanvasWidth()));
1927 proposed_scale_onscreen =
1928 wxMax(proposed_scale_onscreen,
1929 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1931 }
1932
1933 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1934
1935 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1936 m_singleChart->GetChartSkew() * PI / 180.,
1937 GetVPRotation());
1938 }
1939 } // new stack
1940
1941 else // No change in Chart Stack
1942 {
1943 double s = GetVPScale();
1944 if ((m_bFollow) && m_singleChart)
1945 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1946 m_singleChart->GetChartSkew() * PI / 180.,
1947 GetVPRotation());
1948 }
1949
1950update_finish:
1951
1952 // TODO
1953 // if( bNewPiano ) UpdateControlBar();
1954
1955 m_bFirstAuto = false; // Auto open on program start
1956
1957 // If we need a Refresh(), do it here...
1958 // But don't duplicate a Refresh() done by SetViewPoint()
1959 if (bNewChart && !bNewView) Refresh(false);
1960
1961#ifdef ocpnUSE_GL
1962 // If a new chart, need to invalidate gl viewport for refresh
1963 // so the fbo gets flushed
1964 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1965#endif
1966
1967 return bNewChart | bNewView;
1968}
1969
1970void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1971 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1972
1973 SetQuiltRefChart(db_index);
1974 if (ChartData) {
1975 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1976 if (pc) {
1977 if (b_autoscale) {
1978 double best_scale_ppm = GetBestVPScale(pc);
1979 SetVPScale(best_scale_ppm);
1980 }
1981 } else
1982 SetQuiltRefChart(-1);
1983 } else
1984 SetQuiltRefChart(-1);
1985}
1986
1987void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1988 std::vector<int> piano_chart_index_array =
1989 GetQuiltExtendedStackdbIndexArray();
1990 int current_db_index = piano_chart_index_array[selected_index];
1991
1992 SelectQuiltRefdbChart(current_db_index);
1993}
1994
1995double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1996 if (pchart) {
1997 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1998
1999 if ((g_bPreserveScaleOnX) ||
2000 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2001 double new_scale_ppm = GetVPScale();
2002 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2003 } else {
2004 // This logic will bring the new chart onscreen at roughly twice the true
2005 // paper scale equivalent.
2006 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2007 double equivalent_vp_scale =
2008 GetCanvasScaleFactor() / proposed_scale_onscreen;
2009 double new_scale_ppm =
2010 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2011 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2012 }
2013
2014 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2015 // set. Otherwise, we get severe performance problems on all platforms
2016
2017 double max_underzoom_multiplier = 2.0;
2018 if (GetVP().b_quilt) {
2019 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2020 pchart->GetChartType(),
2021 pchart->GetChartFamily());
2022 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2023 }
2024
2025 proposed_scale_onscreen = wxMin(
2026 proposed_scale_onscreen,
2027 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2028 max_underzoom_multiplier);
2029
2030 // And, do not allow excessive overzoom either
2031 proposed_scale_onscreen =
2032 wxMax(proposed_scale_onscreen,
2033 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2034
2035 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2036 } else
2037 return 1.0;
2038}
2039
2040void ChartCanvas::SetupCanvasQuiltMode() {
2041 if (GetQuiltMode()) // going to quilt mode
2042 {
2043 ChartData->LockCache();
2044
2045 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2046
2047 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2048
2049 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2050 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2051 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2052 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2053
2054 m_Piano->SetRoundedRectangles(true);
2055
2056 // Select the proper Ref chart
2057 int target_new_dbindex = -1;
2058 if (m_pCurrentStack) {
2059 target_new_dbindex =
2060 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2061
2062 if (-1 != target_new_dbindex) {
2063 if (!IsChartQuiltableRef(target_new_dbindex)) {
2064 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2065 int type = ChartData->GetDBChartType(target_new_dbindex);
2066
2067 // walk the stack up looking for a satisfactory chart
2068 int stack_index = m_pCurrentStack->CurrentStackEntry;
2069
2070 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2071 (stack_index >= 0)) {
2072 int proj_tent = ChartData->GetDBChartProj(
2073 m_pCurrentStack->GetDBIndex(stack_index));
2074 int type_tent = ChartData->GetDBChartType(
2075 m_pCurrentStack->GetDBIndex(stack_index));
2076
2077 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2078 if ((proj == proj_tent) && (type_tent == type)) {
2079 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2080 break;
2081 }
2082 }
2083 stack_index++;
2084 }
2085 }
2086 }
2087 }
2088
2089 if (IsChartQuiltableRef(target_new_dbindex))
2090 SelectQuiltRefdbChart(target_new_dbindex,
2091 false); // Try not to allow a scale change
2092 else { // fall back to last selected no-quilt chart as new reference
2093 int stack_index = m_pCurrentStack->CurrentStackEntry;
2094 SelectQuiltRefdbChart(m_pCurrentStack->GetDBIndex(stack_index), false);
2095 }
2096
2097 m_singleChart = NULL; // Bye....
2098
2099 // Re-qualify the quilt reference chart selection
2100 AdjustQuiltRefChart();
2101
2102 // Restore projection type saved on last quilt mode toggle
2103 // TODO
2104 // if(g_sticky_projection != -1)
2105 // GetVP().SetProjectionType(g_sticky_projection);
2106 // else
2107 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2108 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2109
2110 } else // going to SC Mode
2111 {
2112 std::vector<int> empty_array;
2113 m_Piano->SetActiveKeyArray(empty_array);
2114 m_Piano->SetNoshowIndexArray(empty_array);
2115 m_Piano->SetEclipsedIndexArray(empty_array);
2116
2117 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2118 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2119 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2120 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2121 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2122
2123 m_Piano->SetRoundedRectangles(false);
2124 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2125 }
2126
2127 // When shifting from quilt to single chart mode, select the "best" single
2128 // chart to show
2129 if (!GetQuiltMode()) {
2130 if (ChartData && ChartData->IsValid()) {
2131 UnlockQuilt();
2132
2133 double tLat, tLon;
2134 if (m_bFollow == true) {
2135 tLat = gLat;
2136 tLon = gLon;
2137 } else {
2138 tLat = m_vLat;
2139 tLon = m_vLon;
2140 }
2141
2142 if (!m_singleChart) {
2143 // First choice is to adopt the outgoing quilt reference chart
2144 if (GetQuiltReferenceChartIndex() >= 0) {
2145 m_singleChart = ChartData->OpenChartFromDB(
2146 GetQuiltReferenceChartIndex(), FULL_INIT);
2147 }
2148 // Second choice is to use any "no-quilt restore index", if available
2149 else if (m_restore_dbindex >= 0) {
2150 m_singleChart =
2151 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
2152 }
2153 // Final choice it to pick a suitable chart based on current stack.
2154 else {
2155 // Build a temporary chart stack based on tLat, tLon
2156 ChartStack TempStack;
2157 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2158 m_groupIndex);
2159
2160 // Iterate over the quilt charts actually shown, looking for the
2161 // largest scale chart that will be in the new chartstack.... This
2162 // will (almost?) always be the reference chart....
2163
2164 ChartBase *Candidate_Chart = NULL;
2165 int cur_max_scale = (int)1e8;
2166
2167 ChartBase *pChart = GetFirstQuiltChart();
2168 while (pChart) {
2169 // Is this pChart in new stack?
2170 int tEntry =
2171 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2172 if (tEntry != -1) {
2173 if (pChart->GetNativeScale() < cur_max_scale) {
2174 Candidate_Chart = pChart;
2175 cur_max_scale = pChart->GetNativeScale();
2176 }
2177 }
2178 pChart = GetNextQuiltChart();
2179 }
2180
2181 m_singleChart = Candidate_Chart;
2182
2183 // If the quilt is empty, there is no "best" chart.
2184 // So, open the smallest scale chart in the current stack
2185 if (NULL == m_singleChart) {
2186 m_singleChart = ChartData->OpenStackChartConditional(
2187 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2188 CHART_FAMILY_DONTCARE);
2189 }
2190 }
2191 }
2192 // Invalidate all the charts in the quilt,
2193 // as any cached data may be region based and not have fullscreen coverage
2194 InvalidateAllQuiltPatchs();
2195
2196 if (m_singleChart) {
2197 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2198 std::vector<int> one_array;
2199 one_array.push_back(dbi);
2200 m_Piano->SetActiveKeyArray(one_array);
2201 }
2202
2203 if (m_singleChart) {
2204 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2205 }
2206 }
2207 }
2208 // Invalidate the current stack so that it will be rebuilt on next tick
2209 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2210 SetVPScale(GetVPScale() * 1.0001);
2211}
2212
2213bool ChartCanvas::IsTempMenuBarEnabled() {
2214#ifdef __WXMSW__
2215 int major;
2216 wxGetOsVersion(&major);
2217 return (major >
2218 5); // For Windows, function is only available on Vista and above
2219#else
2220 return true;
2221#endif
2222}
2223
2224double ChartCanvas::GetCanvasRangeMeters() {
2225 int width, height;
2226 GetSize(&width, &height);
2227 int minDimension = wxMin(width, height);
2228
2229 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2230 range *= cos(GetVP().clat * PI / 180.);
2231 return range;
2232}
2233
2234void ChartCanvas::SetCanvasRangeMeters(double range) {
2235 int width, height;
2236 GetSize(&width, &height);
2237 int minDimension = wxMin(width, height);
2238
2239 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2240 SetVPScale(scale_ppm / 2);
2241}
2242
2243bool ChartCanvas::SetUserOwnship() {
2244 // Look for user defined ownship image
2245 // This may be found in the shared data location along with other user
2246 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2247 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2248 double factor_dusk = 0.5;
2249 double factor_night = 0.25;
2250
2251 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2252 m_pos_image_user_day = new wxImage;
2253 *m_pos_image_user_day = pbmp->ConvertToImage();
2254 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2255
2256 int gimg_width = m_pos_image_user_day->GetWidth();
2257 int gimg_height = m_pos_image_user_day->GetHeight();
2258
2259 // Make dusk and night images
2260 m_pos_image_user_dusk = new wxImage;
2261 m_pos_image_user_night = new wxImage;
2262
2263 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2264 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2265
2266 for (int iy = 0; iy < gimg_height; iy++) {
2267 for (int ix = 0; ix < gimg_width; ix++) {
2268 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2269 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2270 m_pos_image_user_day->GetGreen(ix, iy),
2271 m_pos_image_user_day->GetBlue(ix, iy));
2272 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2273 hsv.value = hsv.value * factor_dusk;
2274 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2275 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2276 nrgb.blue);
2277
2278 hsv = wxImage::RGBtoHSV(rgb);
2279 hsv.value = hsv.value * factor_night;
2280 nrgb = wxImage::HSVtoRGB(hsv);
2281 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2282 nrgb.blue);
2283 }
2284 }
2285 }
2286
2287 // Make some alternate greyed out day/dusk/night images
2288 m_pos_image_user_grey_day = new wxImage;
2289 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2290
2291 m_pos_image_user_grey_dusk = new wxImage;
2292 m_pos_image_user_grey_night = new wxImage;
2293
2294 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2295 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2296
2297 for (int iy = 0; iy < gimg_height; iy++) {
2298 for (int ix = 0; ix < gimg_width; ix++) {
2299 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2300 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2301 m_pos_image_user_grey_day->GetGreen(ix, iy),
2302 m_pos_image_user_grey_day->GetBlue(ix, iy));
2303 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2304 hsv.value = hsv.value * factor_dusk;
2305 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2306 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2307 nrgb.blue);
2308
2309 hsv = wxImage::RGBtoHSV(rgb);
2310 hsv.value = hsv.value * factor_night;
2311 nrgb = wxImage::HSVtoRGB(hsv);
2312 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2313 nrgb.blue);
2314 }
2315 }
2316 }
2317
2318 // Make a yellow image for rendering under low accuracy chart conditions
2319 m_pos_image_user_yellow_day = new wxImage;
2320 m_pos_image_user_yellow_dusk = new wxImage;
2321 m_pos_image_user_yellow_night = new wxImage;
2322
2323 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2324 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2325 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2326
2327 for (int iy = 0; iy < gimg_height; iy++) {
2328 for (int ix = 0; ix < gimg_width; ix++) {
2329 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2330 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2331 m_pos_image_user_grey_day->GetGreen(ix, iy),
2332 m_pos_image_user_grey_day->GetBlue(ix, iy));
2333
2334 // Simply remove all "blue" from the greyscaled image...
2335 // so, what is not black becomes yellow.
2336 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2337 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2338 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2339
2340 hsv = wxImage::RGBtoHSV(rgb);
2341 hsv.value = hsv.value * factor_dusk;
2342 nrgb = wxImage::HSVtoRGB(hsv);
2343 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2344
2345 hsv = wxImage::RGBtoHSV(rgb);
2346 hsv.value = hsv.value * factor_night;
2347 nrgb = wxImage::HSVtoRGB(hsv);
2348 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2349 0);
2350 }
2351 }
2352 }
2353
2354 return true;
2355 } else
2356 return false;
2357}
2358
2360 m_display_size_mm = size;
2361
2362 // int sx, sy;
2363 // wxDisplaySize( &sx, &sy );
2364
2365 // Calculate logical pixels per mm for later reference.
2366 wxSize sd = g_Platform->getDisplaySize();
2367 double horizontal = sd.x;
2368 // Set DPI (Win) scale factor
2369 g_scaler = g_Platform->GetDisplayDIPMult(this);
2370
2371 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2372 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2373
2374 if (ps52plib) {
2375 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2376 ps52plib->SetPPMM(m_pix_per_mm);
2377 }
2378
2379 wxString msg;
2380 msg.Printf(
2381 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2382 "%d:%d ",
2383 m_display_size_mm, sd.x, sd.y);
2384 wxLogDebug(msg);
2385
2386 int ssx, ssy;
2387 ssx = g_monitor_info[g_current_monitor].width;
2388 ssy = g_monitor_info[g_current_monitor].height;
2389 msg.Printf("monitor size: %d %d", ssx, ssy);
2390 wxLogDebug(msg);
2391
2392 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2393}
2394#if 0
2395void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2396{
2397 wxString msg(event.m_string.c_str(), wxConvUTF8);
2398 // if cpus are removed between runs
2399 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2400 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2401 }
2402
2403 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2404 {
2405 compress_msg_array.RemoveAt(event.thread);
2406 compress_msg_array.Insert( msg, event.thread);
2407 }
2408 else
2409 compress_msg_array.Add(msg);
2410
2411
2412 wxString combined_msg;
2413 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2414 combined_msg += compress_msg_array[i];
2415 combined_msg += "\n";
2416 }
2417
2418 bool skip = false;
2419 pprog->Update(pprog_count, combined_msg, &skip );
2420 pprog->SetSize(pprog_size);
2421 if(skip)
2422 b_skipout = skip;
2423}
2424#endif
2425void ChartCanvas::InvalidateGL() {
2426 if (!m_glcc) return;
2427#ifdef ocpnUSE_GL
2428 if (g_bopengl) m_glcc->Invalidate();
2429#endif
2430 if (m_Compass) m_Compass->UpdateStatus(true);
2431}
2432
2433int ChartCanvas::GetCanvasChartNativeScale() {
2434 int ret = 1;
2435 if (!VPoint.b_quilt) {
2436 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2437 } else
2438 ret = (int)m_pQuilt->GetRefNativeScale();
2439
2440 return ret;
2441}
2442
2443ChartBase *ChartCanvas::GetChartAtCursor() {
2444 ChartBase *target_chart;
2445 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2446 target_chart = m_singleChart;
2447 else if (VPoint.b_quilt)
2448 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2449 else
2450 target_chart = NULL;
2451 return target_chart;
2452}
2453
2454ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2455 ChartBase *target_chart;
2456 if (VPoint.b_quilt)
2457 target_chart =
2458 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2459 else
2460 target_chart = NULL;
2461 return target_chart;
2462}
2463
2464int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2465 int new_dbIndex = -1;
2466 if (!VPoint.b_quilt) {
2467 if (m_pCurrentStack) {
2468 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2469 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2470 if (sc >= scale) {
2471 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2472 break;
2473 }
2474 }
2475 }
2476 } else {
2477 // Using the current quilt, select a useable reference chart
2478 // Said chart will be in the extended (possibly full-screen) stack,
2479 // And will have a scale equal to or just greater than the stipulated
2480 // value, and will belong to the same chart family (RNC/ENC)
2481 // If no family match is found, then family requirement is ignored.
2482 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2483 if (im > 0) {
2484 // Find closest using scale and family
2485 for (unsigned int is = 0; is < im; is++) {
2486 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2487 m_pQuilt->GetExtendedStackIndexArray()[is]);
2488 if ((m.Scale_ge(scale)) &&
2489 (m_pQuilt->GetRefFamily() == m.GetChartFamily())) {
2490 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2491 break;
2492 }
2493 }
2494 // if not found, likely due to Family requirement
2495 // That is, there is no matching family chart in the StackIndexArray.
2496 // Try again, without consideration of family.
2497 if (new_dbIndex < 0) {
2498 for (unsigned int is = 0; is < im; is++) {
2499 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2500 m_pQuilt->GetExtendedStackIndexArray()[is]);
2501 if (m.Scale_ge(scale)) {
2502 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2503 break;
2504 }
2505 }
2506 }
2507 }
2508 }
2509
2510 return new_dbIndex;
2511}
2512
2513void ChartCanvas::EnablePaint(bool b_enable) {
2514 m_b_paint_enable = b_enable;
2515#ifdef ocpnUSE_GL
2516 if (m_glcc) m_glcc->EnablePaint(b_enable);
2517#endif
2518}
2519
2520bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2521
2522void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2523
2524std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2525 return m_pQuilt->GetQuiltIndexArray();
2526 ;
2527}
2528
2529void ChartCanvas::SetQuiltMode(bool b_quilt) {
2530 VPoint.b_quilt = b_quilt;
2531 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2532}
2533
2534bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2535
2536int ChartCanvas::GetQuiltReferenceChartIndex() {
2537 return m_pQuilt->GetRefChartdbIndex();
2538}
2539
2540void ChartCanvas::InvalidateAllQuiltPatchs() {
2541 m_pQuilt->InvalidateAllQuiltPatchs();
2542}
2543
2544ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2545 return m_pQuilt->GetLargestScaleChart();
2546}
2547
2548ChartBase *ChartCanvas::GetFirstQuiltChart() {
2549 return m_pQuilt->GetFirstChart();
2550}
2551
2552ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2553
2554int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2555
2556void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2557 m_pQuilt->SetHiliteIndex(dbIndex);
2558}
2559
2560void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2561 m_pQuilt->SetHiliteIndexArray(hilite_array);
2562}
2563
2564void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2565 m_pQuilt->ClearHiliteIndexArray();
2566}
2567
2568std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2569 bool flag2) {
2570 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2571}
2572
2573int ChartCanvas::GetQuiltRefChartdbIndex() {
2574 return m_pQuilt->GetRefChartdbIndex();
2575}
2576
2577std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2578 return m_pQuilt->GetExtendedStackIndexArray();
2579}
2580
2581std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2582 return m_pQuilt->GetFullscreenIndexArray();
2583}
2584
2585std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2586 return m_pQuilt->GetEclipsedStackIndexArray();
2587}
2588
2589void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2590
2591double ChartCanvas::GetQuiltMaxErrorFactor() {
2592 return m_pQuilt->GetMaxErrorFactor();
2593}
2594
2595bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2596 return m_pQuilt->IsChartQuiltableRef(db_index);
2597}
2598
2599bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2600 double chartMaxScale =
2601 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2602 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2603}
2604
2605void ChartCanvas::StartMeasureRoute() {
2606 if (!m_routeState) { // no measure tool if currently creating route
2607 if (m_bMeasure_Active) {
2608 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2609 m_pMeasureRoute = NULL;
2610 }
2611
2612 m_bMeasure_Active = true;
2613 m_nMeasureState = 1;
2614 m_bDrawingRoute = false;
2615
2616 SetCursor(*pCursorPencil);
2617 Refresh();
2618 }
2619}
2620
2621void ChartCanvas::CancelMeasureRoute() {
2622 m_bMeasure_Active = false;
2623 m_nMeasureState = 0;
2624 m_bDrawingRoute = false;
2625
2626 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2627 m_pMeasureRoute = NULL;
2628
2629 SetCursor(*pCursorArrow);
2630}
2631
2632ViewPort &ChartCanvas::GetVP() { return VPoint; }
2633
2634void ChartCanvas::SetVP(ViewPort &vp) {
2635 VPoint = vp;
2636 VPoint.SetPixelScale(m_displayScale);
2637}
2638
2639// void ChartCanvas::SetFocus()
2640// {
2641// printf("set %d\n", m_canvasIndex);
2642// //wxWindow:SetFocus();
2643// }
2644
2645void ChartCanvas::TriggerDeferredFocus() {
2646 // #if defined(__WXGTK__) || defined(__WXOSX__)
2647
2648 m_deferredFocusTimer.Start(20, true);
2649
2650#if defined(__WXGTK__) || defined(__WXOSX__)
2651 top_frame::Get()->Raise();
2652#endif
2653
2654 // top_frame::Get()->Raise();
2655 // #else
2656 // SetFocus();
2657 // Refresh(true);
2658 // #endif
2659}
2660
2661void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2662 SetFocus();
2663 Refresh(true);
2664}
2665
2666void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2667 if (SendKeyEventToPlugins(event))
2668 return; // PlugIn did something, and does not want the canvas to do
2669 // anything else
2670
2671 int key_char = event.GetKeyCode();
2672 switch (key_char) {
2673 case '?':
2674 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2675 break;
2676 case '+':
2677 ZoomCanvas(g_plus_minus_zoom_factor, false);
2678 break;
2679 case '-':
2680 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2681 break;
2682 default:
2683 break;
2684 }
2685 if (g_benable_rotate) {
2686 switch (key_char) {
2687 case ']':
2688 RotateCanvas(1);
2689 Refresh();
2690 break;
2691
2692 case '[':
2693 RotateCanvas(-1);
2694 Refresh();
2695 break;
2696
2697 case '\\':
2698 DoRotateCanvas(0);
2699 break;
2700 }
2701 }
2702
2703 event.Skip();
2704}
2705
2706void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2707 if (SendKeyEventToPlugins(event))
2708 return; // PlugIn did something, and does not want the canvas to do
2709 // anything else
2710
2711 bool b_handled = false;
2712
2713 m_modkeys = event.GetModifiers();
2714
2715 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2716
2717#ifdef OCPN_ALT_MENUBAR
2718#ifndef __WXOSX__
2719 // If the permanent menubar is disabled, we show it temporarily when Alt is
2720 // pressed or when Alt + a letter is presssed (for the top-menu-level
2721 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2722 // some special cases.
2723 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2724 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2725 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2726 if (!g_bTempShowMenuBar) {
2727 g_bTempShowMenuBar = true;
2728 top_frame::Get()->ApplyGlobalSettings(false);
2729 }
2730 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2731 event.Skip();
2732 return;
2733 }
2734 // If another key is pressed while Alt is down, do NOT toggle the menus when
2735 // Alt is released
2736 if (event.GetKeyCode() != WXK_ALT) {
2737 m_bMayToggleMenuBar = false;
2738 }
2739 }
2740#endif
2741#endif
2742
2743 // HOTKEYS
2744 switch (event.GetKeyCode()) {
2745 case WXK_TAB:
2746 // parent_frame->SwitchKBFocus( this );
2747 break;
2748
2749 case WXK_MENU:
2750 int x, y;
2751 event.GetPosition(&x, &y);
2752 m_FinishRouteOnKillFocus = false;
2753 CallPopupMenu(x, y);
2754 m_FinishRouteOnKillFocus = true;
2755 break;
2756
2757 case WXK_ALT:
2758 m_modkeys |= wxMOD_ALT;
2759 break;
2760
2761 case WXK_CONTROL:
2762 m_modkeys |= wxMOD_CONTROL;
2763 break;
2764
2765#ifdef __WXOSX__
2766 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2767 case WXK_RAW_CONTROL:
2768 m_modkeys |= wxMOD_RAW_CONTROL;
2769 break;
2770#endif
2771
2772 case WXK_LEFT:
2773 if (m_modkeys == wxMOD_CONTROL)
2774 top_frame::Get()->DoStackDown(this);
2775 else if (g_bsmoothpanzoom) {
2776 StartTimedMovement();
2777 m_panx = -1;
2778 } else {
2779 PanCanvas(-panspeed, 0);
2780 }
2781 b_handled = true;
2782 break;
2783
2784 case WXK_UP:
2785 if (g_bsmoothpanzoom) {
2786 StartTimedMovement();
2787 m_pany = -1;
2788 } else
2789 PanCanvas(0, -panspeed);
2790 b_handled = true;
2791 break;
2792
2793 case WXK_RIGHT:
2794 if (m_modkeys == wxMOD_CONTROL)
2795 top_frame::Get()->DoStackUp(this);
2796 else if (g_bsmoothpanzoom) {
2797 StartTimedMovement();
2798 m_panx = 1;
2799 } else
2800 PanCanvas(panspeed, 0);
2801 b_handled = true;
2802
2803 break;
2804
2805 case WXK_DOWN:
2806 if (g_bsmoothpanzoom) {
2807 StartTimedMovement();
2808 m_pany = 1;
2809 } else
2810 PanCanvas(0, panspeed);
2811 b_handled = true;
2812 break;
2813
2814 case WXK_F2: {
2815 if (event.ShiftDown()) {
2816 double scale = GetVP().view_scale_ppm;
2817 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2818
2819 std::shared_ptr<HostApi> host_api;
2820 host_api = GetHostApi();
2821 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2822
2823 if (api_121)
2824 api_121->SelectChartFamily(m_canvasIndex,
2825 (ChartFamilyEnumPI)target_family);
2826 } else
2827 TogglebFollow();
2828 break;
2829 }
2830 case WXK_F3: {
2831 if (event.ShiftDown()) {
2832 double scale = GetVP().view_scale_ppm;
2833 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2834
2835 std::shared_ptr<HostApi> host_api;
2836 host_api = GetHostApi();
2837 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2838
2839 if (api_121)
2840 api_121->SelectChartFamily(m_canvasIndex,
2841 (ChartFamilyEnumPI)target_family);
2842 } else {
2843 SetShowENCText(!GetShowENCText());
2844 Refresh(true);
2845 InvalidateGL();
2846 }
2847 break;
2848 }
2849 case WXK_F4:
2850 if (!m_bMeasure_Active) {
2851 if (event.ShiftDown())
2852 m_bMeasure_DistCircle = true;
2853 else
2854 m_bMeasure_DistCircle = false;
2855
2856 StartMeasureRoute();
2857 } else {
2858 CancelMeasureRoute();
2859
2860 SetCursor(*pCursorArrow);
2861
2862 // SurfaceToolbar();
2863 InvalidateGL();
2864 Refresh(false);
2865 }
2866
2867 break;
2868
2869 case WXK_F5:
2870 top_frame::Get()->ToggleColorScheme();
2871 top_frame::Get()->Raise();
2872 TriggerDeferredFocus();
2873 break;
2874
2875 case WXK_F6: {
2876 int mod = m_modkeys & wxMOD_SHIFT;
2877 if (mod != m_brightmod) {
2878 m_brightmod = mod;
2879 m_bbrightdir = !m_bbrightdir;
2880 }
2881
2882 if (!m_bbrightdir) {
2883 g_nbrightness -= 10;
2884 if (g_nbrightness <= MIN_BRIGHT) {
2885 g_nbrightness = MIN_BRIGHT;
2886 m_bbrightdir = true;
2887 }
2888 } else {
2889 g_nbrightness += 10;
2890 if (g_nbrightness >= MAX_BRIGHT) {
2891 g_nbrightness = MAX_BRIGHT;
2892 m_bbrightdir = false;
2893 }
2894 }
2895
2896 SetScreenBrightness(g_nbrightness);
2897 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2898
2899 SetFocus(); // just in case the external program steals it....
2900 top_frame::Get()->Raise(); // And reactivate the application main
2901
2902 break;
2903 }
2904
2905 case WXK_F7:
2906 top_frame::Get()->DoStackDown(this);
2907 break;
2908
2909 case WXK_F8:
2910 top_frame::Get()->DoStackUp(this);
2911 break;
2912
2913#ifndef __WXOSX__
2914 case WXK_F9: {
2915 ToggleCanvasQuiltMode();
2916 break;
2917 }
2918#endif
2919
2920 case WXK_F11:
2921 top_frame::Get()->ToggleFullScreen();
2922 b_handled = true;
2923 break;
2924
2925 case WXK_F12: {
2926 if (m_modkeys == wxMOD_ALT) {
2927 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2928 } else {
2929 ToggleChartOutlines();
2930 }
2931 break;
2932 }
2933
2934 case WXK_PAUSE: // Drop MOB
2935 top_frame::Get()->ActivateMOB();
2936 break;
2937
2938 // NUMERIC PAD
2939 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2940 case WXK_PAGEUP: {
2941 ZoomCanvas(g_plus_minus_zoom_factor, false);
2942 break;
2943 }
2944 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2945 case WXK_PAGEDOWN: {
2946 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2947 break;
2948 }
2949 case WXK_DELETE:
2950 case WXK_BACK:
2951 if (m_bMeasure_Active) {
2952 if (m_nMeasureState > 2) {
2953 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2954 m_pMeasureRoute->m_lastMousePointIndex =
2955 m_pMeasureRoute->GetnPoints();
2956 m_nMeasureState--;
2957 top_frame::Get()->RefreshAllCanvas();
2958 } else {
2959 CancelMeasureRoute();
2960 StartMeasureRoute();
2961 }
2962 }
2963 break;
2964 default:
2965 break;
2966 }
2967
2968 if (event.GetKeyCode() < 128) // ascii
2969 {
2970 int key_char = event.GetKeyCode();
2971
2972 // Handle both QWERTY and AZERTY keyboard separately for a few control
2973 // codes
2974 if (!g_b_assume_azerty) {
2975#ifdef __WXMAC__
2976 if (g_benable_rotate) {
2977 switch (key_char) {
2978 // On other platforms these are handled in OnKeyChar, which
2979 // (apparently) works better in some locales. On OS X it is better
2980 // to handle them here, since pressing Alt (which should change the
2981 // rotation speed) changes the key char and so prevents the keys
2982 // from working.
2983 case ']':
2984 RotateCanvas(1);
2985 b_handled = true;
2986 break;
2987
2988 case '[':
2989 RotateCanvas(-1);
2990 b_handled = true;
2991 break;
2992
2993 case '\\':
2994 DoRotateCanvas(0);
2995 b_handled = true;
2996 break;
2997 }
2998 }
2999#endif
3000 } else { // AZERTY
3001 switch (key_char) {
3002 case 43:
3003 ZoomCanvas(g_plus_minus_zoom_factor, false);
3004 break;
3005
3006 case 54: // '-' alpha/num pad
3007 // case 56: // '_' alpha/num pad
3008 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3009 break;
3010 }
3011 }
3012
3013#ifdef __WXOSX__
3014 // Ctrl+Cmd+F toggles fullscreen on macOS
3015 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3016 m_modkeys & wxMOD_RAW_CONTROL) {
3017 top_frame::Get()->ToggleFullScreen();
3018 return;
3019 }
3020#endif
3021
3022 if (event.ControlDown()) key_char -= 64;
3023
3024 if (key_char >= '0' && key_char <= '9')
3025 SetGroupIndex(key_char - '0');
3026 else
3027
3028 switch (key_char) {
3029 case 'A':
3030 SetShowENCAnchor(!GetShowENCAnchor());
3031 ReloadVP();
3032
3033 break;
3034
3035 case 'C':
3036 top_frame::Get()->ToggleColorScheme();
3037 break;
3038
3039 case 'D': {
3040 int x, y;
3041 event.GetPosition(&x, &y);
3042 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3043 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3044 // First find out what kind of chart is being used
3045 if (!pPopupDetailSlider) {
3046 if (VPoint.b_quilt) {
3047 if (m_pQuilt) {
3048 if (m_pQuilt->GetChartAtPix(
3049 VPoint,
3050 wxPoint(
3051 x, y))) // = null if no chart loaded for this point
3052 {
3053 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3054 ->GetChartType();
3055 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3056 ->GetChartFamily();
3057 }
3058 }
3059 } else {
3060 if (m_singleChart) {
3061 ChartType = m_singleChart->GetChartType();
3062 ChartFam = m_singleChart->GetChartFamily();
3063 }
3064 }
3065 // If a charttype is found show the popupslider
3066 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3067 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3069 this, -1, ChartType, ChartFam,
3070 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3071 wxDefaultSize, wxSIMPLE_BORDER, "");
3073 }
3074 } else //( !pPopupDetailSlider ) close popupslider
3075 {
3077 pPopupDetailSlider = NULL;
3078 }
3079 break;
3080 }
3081
3082 case 'E':
3083 m_nmea_log->Show();
3084 m_nmea_log->Raise();
3085 break;
3086
3087 case 'L':
3088 SetShowENCLights(!GetShowENCLights());
3089 ReloadVP();
3090
3091 break;
3092
3093 case 'M':
3094 if (event.ShiftDown())
3095 m_bMeasure_DistCircle = true;
3096 else
3097 m_bMeasure_DistCircle = false;
3098
3099 StartMeasureRoute();
3100 break;
3101
3102 case 'N':
3103 if (g_bInlandEcdis && ps52plib) {
3104 SetENCDisplayCategory((_DisCat)STANDARD);
3105 }
3106 break;
3107
3108 case 'O':
3109 ToggleChartOutlines();
3110 break;
3111
3112 case 'Q':
3113 ToggleCanvasQuiltMode();
3114 break;
3115
3116 case 'P':
3117 top_frame::Get()->ToggleTestPause();
3118 break;
3119 case 'R':
3120 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3121 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3122 g_iNavAidRadarRingsNumberVisible = 1;
3123 else if (!g_bNavAidRadarRingsShown &&
3124 g_iNavAidRadarRingsNumberVisible == 1)
3125 g_iNavAidRadarRingsNumberVisible = 0;
3126 break;
3127 case 'S':
3128 SetShowENCDepth(!m_encShowDepth);
3129 ReloadVP();
3130 break;
3131
3132 case 'T':
3133 SetShowENCText(!GetShowENCText());
3134 ReloadVP();
3135 break;
3136
3137 case 'U':
3138 SetShowENCDataQual(!GetShowENCDataQual());
3139 ReloadVP();
3140 break;
3141
3142 case 'V':
3143 m_bShowNavobjects = !m_bShowNavobjects;
3144 Refresh(true);
3145 break;
3146
3147 case 'W': // W Toggle CPA alarm
3148 ToggleCPAWarn();
3149
3150 break;
3151
3152 case 1: // Ctrl A
3153 TogglebFollow();
3154
3155 break;
3156
3157 case 2: // Ctrl B
3158 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3159 break;
3160
3161 case 13: // Ctrl M // Drop Marker at cursor
3162 {
3163 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3164 break;
3165 }
3166
3167 case 14: // Ctrl N - Activate next waypoint in a route
3168 {
3169 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3170 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3171 if ((indexActive + 1) <= r->GetnPoints()) {
3173 InvalidateGL();
3174 Refresh(false);
3175 }
3176 }
3177 break;
3178 }
3179
3180 case 15: // Ctrl O - Drop Marker at boat's position
3181 {
3182 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3183 break;
3184 }
3185
3186 case 32: // Special needs use space bar
3187 {
3188 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3189 break;
3190 }
3191
3192 case -32: // Ctrl Space // Drop MOB
3193 {
3194 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3195
3196 break;
3197 }
3198
3199 case -20: // Ctrl ,
3200 {
3201 top_frame::Get()->DoSettings();
3202 break;
3203 }
3204 case 17: // Ctrl Q
3205 parent_frame->Close();
3206 return;
3207
3208 case 18: // Ctrl R
3209 StartRoute();
3210 return;
3211
3212 case 20: // Ctrl T
3213 if (NULL == pGoToPositionDialog) // There is one global instance of
3214 // the Go To Position Dialog
3216 pGoToPositionDialog->SetCanvas(this);
3217 pGoToPositionDialog->Show();
3218 break;
3219
3220 case 25: // Ctrl Y
3221 if (undo->AnythingToRedo()) {
3222 undo->RedoNextAction();
3223 InvalidateGL();
3224 Refresh(false);
3225 }
3226 break;
3227
3228 case 26:
3229 if (event.ShiftDown()) { // Shift-Ctrl-Z
3230 if (undo->AnythingToRedo()) {
3231 undo->RedoNextAction();
3232 InvalidateGL();
3233 Refresh(false);
3234 }
3235 } else { // Ctrl Z
3236 if (undo->AnythingToUndo()) {
3237 undo->UndoLastAction();
3238 InvalidateGL();
3239 Refresh(false);
3240 }
3241 }
3242 break;
3243
3244 case 27:
3245 // Generic break
3246 if (m_bMeasure_Active) {
3247 CancelMeasureRoute();
3248
3249 SetCursor(*pCursorArrow);
3250
3251 // SurfaceToolbar();
3252 top_frame::Get()->RefreshAllCanvas();
3253 }
3254
3255 if (m_routeState) // creating route?
3256 {
3257 FinishRoute();
3258 // SurfaceToolbar();
3259 InvalidateGL();
3260 Refresh(false);
3261 }
3262
3263 break;
3264
3265 case 7: // Ctrl G
3266 switch (gamma_state) {
3267 case (0):
3268 r_gamma_mult = 0;
3269 g_gamma_mult = 1;
3270 b_gamma_mult = 0;
3271 gamma_state = 1;
3272 break;
3273 case (1):
3274 r_gamma_mult = 1;
3275 g_gamma_mult = 0;
3276 b_gamma_mult = 0;
3277 gamma_state = 2;
3278 break;
3279 case (2):
3280 r_gamma_mult = 1;
3281 g_gamma_mult = 1;
3282 b_gamma_mult = 1;
3283 gamma_state = 0;
3284 break;
3285 }
3286 SetScreenBrightness(g_nbrightness);
3287
3288 break;
3289
3290 case 9: // Ctrl I
3291 if (event.ControlDown()) {
3292 m_bShowCompassWin = !m_bShowCompassWin;
3293 SetShowGPSCompassWindow(m_bShowCompassWin);
3294 Refresh(false);
3295 }
3296 break;
3297
3298 default:
3299 break;
3300
3301 } // switch
3302 }
3303
3304 // Allow OnKeyChar to catch the key events too.
3305 if (!b_handled) {
3306 event.Skip();
3307 }
3308}
3309
3310void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3311 if (SendKeyEventToPlugins(event))
3312 return; // PlugIn did something, and does not want the canvas to do
3313 // anything else
3314
3315 switch (event.GetKeyCode()) {
3316 case WXK_TAB:
3317 top_frame::Get()->SwitchKBFocus(this);
3318 break;
3319
3320 case WXK_LEFT:
3321 case WXK_RIGHT:
3322 m_panx = 0;
3323 if (!m_pany) m_panspeed = 0;
3324 break;
3325
3326 case WXK_UP:
3327 case WXK_DOWN:
3328 m_pany = 0;
3329 if (!m_panx) m_panspeed = 0;
3330 break;
3331
3332 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3333 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3334 case WXK_PAGEUP:
3335 case WXK_PAGEDOWN:
3336 if (m_mustmove) DoMovement(m_mustmove);
3337
3338 m_zoom_factor = 1;
3339 break;
3340
3341 case WXK_ALT:
3342 m_modkeys &= ~wxMOD_ALT;
3343#ifdef OCPN_ALT_MENUBAR
3344#ifndef __WXOSX__
3345 // If the permanent menu bar is disabled, and we are not in the middle of
3346 // another key combo, then show the menu bar temporarily when Alt is
3347 // released (or hide it if already visible).
3348 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3349 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3350 top_frame::Get()->ApplyGlobalSettings(false);
3351 }
3352 m_bMayToggleMenuBar = true;
3353#endif
3354#endif
3355 break;
3356
3357 case WXK_CONTROL:
3358 m_modkeys &= ~wxMOD_CONTROL;
3359 break;
3360 }
3361
3362 if (event.GetKeyCode() < 128) // ascii
3363 {
3364 int key_char = event.GetKeyCode();
3365
3366 // Handle both QWERTY and AZERTY keyboard separately for a few control
3367 // codes
3368 if (!g_b_assume_azerty) {
3369 switch (key_char) {
3370 case '+':
3371 case '=':
3372 case '-':
3373 case '_':
3374 case 54:
3375 case 56: // '_' alpha/num pad
3376 DoMovement(m_mustmove);
3377
3378 // m_zoom_factor = 1;
3379 break;
3380 case '[':
3381 case ']':
3382 DoMovement(m_mustmove);
3383 m_rotation_speed = 0;
3384 break;
3385 }
3386 } else {
3387 switch (key_char) {
3388 case 43:
3389 case 54: // '-' alpha/num pad
3390 case 56: // '_' alpha/num pad
3391 DoMovement(m_mustmove);
3392
3393 m_zoom_factor = 1;
3394 break;
3395 }
3396 }
3397 }
3398 event.Skip();
3399}
3400
3401void ChartCanvas::ToggleChartOutlines() {
3402 m_bShowOutlines = !m_bShowOutlines;
3403
3404 Refresh(false);
3405
3406#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3407 // needs a full refresh
3408 if (g_bopengl) InvalidateGL();
3409#endif
3410}
3411
3412void ChartCanvas::ToggleLookahead() {
3413 m_bLookAhead = !m_bLookAhead;
3414 m_OSoffsetx = 0; // center ownship
3415 m_OSoffsety = 0;
3416}
3417
3418void ChartCanvas::SetUpMode(int mode) {
3419 m_upMode = mode;
3420
3421 if (mode != NORTH_UP_MODE) {
3422 // Stuff the COGAvg table in case COGUp is selected
3423 double stuff = 0;
3424 if (!std::isnan(gCog)) stuff = gCog;
3425
3426 if (g_COGAvgSec > 0) {
3427 auto cog_table = top_frame::Get()->GetCOGTable();
3428 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3429 }
3430 g_COGAvg = stuff;
3431 top_frame::Get()->StartCogTimer();
3432 } else {
3433 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3434 SetVPRotation(GetVPSkew());
3435 else
3436 SetVPRotation(0); /* reset to north up */
3437 }
3438
3439 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3440 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3441
3442 UpdateGPSCompassStatusBox(true);
3443 top_frame::Get()->DoChartUpdate();
3444}
3445
3446bool ChartCanvas::DoCanvasCOGSet() {
3447 if (GetUpMode() == NORTH_UP_MODE) return false;
3448 double cog_use = g_COGAvg;
3449 if (g_btenhertz) cog_use = gCog;
3450
3451 double rotation = 0;
3452 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3453 rotation = -gHdt * PI / 180.;
3454 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3455 rotation = -cog_use * PI / 180.;
3456
3457 SetVPRotation(rotation);
3458 return true;
3459}
3460
3461double easeOutCubic(double t) {
3462 // Starts quickly and slows down toward the end
3463 return 1.0 - pow(1.0 - t, 3.0);
3464}
3465
3466void ChartCanvas::StartChartDragInertia() {
3467 m_bChartDragging = false;
3468
3469 // Set some parameters
3470 m_chart_drag_inertia_time = 750; // msec
3471 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3472 m_last_elapsed = 0;
3473
3474 // Calculate ending drag velocity
3475 size_t n_vel = 10;
3476 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3477 int xacc = 0;
3478 int yacc = 0;
3479 double tacc = 0;
3480 size_t length = m_drag_vec_t.size();
3481 for (size_t i = 0; i < n_vel; i++) {
3482 xacc += m_drag_vec_x.at(length - 1 - i);
3483 yacc += m_drag_vec_y.at(length - 1 - i);
3484 tacc += m_drag_vec_t.at(length - 1 - i);
3485 }
3486
3487 if (tacc == 0) return;
3488
3489 double drag_velocity_x = xacc / tacc;
3490 double drag_velocity_y = yacc / tacc;
3491 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3492 // drag_velocity_y);
3493
3494 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3495 // touch tap.
3496 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3497
3498 m_chart_drag_velocity_x = drag_velocity_x;
3499 m_chart_drag_velocity_y = drag_velocity_y;
3500
3501 m_chart_drag_inertia_active = true;
3502 // First callback as fast as possible.
3503 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3504}
3505
3506void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3507 if (!m_chart_drag_inertia_active) return;
3508 // Calculate time fraction from 0..1
3509 wxLongLong now = wxGetLocalTimeMillis();
3510 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3511 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3512 if (t > 1.0) t = 1.0;
3513 double e = 1.0 - easeOutCubic(t); // 0..1
3514
3515 double dx =
3516 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3517 double dy =
3518 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3519
3520 m_last_elapsed = elapsed;
3521
3522 // Ensure that target destination lies on whole-pixel boundary
3523 // This allows the render engine to use a faster FBO copy method for drawing
3524 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3525 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3526 double inertia_lat, inertia_lon;
3527 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3528 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3529 // Check if ownship has moved off-screen
3530 if (!IsOwnshipOnScreen()) {
3531 m_bFollow = false; // update the follow flag
3532 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3533 UpdateFollowButtonState();
3534 m_OSoffsetx = 0;
3535 m_OSoffsety = 0;
3536 } else {
3537 m_OSoffsetx += dx;
3538 m_OSoffsety -= dy;
3539 }
3540
3541 Refresh(false);
3542
3543 // Stop condition
3544 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3545 m_chart_drag_inertia_timer.Stop();
3546
3547 // Disable chart pan movement logic
3548 m_target_lat = GetVP().clat;
3549 m_target_lon = GetVP().clon;
3550 m_pan_drag.x = m_pan_drag.y = 0;
3551 m_panx = m_pany = 0;
3552 m_chart_drag_inertia_active = false;
3553 DoCanvasUpdate();
3554
3555 } else {
3556 int target_redraw_interval = 40; // msec
3557 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3558 }
3559}
3560
3561void ChartCanvas::StopMovement() {
3562 m_panx = m_pany = 0;
3563 m_panspeed = 0;
3564 m_zoom_factor = 1;
3565 m_rotation_speed = 0;
3566 m_mustmove = 0;
3567#if 0
3568#if !defined(__WXGTK__) && !defined(__WXQT__)
3569 SetFocus();
3570 top_frame::Get()->Raise();
3571#endif
3572#endif
3573}
3574
3575/* instead of integrating in timer callbacks
3576 (which do not always get called fast enough)
3577 we can perform the integration of movement
3578 at each render frame based on the time change */
3579bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3580 // Start/restart the stop movement timer
3581 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3582
3583 if (!pMovementTimer->IsRunning()) {
3584 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3585 }
3586
3587 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3588 // already moving, gets called again because of key-repeat event
3589 return false;
3590 }
3591
3592 m_last_movement_time = wxDateTime::UNow();
3593
3594 return true;
3595}
3596void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3597 int nstep) {
3598 // Save the target
3599 m_target_lat = target_lat;
3600 m_target_lon = target_lon;
3601
3602 // Save the start point
3603 m_start_lat = GetVP().clat;
3604 m_start_lon = GetVP().clon;
3605
3606 m_VPMovementTimer.Start(1, true); // oneshot
3607 m_timed_move_vp_active = true;
3608 m_stvpc = 0;
3609 m_timedVP_step = nstep;
3610}
3611
3612void ChartCanvas::DoTimedMovementVP() {
3613 if (!m_timed_move_vp_active) return; // not active
3614 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3615 StopMovement();
3616 return;
3617 }
3618 // Stop condition
3619 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3620 double d2 =
3621 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3622 d2 = pow(d2, 0.5);
3623
3624 if (d2 < one_pix) {
3625 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3626 StopMovementVP();
3627 return;
3628 }
3629
3630 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3631 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3632 // StopMovementVP();
3633 // return;
3634 // }
3635
3636 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3637 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3638
3639 m_run_lat = new_lat;
3640 m_run_lon = new_lon;
3641
3642 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3643}
3644
3645void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3646
3647void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3648
3649void ChartCanvas::StartTimedMovementTarget() {}
3650
3651void ChartCanvas::DoTimedMovementTarget() {}
3652
3653void ChartCanvas::StopMovementTarget() {}
3654int ntm;
3655
3656void ChartCanvas::DoTimedMovement() {
3657 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3658 !m_rotation_speed)
3659 return; /* not moving */
3660
3661 wxDateTime now = wxDateTime::UNow();
3662 long dt = 0;
3663 if (m_last_movement_time.IsValid())
3664 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3665
3666 m_last_movement_time = now;
3667
3668 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3669 dt = 500;
3670
3671 DoMovement(dt);
3672}
3673
3675 /* if we get here quickly assume 1ms so that some movement occurs */
3676 if (dt == 0) dt = 1;
3677
3678 m_mustmove -= dt;
3679 if (m_mustmove < 0) m_mustmove = 0;
3680
3681 if (!m_inPinch) { // this stops compound zoom/pan
3682 if (m_pan_drag.x || m_pan_drag.y) {
3683 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3684 m_pan_drag.x = m_pan_drag.y = 0;
3685 }
3686
3687 if (m_panx || m_pany) {
3688 const double slowpan = .1, maxpan = 2;
3689 if (m_modkeys == wxMOD_ALT)
3690 m_panspeed = slowpan;
3691 else {
3692 m_panspeed += (double)dt / 500; /* apply acceleration */
3693 m_panspeed = wxMin(maxpan, m_panspeed);
3694 }
3695 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3696 }
3697 }
3698 if (m_zoom_factor != 1) {
3699 double alpha = 400, beta = 1.5;
3700 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3701
3702 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3703
3704 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3705
3706 // Try to hit the zoom target exactly.
3707 // if(m_wheelzoom_stop_oneshot > 0)
3708 {
3709 if (zoom_factor > 1) {
3710 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3711 zoom_factor = VPoint.chart_scale / m_zoom_target;
3712 }
3713
3714 else if (zoom_factor < 1) {
3715 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3716 zoom_factor = VPoint.chart_scale / m_zoom_target;
3717 }
3718 }
3719
3720 if (fabs(zoom_factor - 1) > 1e-4) {
3721 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3722 } else {
3723 StopMovement();
3724 }
3725
3726 if (m_wheelzoom_stop_oneshot > 0) {
3727 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3728 m_wheelzoom_stop_oneshot = 0;
3729 StopMovement();
3730 }
3731
3732 // Don't overshoot the zoom target.
3733 if (zoom_factor > 1) {
3734 if (VPoint.chart_scale <= m_zoom_target) {
3735 m_wheelzoom_stop_oneshot = 0;
3736 StopMovement();
3737 }
3738 } else if (zoom_factor < 1) {
3739 if (VPoint.chart_scale >= m_zoom_target) {
3740 m_wheelzoom_stop_oneshot = 0;
3741 StopMovement();
3742 }
3743 }
3744 }
3745 }
3746
3747 if (m_rotation_speed) { /* in degrees per second */
3748 double speed = m_rotation_speed;
3749 if (m_modkeys == wxMOD_ALT) speed /= 10;
3750 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3751 }
3752}
3753
3754void ChartCanvas::SetColorScheme(ColorScheme cs) {
3755 SetAlertString("");
3756
3757 // Setup ownship image pointers
3758 switch (cs) {
3759 case GLOBAL_COLOR_SCHEME_DAY:
3760 m_pos_image_red = &m_os_image_red_day;
3761 m_pos_image_grey = &m_os_image_grey_day;
3762 m_pos_image_yellow = &m_os_image_yellow_day;
3763 m_pos_image_user = m_pos_image_user_day;
3764 m_pos_image_user_grey = m_pos_image_user_grey_day;
3765 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3766 m_cTideBitmap = m_bmTideDay;
3767 m_cCurrentBitmap = m_bmCurrentDay;
3768
3769 break;
3770 case GLOBAL_COLOR_SCHEME_DUSK:
3771 m_pos_image_red = &m_os_image_red_dusk;
3772 m_pos_image_grey = &m_os_image_grey_dusk;
3773 m_pos_image_yellow = &m_os_image_yellow_dusk;
3774 m_pos_image_user = m_pos_image_user_dusk;
3775 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3776 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3777 m_cTideBitmap = m_bmTideDusk;
3778 m_cCurrentBitmap = m_bmCurrentDusk;
3779 break;
3780 case GLOBAL_COLOR_SCHEME_NIGHT:
3781 m_pos_image_red = &m_os_image_red_night;
3782 m_pos_image_grey = &m_os_image_grey_night;
3783 m_pos_image_yellow = &m_os_image_yellow_night;
3784 m_pos_image_user = m_pos_image_user_night;
3785 m_pos_image_user_grey = m_pos_image_user_grey_night;
3786 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3787 m_cTideBitmap = m_bmTideNight;
3788 m_cCurrentBitmap = m_bmCurrentNight;
3789 break;
3790 default:
3791 m_pos_image_red = &m_os_image_red_day;
3792 m_pos_image_grey = &m_os_image_grey_day;
3793 m_pos_image_yellow = &m_os_image_yellow_day;
3794 m_pos_image_user = m_pos_image_user_day;
3795 m_pos_image_user_grey = m_pos_image_user_grey_day;
3796 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3797 m_cTideBitmap = m_bmTideDay;
3798 m_cCurrentBitmap = m_bmCurrentDay;
3799 break;
3800 }
3801
3802 CreateDepthUnitEmbossMaps(cs);
3803 CreateOZEmbossMapData(cs);
3804
3805 // Set up fog effect base color
3806 m_fog_color = wxColor(
3807 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3808 float dim = 1.0;
3809 switch (cs) {
3810 case GLOBAL_COLOR_SCHEME_DUSK:
3811 dim = 0.5;
3812 break;
3813 case GLOBAL_COLOR_SCHEME_NIGHT:
3814 dim = 0.25;
3815 break;
3816 default:
3817 break;
3818 }
3819 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3820 m_fog_color.Blue() * dim);
3821
3822 // Really dark
3823#if 0
3824 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3825 SetBackgroundColour( wxColour(0,0,0) );
3826
3827 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3828 }
3829 else{
3830 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3831#ifndef __WXMAC__
3832 SetBackgroundColour( wxNullColour );
3833#endif
3834 }
3835#endif
3836
3837 // UpdateToolbarColorScheme(cs);
3838
3839 m_Piano->SetColorScheme(cs);
3840
3841 m_Compass->SetColorScheme(cs);
3842
3843 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3844
3845 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3846
3847 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3848 if (m_notification_button) {
3849 m_notification_button->SetColorScheme(cs);
3850 }
3851
3852#ifdef ocpnUSE_GL
3853 if (g_bopengl && m_glcc) {
3854 m_glcc->SetColorScheme(cs);
3855 g_glTextureManager->ClearAllRasterTextures();
3856 // m_glcc->FlushFBO();
3857 }
3858#endif
3859 SetbTCUpdate(true); // force re-render of tide/current locators
3860 m_brepaint_piano = true;
3861
3862 ReloadVP();
3863
3864 m_cs = cs;
3865}
3866
3867wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3868 wxImage img = Bitmap.ConvertToImage();
3869 int sx = img.GetWidth();
3870 int sy = img.GetHeight();
3871
3872 wxImage new_img(img);
3873
3874 for (int i = 0; i < sx; i++) {
3875 for (int j = 0; j < sy; j++) {
3876 if (!img.IsTransparent(i, j)) {
3877 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3878 (unsigned char)(img.GetGreen(i, j) * factor),
3879 (unsigned char)(img.GetBlue(i, j) * factor));
3880 }
3881 }
3882 }
3883
3884 wxBitmap ret = wxBitmap(new_img);
3885
3886 return ret;
3887}
3888
3889void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3890 int max) {
3891 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3892 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3893
3894 if (!m_pBrightPopup) {
3895 // Calculate size
3896 int x, y;
3897 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3898
3899 m_pBrightPopup = new TimedPopupWin(this, 3);
3900
3901 m_pBrightPopup->SetSize(x, y);
3902 m_pBrightPopup->Move(120, 120);
3903 }
3904
3905 int bmpsx = m_pBrightPopup->GetSize().x;
3906 int bmpsy = m_pBrightPopup->GetSize().y;
3907
3908 wxBitmap bmp(bmpsx, bmpsx);
3909 wxMemoryDC mdc(bmp);
3910
3911 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3912 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3913 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3914 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3915 mdc.Clear();
3916
3917 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3918
3919 mdc.SetFont(*pfont);
3920 wxString val;
3921
3922 if (brightness == max)
3923 val = "MAX";
3924 else if (brightness == min)
3925 val = "MIN";
3926 else
3927 val.Printf("%3d", brightness);
3928
3929 mdc.DrawText(val, 0, 0);
3930
3931 mdc.SelectObject(wxNullBitmap);
3932
3933 m_pBrightPopup->SetBitmap(bmp);
3934 m_pBrightPopup->Show();
3935 m_pBrightPopup->Refresh();
3936}
3937
3938void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3939 m_b_rot_hidef = true;
3940 ReloadVP();
3941}
3942
3943void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3944 if (!g_bRollover) return;
3945
3946 bool b_need_refresh = false;
3947
3948 wxSize win_size = GetSize() * m_displayScale;
3949 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3950
3951 // Handle the AIS Rollover Window first
3952 bool showAISRollover = false;
3953 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3954 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3955 SelectItem *pFind = pSelectAIS->FindSelection(
3956 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3957 if (pFind) {
3958 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3959 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3960
3961 if (ptarget) {
3962 showAISRollover = true;
3963
3964 if (NULL == m_pAISRolloverWin) {
3965 m_pAISRolloverWin = new RolloverWin(this);
3966 m_pAISRolloverWin->IsActive(false);
3967 b_need_refresh = true;
3968 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3969 m_AISRollover_MMSI != FoundAIS_MMSI) {
3970 // Sometimes the mouse moves fast enough to get over a new AIS
3971 // target before the one-shot has fired to remove the old target.
3972 // Result: wrong target data is shown.
3973 // Detect this case,close the existing rollover ASAP, and restart
3974 // the timer.
3975 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3976 m_pAISRolloverWin->IsActive(false);
3977 m_AISRollover_MMSI = 0;
3978 Refresh();
3979 return;
3980 }
3981
3982 m_AISRollover_MMSI = FoundAIS_MMSI;
3983
3984 if (!m_pAISRolloverWin->IsActive()) {
3985 wxString s = ptarget->GetRolloverString();
3986 m_pAISRolloverWin->SetString(s);
3987
3988 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3989 AIS_ROLLOVER, win_size);
3990 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3991 m_pAISRolloverWin->IsActive(true);
3992 b_need_refresh = true;
3993 }
3994 }
3995 } else {
3996 m_AISRollover_MMSI = 0;
3997 showAISRollover = false;
3998 }
3999 }
4000
4001 // Maybe turn the rollover off
4002 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
4003 m_pAISRolloverWin->IsActive(false);
4004 m_AISRollover_MMSI = 0;
4005 b_need_refresh = true;
4006 }
4007
4008 // Now the Route info rollover
4009 // Show the route segment info
4010 bool showRouteRollover = false;
4011
4012 if (NULL == m_pRolloverRouteSeg) {
4013 // Get a list of all selectable sgements, and search for the first
4014 // visible segment as the rollover target.
4015
4016 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4017 SelectableItemList SelList = pSelect->FindSelectionList(
4018 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4019 auto node = SelList.begin();
4020 while (node != SelList.end()) {
4021 SelectItem *pFindSel = *node;
4022
4023 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4024
4025 if (pr && pr->IsVisible()) {
4026 m_pRolloverRouteSeg = pFindSel;
4027 showRouteRollover = true;
4028
4029 if (NULL == m_pRouteRolloverWin) {
4030 m_pRouteRolloverWin = new RolloverWin(this, 10);
4031 m_pRouteRolloverWin->IsActive(false);
4032 }
4033
4034 if (!m_pRouteRolloverWin->IsActive()) {
4035 wxString s;
4036 RoutePoint *segShow_point_a =
4037 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4038 RoutePoint *segShow_point_b =
4039 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4040
4041 double brg, dist;
4042 DistanceBearingMercator(
4043 segShow_point_b->m_lat, segShow_point_b->m_lon,
4044 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4045
4046 if (!pr->m_bIsInLayer)
4047 s.Append(_("Route") + ": ");
4048 else
4049 s.Append(_("Layer Route: "));
4050
4051 if (pr->m_RouteNameString.IsEmpty())
4052 s.Append(_("(unnamed)"));
4053 else
4054 s.Append(pr->m_RouteNameString);
4055
4056 s << "\n"
4057 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4058 << "\n"
4059 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4060 << segShow_point_b->GetName() << "\n";
4061
4062 if (g_bShowTrue)
4063 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4064 (int)floor(brg + 0.5), 0x00B0);
4065 if (g_bShowMag) {
4066 double latAverage =
4067 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4068 double lonAverage =
4069 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4070 double varBrg =
4071 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4072
4073 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4074 (int)floor(varBrg + 0.5), 0x00B0);
4075 }
4076
4077 s << FormatDistanceAdaptive(dist);
4078
4079 // Compute and display cumulative distance from route start point to
4080 // current leg end point and RNG,TTG,ETA from ship to current leg end
4081 // point for active route
4082 double shiptoEndLeg = 0.;
4083 bool validActive = false;
4084 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4085 validActive = true;
4086
4087 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4088 auto node = pr->pRoutePointList->begin();
4089 RoutePoint *prp;
4090 float dist_to_endleg = 0;
4091 wxString t;
4092
4093 for (++node; node != pr->pRoutePointList->end(); ++node) {
4094 prp = *node;
4095 if (validActive)
4096 shiptoEndLeg += prp->m_seg_len;
4097 else if (prp->m_bIsActive)
4098 validActive = true;
4099 dist_to_endleg += prp->m_seg_len;
4100 if (prp->IsSame(segShow_point_a)) break;
4101 }
4102 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4103 }
4104 // write from ship to end selected leg point data if the route is
4105 // active
4106 if (validActive) {
4107 s << "\n"
4108 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4109 shiptoEndLeg +=
4111 ->GetCurrentRngToActivePoint(); // add distance from ship
4112 // to active point
4113 shiptoEndLeg +=
4114 segShow_point_b
4115 ->m_seg_len; // add the lenght of the selected leg
4116 s << FormatDistanceAdaptive(shiptoEndLeg);
4117 // ensure sog/cog are valid and vmg is positive to keep data
4118 // coherent
4119 double vmg = 0.;
4120 if (!std::isnan(gCog) && !std::isnan(gSog))
4121 vmg = gSog *
4122 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4123 PI / 180.);
4124 if (vmg > 0.) {
4125 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4126 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4127 s << " - "
4128 << wxString(ttg_sec > SECONDS_PER_DAY
4129 ? ttg_span.Format(_("%Dd %H:%M"))
4130 : ttg_span.Format(_("%H:%M")));
4131 wxDateTime dtnow, eta;
4132 eta = dtnow.SetToCurrent().Add(ttg_span);
4133 s << " - " << eta.Format("%b").Mid(0, 4)
4134 << eta.Format(" %d %H:%M");
4135 } else
4136 s << " ---- ----";
4137 }
4138 m_pRouteRolloverWin->SetString(s);
4139
4140 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4141 LEG_ROLLOVER, win_size);
4142 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4143 m_pRouteRolloverWin->IsActive(true);
4144 b_need_refresh = true;
4145 showRouteRollover = true;
4146 break;
4147 }
4148 } else {
4149 ++node;
4150 }
4151 }
4152 } else {
4153 // Is the cursor still in select radius, and not timed out?
4154 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4155 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4156 m_pRolloverRouteSeg))
4157 showRouteRollover = false;
4158 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4159 showRouteRollover = false;
4160 else
4161 showRouteRollover = true;
4162 }
4163
4164 // If currently creating a route, do not show this rollover window
4165 if (m_routeState) showRouteRollover = false;
4166
4167 // Similar for AIS target rollover window
4168 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4169 showRouteRollover = false;
4170
4171 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4172 !showRouteRollover) {
4173 m_pRouteRolloverWin->IsActive(false);
4174 m_pRolloverRouteSeg = NULL;
4175 m_pRouteRolloverWin->Destroy();
4176 m_pRouteRolloverWin = NULL;
4177 b_need_refresh = true;
4178 } else if (m_pRouteRolloverWin && showRouteRollover) {
4179 m_pRouteRolloverWin->IsActive(true);
4180 b_need_refresh = true;
4181 }
4182
4183 // Now the Track info rollover
4184 // Show the track segment info
4185 bool showTrackRollover = false;
4186
4187 if (NULL == m_pRolloverTrackSeg) {
4188 // Get a list of all selectable sgements, and search for the first
4189 // visible segment as the rollover target.
4190
4191 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4192 SelectableItemList SelList = pSelect->FindSelectionList(
4193 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4194
4195 auto node = SelList.begin();
4196 while (node != SelList.end()) {
4197 SelectItem *pFindSel = *node;
4198
4199 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4200
4201 if (pt && pt->IsVisible()) {
4202 m_pRolloverTrackSeg = pFindSel;
4203 showTrackRollover = true;
4204
4205 if (NULL == m_pTrackRolloverWin) {
4206 m_pTrackRolloverWin = new RolloverWin(this, 10);
4207 m_pTrackRolloverWin->IsActive(false);
4208 }
4209
4210 if (!m_pTrackRolloverWin->IsActive()) {
4211 wxString s;
4212 TrackPoint *segShow_point_a =
4213 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4214 TrackPoint *segShow_point_b =
4215 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4216
4217 double brg, dist;
4218 DistanceBearingMercator(
4219 segShow_point_b->m_lat, segShow_point_b->m_lon,
4220 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4221
4222 if (!pt->m_bIsInLayer)
4223 s.Append(_("Track") + ": ");
4224 else
4225 s.Append(_("Layer Track: "));
4226
4227 if (pt->GetName().IsEmpty())
4228 s.Append(_("(unnamed)"));
4229 else
4230 s.Append(pt->GetName());
4231 double tlenght = pt->Length();
4232 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4233 if (pt->GetLastPoint()->GetTimeString() &&
4234 pt->GetPoint(0)->GetTimeString()) {
4235 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4236 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4237 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4238 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4239 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4240 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4241 << getUsrSpeedUnit();
4242 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4243 : ttime.Format(" %H:%M"));
4244 }
4245 }
4246
4247 if (g_bShowTrackPointTime &&
4248 strlen(segShow_point_b->GetTimeString())) {
4249 wxString stamp = segShow_point_b->GetTimeString();
4250 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4251 if (timestamp.IsValid()) {
4252 // Format track rollover timestamp to OCPN global TZ setting
4255 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4256 }
4257 s << "\n" << _("Segment Created: ") << stamp;
4258 }
4259
4260 s << "\n";
4261 if (g_bShowTrue)
4262 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4263 0x00B0);
4264
4265 if (g_bShowMag) {
4266 double latAverage =
4267 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4268 double lonAverage =
4269 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4270 double varBrg =
4271 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4272
4273 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4274 0x00B0);
4275 }
4276
4277 s << FormatDistanceAdaptive(dist);
4278
4279 if (segShow_point_a->GetTimeString() &&
4280 segShow_point_b->GetTimeString()) {
4281 wxDateTime apoint = segShow_point_a->GetCreateTime();
4282 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4283 if (apoint.IsValid() && bpoint.IsValid()) {
4284 double segmentSpeed = toUsrSpeed(
4285 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4286 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4287 << getUsrSpeedUnit();
4288 }
4289 }
4290
4291 m_pTrackRolloverWin->SetString(s);
4292
4293 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4294 LEG_ROLLOVER, win_size);
4295 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4296 m_pTrackRolloverWin->IsActive(true);
4297 b_need_refresh = true;
4298 showTrackRollover = true;
4299 break;
4300 }
4301 } else {
4302 ++node;
4303 }
4304 }
4305 } else {
4306 // Is the cursor still in select radius, and not timed out?
4307 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4308 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4309 m_pRolloverTrackSeg))
4310 showTrackRollover = false;
4311 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4312 showTrackRollover = false;
4313 else
4314 showTrackRollover = true;
4315 }
4316
4317 // Similar for AIS target rollover window
4318 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4319 showTrackRollover = false;
4320
4321 // Similar for route rollover window
4322 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4323 showTrackRollover = false;
4324
4325 // TODO We onlt show tracks on primary canvas....
4326 // if(!IsPrimaryCanvas())
4327 // showTrackRollover = false;
4328
4329 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4330 !showTrackRollover) {
4331 m_pTrackRolloverWin->IsActive(false);
4332 m_pRolloverTrackSeg = NULL;
4333 m_pTrackRolloverWin->Destroy();
4334 m_pTrackRolloverWin = NULL;
4335 b_need_refresh = true;
4336 } else if (m_pTrackRolloverWin && showTrackRollover) {
4337 m_pTrackRolloverWin->IsActive(true);
4338 b_need_refresh = true;
4339 }
4340
4341 if (b_need_refresh) Refresh();
4342}
4343
4344void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4345 if ((GetShowENCLights() || m_bsectors_shown) &&
4346 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4347 extendedSectorLegs)) {
4348 if (!m_bsectors_shown) {
4349 ReloadVP(false);
4350 m_bsectors_shown = true;
4351 }
4352 } else {
4353 if (m_bsectors_shown) {
4354 ReloadVP(false);
4355 m_bsectors_shown = false;
4356 }
4357 }
4358
4359// This is here because GTK status window update is expensive..
4360// cairo using pango rebuilds the font every time so is very
4361// inefficient
4362// Anyway, only update the status bar when this timer expires
4363#if defined(__WXGTK__) || defined(__WXQT__)
4364 {
4365 // Check the absolute range of the cursor position
4366 // There could be a window wherein the chart geoereferencing is not
4367 // valid....
4368 double cursor_lat, cursor_lon;
4369 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4370
4371 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4372 while (cursor_lon < -180.) cursor_lon += 360.;
4373
4374 while (cursor_lon > 180.) cursor_lon -= 360.;
4375
4376 SetCursorStatus(cursor_lat, cursor_lon);
4377 }
4378 }
4379#endif
4380}
4381
4382void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4383 if (!top_frame::Get()->GetFrameStatusBar()) return;
4384
4385 wxString s1;
4386 s1 += " ";
4387 s1 += toSDMM(1, cursor_lat);
4388 s1 += " ";
4389 s1 += toSDMM(2, cursor_lon);
4390
4391 if (STAT_FIELD_CURSOR_LL >= 0)
4392 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4393
4394 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4395
4396 double brg, dist;
4397 wxString sm;
4398 wxString st;
4399 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4400 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4401 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4402
4403 wxString s = st + sm;
4404 s << FormatDistanceAdaptive(dist);
4405
4406 // CUSTOMIZATION - LIVE ETA OPTION
4407 // -------------------------------------------------------
4408 // Calculate an "live" ETA based on route starting from the current
4409 // position of the boat and goes to the cursor of the mouse.
4410 // In any case, an standard ETA will be calculated with a default speed
4411 // of the boat to give an estimation of the route (in particular if GPS
4412 // is off).
4413
4414 // Display only if option "live ETA" is selected in Settings > Display >
4415 // General.
4416 if (g_bShowLiveETA) {
4417 float realTimeETA;
4418 float boatSpeed;
4419 float boatSpeedDefault = g_defaultBoatSpeed;
4420
4421 // Calculate Estimate Time to Arrival (ETA) in minutes
4422 // Check before is value not closed to zero (it will make an very big
4423 // number...)
4424 if (!std::isnan(gSog)) {
4425 boatSpeed = gSog;
4426 if (boatSpeed < 0.5) {
4427 realTimeETA = 0;
4428 } else {
4429 realTimeETA = dist / boatSpeed * 60;
4430 }
4431 } else {
4432 realTimeETA = 0;
4433 }
4434
4435 // Add space after distance display
4436 s << " ";
4437 // Display ETA
4438 s << minutesToHoursDays(realTimeETA);
4439
4440 // In any case, display also an ETA with default speed at 6knts
4441
4442 s << " [@";
4443 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4444 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4445 s << " ";
4446 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4447 s << "]";
4448 }
4449 // END OF - LIVE ETA OPTION
4450
4451 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4452}
4453
4454// CUSTOMIZATION - FORMAT MINUTES
4455// -------------------------------------------------------
4456// New function to format minutes into a more readable format:
4457// * Hours + minutes, or
4458// * Days + hours.
4459wxString minutesToHoursDays(float timeInMinutes) {
4460 wxString s;
4461
4462 if (timeInMinutes == 0) {
4463 s << "--min";
4464 }
4465
4466 // Less than 60min, keep time in minutes
4467 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4468 s << wxString::Format("%d", (int)timeInMinutes);
4469 s << "min";
4470 }
4471
4472 // Between 1h and less than 24h, display time in hours, minutes
4473 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4474 int hours;
4475 int min;
4476 hours = (int)timeInMinutes / 60;
4477 min = (int)timeInMinutes % 60;
4478
4479 if (min == 0) {
4480 s << wxString::Format("%d", hours);
4481 s << "h";
4482 } else {
4483 s << wxString::Format("%d", hours);
4484 s << "h";
4485 s << wxString::Format("%d", min);
4486 s << "min";
4487 }
4488
4489 }
4490
4491 // More than 24h, display time in days, hours
4492 else if (timeInMinutes > 24 * 60) {
4493 int days;
4494 int hours;
4495 days = (int)(timeInMinutes / 60) / 24;
4496 hours = (int)(timeInMinutes / 60) % 24;
4497
4498 if (hours == 0) {
4499 s << wxString::Format("%d", days);
4500 s << "d";
4501 } else {
4502 s << wxString::Format("%d", days);
4503 s << "d";
4504 s << wxString::Format("%d", hours);
4505 s << "h";
4506 }
4507 }
4508
4509 return s;
4510}
4511
4512// END OF CUSTOMIZATION - FORMAT MINUTES
4513// Thanks open source code ;-)
4514// -------------------------------------------------------
4515
4516void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4517 double clat, clon;
4518 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4519 *lat = clat;
4520 *lon = clon;
4521}
4522
4523void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4524 wxPoint2DDouble *r) {
4525 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4526}
4527
4529 double rlon, wxPoint2DDouble *r) {
4530 // If the Current Chart is a raster chart, and the
4531 // requested lat/long is within the boundaries of the chart,
4532 // and the VP is not rotated,
4533 // then use the embedded BSB chart georeferencing algorithm
4534 // for greater accuracy
4535 // Additionally, use chart embedded georef if the projection is TMERC
4536 // i.e. NOT MERCATOR and NOT POLYCONIC
4537
4538 // If for some reason the chart rejects the request by returning an error,
4539 // then fall back to Viewport Projection estimate from canvas parameters
4540 if (!g_bopengl && m_singleChart &&
4541 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4542 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4543 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4544 (m_singleChart->GetChartProjectionType() !=
4545 PROJECTION_TRANSVERSE_MERCATOR) &&
4546 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4547 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4548 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4549 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4550 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4551 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4552 // Cur_BSB_Ch->GetCOVRTablenPoints
4553 // ( 0 ), rlon,
4554 // rlat );
4555 // bInside = true;
4556 // if ( bInside )
4557 if (Cur_BSB_Ch) {
4558 // This is a Raster chart....
4559 // If the VP is changing, the raster chart parameters may not yet be
4560 // setup So do that before accessing the chart's embedded
4561 // georeferencing
4562 Cur_BSB_Ch->SetVPRasterParms(vp);
4563 double rpixxd, rpixyd;
4564 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4565 r->m_x = rpixxd;
4566 r->m_y = rpixyd;
4567 return;
4568 }
4569 }
4570 }
4571
4572 // if needed, use the VPoint scaling estimator,
4573 *r = vp.GetDoublePixFromLL(rlat, rlon);
4574}
4575
4576// This routine might be deleted and all of the rendering improved
4577// to have floating point accuracy
4578bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4579 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4580}
4581
4582bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4583 wxPoint *r) {
4584 wxPoint2DDouble p;
4585 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4586
4587 // some projections give nan values when invisible values (other side of
4588 // world) are requested we should stop using integer coordinates or return
4589 // false here (and test it everywhere)
4590 if (std::isnan(p.m_x)) {
4591 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4592 return false;
4593 }
4594
4595 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4596 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4597 else
4598 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4599
4600 return true;
4601}
4602
4603void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4604 double &lon) {
4605 // If the Current Chart is a raster chart, and the
4606 // requested x,y is within the boundaries of the chart,
4607 // and the VP is not rotated,
4608 // then use the embedded BSB chart georeferencing algorithm
4609 // for greater accuracy
4610 // Additionally, use chart embedded georef if the projection is TMERC
4611 // i.e. NOT MERCATOR and NOT POLYCONIC
4612
4613 // If for some reason the chart rejects the request by returning an error,
4614 // then fall back to Viewport Projection estimate from canvas parameters
4615 bool bUseVP = true;
4616
4617 if (!g_bopengl && m_singleChart &&
4618 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4619 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4620 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4621 (m_singleChart->GetChartProjectionType() !=
4622 PROJECTION_TRANSVERSE_MERCATOR) &&
4623 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4624 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4625 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4626 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4627
4628 // TODO maybe need iterative process to validate bInside
4629 // first pass is mercator, then check chart boundaries
4630
4631 if (Cur_BSB_Ch) {
4632 // This is a Raster chart....
4633 // If the VP is changing, the raster chart parameters may not yet be
4634 // setup So do that before accessing the chart's embedded
4635 // georeferencing
4636 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4637
4638 double slat, slon;
4639 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4640 lat = slat;
4641
4642 if (slon < -180.)
4643 slon += 360.;
4644 else if (slon > 180.)
4645 slon -= 360.;
4646
4647 lon = slon;
4648 bUseVP = false;
4649 }
4650 }
4651 }
4652
4653 // if needed, use the VPoint scaling estimator
4654 if (bUseVP) {
4655 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4656 }
4657}
4658
4660 StopMovement();
4661 DoZoomCanvas(factor, false);
4662 extendedSectorLegs.clear();
4663}
4664
4665void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4666 bool stoptimer) {
4667 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4668
4669 if (g_bsmoothpanzoom) {
4670 if (StartTimedMovement(stoptimer)) {
4671 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4672 m_zoom_factor = factor;
4673 }
4674
4675 m_zoom_target = VPoint.chart_scale / factor;
4676 } else {
4677 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4678
4679 DoZoomCanvas(factor, can_zoom_to_cursor);
4680 }
4681
4682 extendedSectorLegs.clear();
4683}
4684
4685void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4686 // possible on startup
4687 if (!ChartData) return;
4688 if (!m_pCurrentStack) return;
4689
4690 /* TODO: queue the quilted loading code to a background thread
4691 so yield is never called from here, and also rendering is not delayed */
4692
4693 // Cannot allow Yield() re-entrancy here
4694 if (m_bzooming) return;
4695 m_bzooming = true;
4696
4697 double old_ppm = GetVP().view_scale_ppm;
4698
4699 // Capture current cursor position for zoom to cursor
4700 double zlat = m_cursor_lat;
4701 double zlon = m_cursor_lon;
4702
4703 double proposed_scale_onscreen =
4704 GetVP().chart_scale /
4705 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4706 bool b_do_zoom = false;
4707
4708 if (factor > 1) {
4709 b_do_zoom = true;
4710
4711 // double zoom_factor = factor;
4712
4713 ChartBase *pc = NULL;
4714
4715 if (!VPoint.b_quilt) {
4716 pc = m_singleChart;
4717 } else {
4718 if (!m_disable_adjust_on_zoom) {
4719 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4720 if (new_db_index >= 0)
4721 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4722 else { // for whatever reason, no reference chart is known
4723 // Choose the smallest scale chart on the current stack
4724 // and then adjust for scale range
4725 int current_ref_stack_index = -1;
4726 if (m_pCurrentStack->nEntry) {
4727 int trial_index =
4728 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4729 m_pQuilt->SetReferenceChart(trial_index);
4730 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4731 if (new_db_index >= 0)
4732 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4733 }
4734 }
4735
4736 if (m_pCurrentStack)
4737 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4738 new_db_index); // highlite the correct bar entry
4739 }
4740 }
4741
4742 if (pc) {
4743 // double target_scale_ppm = GetVPScale() * zoom_factor;
4744 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4745 // target_scale_ppm;
4746
4747 // Query the chart to determine the appropriate zoom range
4748 double min_allowed_scale =
4749 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4750
4751 if (proposed_scale_onscreen < min_allowed_scale) {
4752 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4753 m_zoom_factor = 1; /* stop zooming */
4754 b_do_zoom = false;
4755 } else
4756 proposed_scale_onscreen = min_allowed_scale;
4757 }
4758
4759 } else {
4760 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4761 }
4762
4763 } else if (factor < 1) {
4764 b_do_zoom = true;
4765
4766 ChartBase *pc = NULL;
4767
4768 bool b_smallest = false;
4769
4770 if (!VPoint.b_quilt) { // not quilted
4771 pc = m_singleChart;
4772
4773 if (pc) {
4774 // If m_singleChart is not on the screen, unbound the zoomout
4775 LLBBox viewbox = VPoint.GetBBox();
4776 // BoundingBox chart_box;
4777 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4778 double max_allowed_scale;
4779
4780 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4781
4782 // We can allow essentially unbounded zoomout in single chart mode
4783 // if( ChartData->GetDBBoundingBox( current_index,
4784 // &chart_box ) &&
4785 // !viewbox.IntersectOut( chart_box ) )
4786 // // Clamp the minimum scale zoom-out to the value
4787 // specified by the chart max_allowed_scale =
4788 // wxMin(max_allowed_scale, 4.0 *
4789 // pc->GetNormalScaleMax(
4790 // GetCanvasScaleFactor(),
4791 // GetCanvasWidth() ) );
4792 if (proposed_scale_onscreen > max_allowed_scale) {
4793 m_zoom_factor = 1; /* stop zooming */
4794 proposed_scale_onscreen = max_allowed_scale;
4795 }
4796 }
4797
4798 } else {
4799 if (!m_disable_adjust_on_zoom) {
4800 int new_db_index =
4801 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4802 if (new_db_index >= 0)
4803 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4804
4805 if (m_pCurrentStack)
4806 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4807 new_db_index); // highlite the correct bar entry
4808
4809 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4810
4811 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4812 proposed_scale_onscreen =
4813 wxMin(proposed_scale_onscreen,
4814 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4815 }
4816
4817 // set a minimum scale
4818 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4819 m_absolute_min_scale_ppm)
4820 proposed_scale_onscreen =
4821 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4822 }
4823 }
4824 double new_scale =
4825 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4826
4827 if (b_do_zoom) {
4828 // Disable ZTC if lookahead is ON, and currently b_follow is active
4829 bool b_allow_ztc = true;
4830 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4831 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4832 if (m_bLookAhead) {
4833 double brg, distance;
4834 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4835 &distance);
4836 dir_to_shift = brg;
4837 meters_to_shift = distance * 1852;
4838 }
4839 // Arrange to combine the zoom and pan into one operation for smoother
4840 // appearance
4841 SetVPScale(new_scale, false); // adjust, but deferred refresh
4842 wxPoint r;
4843 GetCanvasPointPix(zlat, zlon, &r);
4844 // this will emit the Refresh()
4845 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4846 } else {
4847 SetVPScale(new_scale);
4848 if (m_bFollow) DoCanvasUpdate();
4849 }
4850 }
4851
4852 m_bzooming = false;
4853}
4854
4855void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4856 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4857 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4858}
4859
4860int rot;
4861void ChartCanvas::RotateCanvas(double dir) {
4862 // SetUpMode(NORTH_UP_MODE);
4863
4864 if (g_bsmoothpanzoom) {
4865 if (StartTimedMovement()) {
4866 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4867 m_rotation_speed = dir * 60;
4868 }
4869 } else {
4870 double speed = dir * 10;
4871 if (m_modkeys == wxMOD_ALT) speed /= 20;
4872 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4873 }
4874}
4875
4876void ChartCanvas::DoRotateCanvas(double rotation) {
4877 while (rotation < 0) rotation += 2 * PI;
4878 while (rotation > 2 * PI) rotation -= 2 * PI;
4879
4880 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4881
4882 SetVPRotation(rotation);
4883 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4884}
4885
4886void ChartCanvas::DoTiltCanvas(double tilt) {
4887 while (tilt < 0) tilt = 0;
4888 while (tilt > .95) tilt = .95;
4889
4890 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4891
4892 VPoint.tilt = tilt;
4893 Refresh(false);
4894}
4895
4896void ChartCanvas::TogglebFollow() {
4897 if (!m_bFollow)
4898 SetbFollow();
4899 else
4900 ClearbFollow();
4901}
4902
4903void ChartCanvas::ClearbFollow() {
4904 m_bFollow = false; // update the follow flag
4905
4906 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4907
4908 UpdateFollowButtonState();
4909
4910 DoCanvasUpdate();
4911 ReloadVP();
4912 top_frame::Get()->SetChartUpdatePeriod();
4913}
4914
4915void ChartCanvas::SetbFollow() {
4916 // Is the OWNSHIP on-screen?
4917 // If not, then reset the OWNSHIP offset to 0 (center screen)
4918 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4919 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4920 m_OSoffsetx = 0;
4921 m_OSoffsety = 0;
4922 }
4923
4924 // Apply the present b_follow offset values to ship position
4925 wxPoint2DDouble p;
4927 p.m_x += m_OSoffsetx;
4928 p.m_y -= m_OSoffsety;
4929
4930 // compute the target center screen lat/lon
4931 double dlat, dlon;
4932 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4933
4934 JumpToPosition(dlat, dlon, GetVPScale());
4935 m_bFollow = true;
4936
4937 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4938 UpdateFollowButtonState();
4939
4940 if (!g_bSmoothRecenter) {
4941 DoCanvasUpdate();
4942 ReloadVP();
4943 }
4944 top_frame::Get()->SetChartUpdatePeriod();
4945}
4946
4947void ChartCanvas::UpdateFollowButtonState() {
4948 if (m_muiBar) {
4949 if (!m_bFollow)
4950 m_muiBar->SetFollowButtonState(0);
4951 else {
4952 if (m_bLookAhead)
4953 m_muiBar->SetFollowButtonState(2);
4954 else
4955 m_muiBar->SetFollowButtonState(1);
4956 }
4957 }
4958
4959#ifdef __ANDROID__
4960 if (!m_bFollow)
4961 androidSetFollowTool(0);
4962 else {
4963 if (m_bLookAhead)
4964 androidSetFollowTool(2);
4965 else
4966 androidSetFollowTool(1);
4967 }
4968#endif
4969
4970 // Look for plugin using API-121 or later
4971 // If found, make the follow state callback.
4972 if (g_pi_manager) {
4973 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4974 if (pic->m_enabled && pic->m_init_state) {
4975 switch (pic->m_api_version) {
4976 case 121: {
4977 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4978 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4979 break;
4980 }
4981 default:
4982 break;
4983 }
4984 }
4985 }
4986 }
4987}
4988
4989void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4990 if (g_bSmoothRecenter && !m_routeState) {
4991 if (StartSmoothJump(lat, lon, scale_ppm))
4992 return;
4993 else {
4994 // move closer to the target destination, and try again
4995 double gcDist, gcBearingEnd;
4996 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4997 &gcBearingEnd);
4998 gcBearingEnd += 180;
4999 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
5000 GetCanvasWidth() / GetVPScale(); // meters
5001 double lon_offset =
5002 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
5003 double new_lat = lat + (lat_offset / (1852 * 60));
5004 double new_lon = lon + (lon_offset / (1852 * 60));
5005 SetViewPoint(new_lat, new_lon);
5006 ReloadVP();
5007 StartSmoothJump(lat, lon, scale_ppm);
5008 return;
5009 }
5010 }
5011
5012 if (lon > 180.0) lon -= 360.0;
5013 m_vLat = lat;
5014 m_vLon = lon;
5015 StopMovement();
5016 m_bFollow = false;
5017
5018 if (!GetQuiltMode()) {
5019 double skew = 0;
5020 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5021 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5022 } else {
5023 if (scale_ppm != GetVPScale()) {
5024 // XXX should be done in SetViewPoint
5025 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5026 AdjustQuiltRefChart();
5027 }
5028 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5029 }
5030
5031 ReloadVP();
5032
5033 UpdateFollowButtonState();
5034
5035 // TODO
5036 // if( g_pi_manager ) {
5037 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5038 // }
5039}
5040
5041bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5042 // Check distance to jump, in pixels at current chart scale
5043 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5044 // width.
5045 double gcDist;
5046 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5047 double distance_pixels = gcDist * GetVPScale();
5048 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5049 // Jump is too far, try again
5050 return false;
5051 }
5052
5053 // Save where we're coming from
5054 m_startLat = m_vLat;
5055 m_startLon = m_vLon;
5056 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5057
5058 // Save where we want to end up
5059 m_endLat = lat;
5060 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5061 m_endScale = scale_ppm;
5062
5063 // Setup timing
5064 m_animationDuration = 600; // ms
5065 m_animationStart = wxGetLocalTimeMillis();
5066
5067 // Stop any previous movement, ensure no conflicts
5068 StopMovement();
5069 m_bFollow = false;
5070
5071 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5072 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5073 m_animationActive = true;
5074
5075 return true;
5076}
5077
5078void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5079 // Calculate time fraction from 0..1
5080 wxLongLong now = wxGetLocalTimeMillis();
5081 double elapsed = (now - m_animationStart).ToDouble();
5082 double t = elapsed / m_animationDuration.ToDouble();
5083 if (t > 1.0) t = 1.0;
5084
5085 // Ease function for smoother movement
5086 double e = easeOutCubic(t);
5087
5088 // Interpolate lat/lon/scale
5089 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5090 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5091 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5092
5093 // Update viewpoint
5094 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5095 // portion)
5096 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5097 ReloadVP();
5098
5099 // If we reached the end, stop the timer and finalize
5100 if (t >= 1.0) {
5101 m_easeTimer.Stop();
5102 m_animationActive = false;
5103 UpdateFollowButtonState();
5104 ZoomCanvasSimple(1.0001);
5105 DoCanvasUpdate();
5106 ReloadVP();
5107 }
5108}
5109
5110bool ChartCanvas::PanCanvas(double dx, double dy) {
5111 if (!ChartData) return false;
5112 extendedSectorLegs.clear();
5113
5114 double dlat, dlon;
5115 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5116
5117 int iters = 0;
5118 for (;;) {
5119 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5120
5121 if (iters++ > 5) return false;
5122 if (!std::isnan(dlat)) break;
5123
5124 dx *= .5, dy *= .5;
5125 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5126 }
5127
5128 // avoid overshooting the poles
5129 if (dlat > 90)
5130 dlat = 90;
5131 else if (dlat < -90)
5132 dlat = -90;
5133
5134 if (dlon > 360.) dlon -= 360.;
5135 if (dlon < -360.) dlon += 360.;
5136
5137 // This should not really be necessary, but round-trip georef on some
5138 // charts is not perfect, So we can get creep on repeated unidimensional
5139 // pans, and corrupt chart cacheing.......
5140
5141 // But this only works on north-up projections
5142 // TODO: can we remove this now?
5143 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5144 // .001 ) ) {
5145 //
5146 // if( dx == 0 ) dlon = clon;
5147 // if( dy == 0 ) dlat = clat;
5148 // }
5149
5150 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5151
5152 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5153
5154 if (VPoint.b_quilt) {
5155 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5156 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5157 // Tweak the scale slightly for a new ref chart
5158 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5159 if (pc) {
5160 double tweak_scale_ppm =
5161 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5162 SetVPScale(tweak_scale_ppm);
5163 }
5164 }
5165
5166 if (new_ref_dbIndex == -1) {
5167#pragma GCC diagnostic push
5168#pragma GCC diagnostic ignored "-Warray-bounds"
5169 // The compiler sees a -1 index being used. Does not happen, though.
5170
5171 // for whatever reason, no reference chart is known
5172 // Probably panned out of the coverage region
5173 // If any charts are anywhere on-screen, choose the smallest
5174 // scale chart on the screen to be a new reference chart.
5175 int trial_index = -1;
5176 if (m_pCurrentStack->nEntry) {
5177 int trial_index =
5178 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5179 }
5180
5181 if (trial_index < 0) {
5182 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5183 if (full_screen_array.size())
5184 trial_index = full_screen_array[full_screen_array.size() - 1];
5185 }
5186
5187 if (trial_index >= 0) {
5188 m_pQuilt->SetReferenceChart(trial_index);
5189 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5190 VPoint.rotation);
5191 ReloadVP();
5192 }
5193#pragma GCC diagnostic pop
5194 }
5195 }
5196
5197 // Turn off bFollow only if the ownship has left the screen
5198 if (m_bFollow) {
5199 double offx, offy;
5200 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5201
5202 double offset_angle = atan2(offy, offx);
5203 double offset_distance = sqrt((offy * offy) + (offx * offx));
5204 double chart_angle = GetVPRotation();
5205 double target_angle = chart_angle - offset_angle;
5206 double d_east_mod = offset_distance * cos(target_angle);
5207 double d_north_mod = offset_distance * sin(target_angle);
5208
5209 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5210 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5211
5212 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5213 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5214 m_bFollow = false; // update the follow flag
5215 UpdateFollowButtonState();
5216 }
5217 }
5218
5219 Refresh(false);
5220
5221 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5222
5223 return true;
5224}
5225
5226bool ChartCanvas::IsOwnshipOnScreen() {
5227 wxPoint r;
5229 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5230 ((r.y > 0) && r.y < GetCanvasHeight()))
5231 return true;
5232 else
5233 return false;
5234}
5235
5236void ChartCanvas::ReloadVP(bool b_adjust) {
5237 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5238
5239 LoadVP(VPoint, b_adjust);
5240}
5241
5242void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5243#ifdef ocpnUSE_GL
5244 if (g_bopengl && m_glcc) {
5245 m_glcc->Invalidate();
5246 if (m_glcc->GetSize() != GetSize()) {
5247 m_glcc->SetSize(GetSize());
5248 }
5249 } else
5250#endif
5251 {
5252 m_cache_vp.Invalidate();
5253 m_bm_cache_vp.Invalidate();
5254 }
5255
5256 VPoint.Invalidate();
5257
5258 if (m_pQuilt) m_pQuilt->Invalidate();
5259
5260 // Make sure that the Selected Group is sensible...
5261 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5262 // m_groupIndex = 0;
5263 // if( !CheckGroup( m_groupIndex ) )
5264 // m_groupIndex = 0;
5265
5266 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5267 vp.m_projection_type, b_adjust);
5268}
5269
5270void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5271 m_pQuilt->SetReferenceChart(dbIndex);
5272 VPoint.Invalidate();
5273 m_pQuilt->Invalidate();
5274}
5275
5276double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5277 if (m_pQuilt)
5278 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5279 else
5280 return vp.view_scale_ppm;
5281}
5282
5283// Verify and adjust the current reference chart,
5284// so that it will not lead to excessive overzoom or underzoom onscreen
5285int ChartCanvas::AdjustQuiltRefChart() {
5286 int ret = -1;
5287 if (m_pQuilt) {
5288 wxASSERT(ChartData);
5289 ChartBase *pc =
5290 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5291 if (pc) {
5292 double min_ref_scale =
5293 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5294 double max_ref_scale =
5295 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5296
5297 if (VPoint.chart_scale < min_ref_scale) {
5298 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5299 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5300 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5301 } else {
5302 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5303
5304 if (!brender_ok) {
5305 int target_stack_index = wxNOT_FOUND;
5306 int il = 0;
5307 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5308 if (index == m_pQuilt->GetRefChartdbIndex()) {
5309 target_stack_index = il;
5310 break;
5311 }
5312 il++;
5313 }
5314 if (wxNOT_FOUND == target_stack_index) // should never happen...
5315 target_stack_index = 0;
5316
5317 int ref_family = pc->GetChartFamily();
5318 int extended_array_count =
5319 m_pQuilt->GetExtendedStackIndexArray().size();
5320 while ((!brender_ok) &&
5321 ((int)target_stack_index < (extended_array_count - 1))) {
5322 target_stack_index++;
5323 int test_db_index =
5324 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5325
5326 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5327 IsChartQuiltableRef(test_db_index)) {
5328 // open the target, and check the min_scale
5329 ChartBase *ptest_chart =
5330 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5331 if (ptest_chart) {
5332 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5333 }
5334 }
5335 }
5336
5337 if (brender_ok) { // found a better reference chart
5338 int new_db_index =
5339 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5340 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5341 IsChartQuiltableRef(new_db_index)) {
5342 m_pQuilt->SetReferenceChart(new_db_index);
5343 ret = new_db_index;
5344 } else
5345 ret = m_pQuilt->GetRefChartdbIndex();
5346 } else
5347 ret = m_pQuilt->GetRefChartdbIndex();
5348
5349 } else
5350 ret = m_pQuilt->GetRefChartdbIndex();
5351 }
5352 } else
5353 ret = -1;
5354 }
5355
5356 return ret;
5357}
5358
5359void ChartCanvas::UpdateCanvasOnGroupChange() {
5360 delete m_pCurrentStack;
5361 m_pCurrentStack = new ChartStack;
5362 wxASSERT(ChartData);
5363 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5364 m_groupIndex);
5365
5366 if (m_pQuilt) {
5367 m_pQuilt->Compose(VPoint);
5368 SetFocus();
5369 }
5370}
5371
5372bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5373 double latNE, double lonNE) {
5374 // Center Point
5375 double latc = (latSW + latNE) / 2.0;
5376 double lonc = (lonSW + lonNE) / 2.0;
5377
5378 // Get scale in ppm (latitude)
5379 double ne_easting, ne_northing;
5380 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5381
5382 double sw_easting, sw_northing;
5383 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5384
5385 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5386
5387 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5388}
5389
5390bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5391 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5392 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5393}
5394
5395bool ChartCanvas::SetVPProjection(int projection) {
5396 if (!g_bopengl) // alternative projections require opengl
5397 return false;
5398
5399 // the view scale varies depending on geographic location and projection
5400 // rescale to keep the relative scale on the screen the same
5401 double prev_true_scale_ppm = m_true_scale_ppm;
5402 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5403 VPoint.skew, VPoint.rotation, projection) &&
5404 SetVPScale(wxMax(
5405 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5406 m_absolute_min_scale_ppm));
5407}
5408
5409bool ChartCanvas::SetViewPoint(double lat, double lon) {
5410 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5411 VPoint.rotation);
5412}
5413
5414bool ChartCanvas::SetVPRotation(double angle) {
5415 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5416 VPoint.skew, angle);
5417}
5418bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5419 double skew, double rotation, int projection,
5420 bool b_adjust, bool b_refresh) {
5421 if (ChartData->IsBusy()) return false;
5422 bool b_ret = false;
5423 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5424 skew -= 2 * PI;
5425 // Any sensible change?
5426 if (VPoint.IsValid()) {
5427 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5428 (fabs(VPoint.skew - skew) < 1e-9) &&
5429 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5430 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5431 (VPoint.m_projection_type == projection ||
5432 projection == PROJECTION_UNKNOWN))
5433 return false;
5434 }
5435 if (VPoint.m_projection_type != projection)
5436 VPoint.InvalidateTransformCache(); // invalidate
5437
5438 // Take a local copy of the last viewport
5439 ViewPort last_vp = VPoint;
5440
5441 VPoint.skew = skew;
5442 VPoint.clat = lat;
5443 VPoint.clon = lon;
5444 VPoint.rotation = rotation;
5445 VPoint.view_scale_ppm = scale_ppm;
5446 if (projection != PROJECTION_UNKNOWN)
5447 VPoint.SetProjectionType(projection);
5448 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5449 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5450
5451 // don't allow latitude above 88 for mercator (90 is infinity)
5452 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5453 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5454 if (VPoint.clat > 89.5)
5455 VPoint.clat = 89.5;
5456 else if (VPoint.clat < -89.5)
5457 VPoint.clat = -89.5;
5458 }
5459
5460 // don't zoom out too far for transverse mercator polyconic until we resolve
5461 // issues
5462 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5463 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5464 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5465
5466 // SetVPRotation(rotation);
5467
5468 if (!g_bopengl) // tilt is not possible without opengl
5469 VPoint.tilt = 0;
5470
5471 if ((VPoint.pix_width <= 0) ||
5472 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5473 return false;
5474
5475 bool bwasValid = VPoint.IsValid();
5476 VPoint.Validate(); // Mark this ViewPoint as OK
5477
5478 // Has the Viewport scale changed? If so, invalidate the vp
5479 if (last_vp.view_scale_ppm != scale_ppm) {
5480 m_cache_vp.Invalidate();
5481 InvalidateGL();
5482 }
5483
5484 // A preliminary value, may be tweaked below
5485 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5486
5487 // recompute cursor position
5488 // and send to interested plugins if the mouse is actually in this window
5489 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5490 int mouseX = mouse_x;
5491 int mouseY = mouse_y;
5492 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5493 (mouseY < VPoint.pix_height)) {
5494 double lat_mouse, lon_mouse;
5495 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5496 m_cursor_lat = lat_mouse;
5497 m_cursor_lon = lon_mouse;
5498 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5499 }
5500 }
5501
5502 if (!VPoint.b_quilt && m_singleChart) {
5503 VPoint.SetBoxes();
5504
5505 // Allow the chart to adjust the new ViewPort for performance optimization
5506 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5507 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5508
5509 // If there is a sensible change in the chart render, refresh the whole
5510 // screen
5511 if ((!m_cache_vp.IsValid()) ||
5512 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5513 Refresh(false);
5514 b_ret = true;
5515 } else {
5516 wxPoint cp_last, cp_this;
5517 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5518 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5519
5520 if (cp_last != cp_this) {
5521 Refresh(false);
5522 b_ret = true;
5523 }
5524 }
5525 // Create the stack
5526 if (m_pCurrentStack) {
5527 assert(ChartData != 0);
5528 int current_db_index;
5529 current_db_index =
5530 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5531
5532 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5533 m_groupIndex);
5534 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5535 }
5536
5537 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5538 }
5539
5540 // Handle the quilted case
5541 if (VPoint.b_quilt) {
5542 VPoint.SetBoxes();
5543
5544 if (last_vp.view_scale_ppm != scale_ppm)
5545 m_pQuilt->InvalidateAllQuiltPatchs();
5546
5547 // Create the quilt
5548 if (ChartData /*&& ChartData->IsValid()*/) {
5549 if (!m_pCurrentStack) return false;
5550
5551 int current_db_index;
5552 current_db_index =
5553 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5554
5555 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5556 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5557
5558 // Check to see if the current quilt reference chart is in the new stack
5559 int current_ref_stack_index = -1;
5560 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5561 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5562 current_ref_stack_index = i;
5563 }
5564
5565 if (g_bFullScreenQuilt) {
5566 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5567 }
5568
5569 // We might need a new Reference Chart
5570 bool b_needNewRef = false;
5571
5572 // If the new stack does not contain the current ref chart....
5573 if ((-1 == current_ref_stack_index) &&
5574 (m_pQuilt->GetRefChartdbIndex() >= 0))
5575 b_needNewRef = true;
5576
5577 // Would the current Ref Chart be excessively underzoomed?
5578 // We need to check this here to be sure, since we cannot know where the
5579 // reference chart was assigned. For instance, the reference chart may
5580 // have been selected from the config file, or from a long jump with a
5581 // chart family switch implicit. Anyway, we check to be sure....
5582 bool renderable = true;
5583 ChartBase *referenceChart =
5584 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5585 if (referenceChart) {
5586 double chartMaxScale = referenceChart->GetNormalScaleMax(
5587 GetCanvasScaleFactor(), GetCanvasWidth());
5588 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5589 }
5590 if (!renderable) b_needNewRef = true;
5591
5592 // Need new refchart?
5593 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5594 const ChartTableEntry &cte_ref =
5595 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5596 int target_scale = cte_ref.GetScale();
5597 int target_type = cte_ref.GetChartType();
5598 int candidate_stack_index;
5599
5600 // reset the ref chart in a way that does not lead to excessive
5601 // underzoom, for performance reasons Try to find a chart that is the
5602 // same type, and has a scale of just smaller than the current ref
5603 // chart
5604
5605 candidate_stack_index = 0;
5606 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5607 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5608 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5609 int candidate_scale = cte_candidate.GetScale();
5610 int candidate_type = cte_candidate.GetChartType();
5611
5612 if ((candidate_scale >= target_scale) &&
5613 (candidate_type == target_type)) {
5614 bool renderable = true;
5615 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5616 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5617 if (tentative_referenceChart) {
5618 double chartMaxScale =
5619 tentative_referenceChart->GetNormalScaleMax(
5620 GetCanvasScaleFactor(), GetCanvasWidth());
5621 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5622 }
5623
5624 if (renderable) break;
5625 }
5626
5627 candidate_stack_index++;
5628 }
5629
5630 // If that did not work, look for a chart of just larger scale and
5631 // same type
5632 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5633 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5634 while (candidate_stack_index >= 0) {
5635 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5636 if (idx >= 0) {
5637 const ChartTableEntry &cte_candidate =
5638 ChartData->GetChartTableEntry(idx);
5639 int candidate_scale = cte_candidate.GetScale();
5640 int candidate_type = cte_candidate.GetChartType();
5641
5642 if ((candidate_scale <= target_scale) &&
5643 (candidate_type == target_type))
5644 break;
5645 }
5646 candidate_stack_index--;
5647 }
5648 }
5649
5650 // and if that did not work, chose stack entry 0
5651 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5652 (candidate_stack_index < 0))
5653 candidate_stack_index = 0;
5654
5655 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5656
5657 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5658 }
5659
5660 if (!g_bopengl) {
5661 // Preset the VPoint projection type to match what the quilt projection
5662 // type will be
5663 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5664
5665 // Always keep the default Mercator projection if the reference chart is
5666 // not in the PatchList or the scale is too small for it to render.
5667
5668 bool renderable = true;
5669 ChartBase *referenceChart =
5670 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5671 if (referenceChart) {
5672 double chartMaxScale = referenceChart->GetNormalScaleMax(
5673 GetCanvasScaleFactor(), GetCanvasWidth());
5674 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5675 proj = ChartData->GetDBChartProj(ref_db_index);
5676 } else
5677 proj = PROJECTION_MERCATOR;
5678
5679 VPoint.b_MercatorProjectionOverride =
5680 (m_pQuilt->GetnCharts() == 0 || !renderable);
5681
5682 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5683
5684 VPoint.SetProjectionType(proj);
5685 }
5686
5687 // If this quilt will be a perceptible delta from the existing quilt,
5688 // then refresh the entire screen
5689 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5690 // Allow the quilt to adjust the new ViewPort for performance
5691 // optimization This will normally be only a fractional (i.e.
5692 // sub-pixel) adjustment...
5693 if (b_adjust) {
5694 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5695 }
5696
5697 // ChartData->ClearCacheInUseFlags();
5698 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5699
5700 // wxStopWatch sw;
5701
5702#ifdef __ANDROID__
5703 // This is an optimization for panning on touch screen systems.
5704 // The quilt composition is deferred until the OnPaint() message gets
5705 // finally removed and processed from the message queue.
5706 // Takes advantage of the fact that touch-screen pan gestures are
5707 // usually short in distance,
5708 // so not requiring a full quilt rebuild until the pan gesture is
5709 // complete.
5710 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5711 // qDebug() << "Force compose";
5712 m_pQuilt->Compose(VPoint);
5713 } else {
5714 m_pQuilt->Invalidate();
5715 }
5716#else
5717 m_pQuilt->Compose(VPoint);
5718#endif
5719
5720 // printf("comp time %ld\n", sw.Time());
5721
5722 // If the extended chart stack has changed, invalidate any cached
5723 // render bitmap
5724 // if(m_pQuilt->GetXStackHash() != hash1) {
5725 // m_bm_cache_vp.Invalidate();
5726 // InvalidateGL();
5727 // }
5728
5729 ChartData->PurgeCacheUnusedCharts(0.7);
5730
5731 if (b_refresh) Refresh(false);
5732
5733 b_ret = true;
5734 }
5735 }
5736
5737 VPoint.skew = 0.; // Quilting supports 0 Skew
5738 } else if (!g_bopengl) {
5739 OcpnProjType projection = PROJECTION_UNKNOWN;
5740 if (m_singleChart) // viewport projection must match chart projection
5741 // without opengl
5742 projection = m_singleChart->GetChartProjectionType();
5743 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5744 VPoint.SetProjectionType(projection);
5745 }
5746
5747 // Has the Viewport projection changed? If so, invalidate the vp
5748 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5749 m_cache_vp.Invalidate();
5750 InvalidateGL();
5751 }
5752
5753 UpdateCanvasControlBar(); // Refresh the Piano
5754
5755 VPoint.chart_scale = 1.0; // fallback default value
5756
5757 if (VPoint.GetBBox().GetValid()) {
5758 // Update the viewpoint reference scale
5759 if (m_singleChart)
5760 VPoint.ref_scale = m_singleChart->GetNativeScale();
5761 else {
5762#ifdef __ANDROID__
5763 // This is an optimization for panning on touch screen systems.
5764 // See above.
5765 // Quilt might not be fully composed at this point, so for cm93
5766 // the reference scale may not be known.
5767 // In this case, do not update the VP ref_scale.
5768 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5769 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5770 }
5771#else
5772 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5773#endif
5774 }
5775
5776 // Calculate the on-screen displayed actual scale
5777 // by a simple traverse northward from the center point
5778 // of roughly one eighth of the canvas height
5779 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5780
5781 double delta_check =
5782 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5783 delta_check /= 8.;
5784
5785 double check_point = wxMin(89., VPoint.clat);
5786
5787 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5788
5789 double rhumbDist;
5790 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5791 VPoint.clon, 0, &rhumbDist);
5792
5793 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5794 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5795 // Calculate the distance between r1 and r in physical pixels.
5796 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5797 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5798
5799 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5800
5801 // A fall back in case of very high zoom-out, giving delta_y == 0
5802 // which can probably only happen with vector charts
5803 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5804
5805 // Another fallback, for highly zoomed out charts
5806 // This adjustment makes the displayed TrueScale correspond to the
5807 // same algorithm used to calculate the chart zoom-out limit for
5808 // ChartDummy.
5809 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5810
5811 if (m_true_scale_ppm)
5812 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5813 else
5814 VPoint.chart_scale = 1.0;
5815
5816 // Create a nice renderable string
5817 double round_factor = 1000.;
5818 if (VPoint.chart_scale <= 1000.)
5819 round_factor = 10.;
5820 else if (VPoint.chart_scale <= 10000.)
5821 round_factor = 100.;
5822 else if (VPoint.chart_scale <= 100000.)
5823 round_factor = 1000.;
5824
5825 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5826 double retina_coef = 1;
5827#ifdef ocpnUSE_GL
5828#ifdef __WXOSX__
5829 if (g_bopengl) {
5830 retina_coef = GetContentScaleFactor();
5831 }
5832#endif
5833#endif
5834
5835 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5836 // rounded to the nearest 10, 100 or 1000.
5837 //
5838 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5839 // true_scale_display. That does not make sense. The chart scale should be
5840 // the same as the true scale within the limits of the rounding factor.
5841 double true_scale_display =
5842 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5843 wxString text;
5844
5845 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5846
5847 if (m_displayed_scale_factor > 10.0)
5848 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5849 m_displayed_scale_factor);
5850 else if (m_displayed_scale_factor > 1.0)
5851 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5852 m_displayed_scale_factor);
5853 else if (m_displayed_scale_factor > 0.1) {
5854 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5855 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5856 } else if (m_displayed_scale_factor > 0.01) {
5857 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5858 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5859 } else {
5860 text.Printf(
5861 "%s %4.0f (---)", _("Scale"),
5862 true_scale_display); // Generally, no chart, so no chart scale factor
5863 }
5864
5865 m_scaleValue = true_scale_display;
5866 m_scaleText = text;
5867 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5868
5869 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5870 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5871 STAT_FIELD_SCALE)) {
5872 // Check to see if the text will fit in the StatusBar field...
5873 bool b_noshow = false;
5874 {
5875 int w = 0;
5876 int h;
5877 wxClientDC dc(top_frame::Get()->GetStatusBar());
5878 if (dc.IsOk()) {
5879 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5880 dc.SetFont(*templateFont);
5881 dc.GetTextExtent(text, &w, &h);
5882
5883 // If text is too long for the allocated field, try to reduce the text
5884 // string a bit.
5885 wxRect rect;
5886 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5887 rect);
5888 if (w && w > rect.width) {
5889 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5890 }
5891
5892 // Test again...if too big still, then give it up.
5893 dc.GetTextExtent(text, &w, &h);
5894
5895 if (w && w > rect.width) {
5896 b_noshow = true;
5897 }
5898 }
5899 }
5900
5901 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5902 }
5903 }
5904
5905 // Maintain member vLat/vLon
5906 m_vLat = VPoint.clat;
5907 m_vLon = VPoint.clon;
5908
5909 return b_ret;
5910}
5911
5912// Static Icon definitions for some symbols requiring
5913// scaling/rotation/translation Very specific wxDC draw commands are
5914// necessary to properly render these icons...See the code in
5915// ShipDraw()
5916
5917// This icon was adapted and scaled from the S52 Presentation Library
5918// version 3_03.
5919// Symbol VECGND02
5920
5921static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5922
5923// This ownship icon was adapted and scaled from the S52 Presentation
5924// Library version 3_03 Symbol OWNSHP05
5925static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5926 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5927
5928wxColour ChartCanvas::PredColor() {
5929 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5930 // visibility.
5931 if (SHIP_NORMAL == m_ownship_state)
5932 return GetGlobalColor("URED");
5933
5934 else if (SHIP_LOWACCURACY == m_ownship_state)
5935 return GetGlobalColor("YELO1");
5936
5937 return GetGlobalColor("NODTA");
5938}
5939
5940wxColour ChartCanvas::ShipColor() {
5941 // Establish ship color
5942 // It changes color based on GPS and Chart accuracy/availability
5943
5944 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5945
5946 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5947
5948 return GetGlobalColor("URED"); // default is OK
5949}
5950
5951void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5952 wxPoint2DDouble lShipMidPoint) {
5953 dc.SetPen(wxPen(PredColor(), 2));
5954
5955 if (SHIP_NORMAL == m_ownship_state)
5956 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5957 else
5958 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5959
5960 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5961 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5962
5963 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5964 lShipMidPoint.m_y);
5965 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5966 lShipMidPoint.m_y + 12);
5967}
5968
5969void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5970 wxPoint GPSOffsetPixels,
5971 wxPoint2DDouble lGPSPoint) {
5972 // if (m_animationActive) return;
5973 // Develop a uniform length for course predictor line dash length, based on
5974 // physical display size Use this reference length to size all other graphics
5975 // elements
5976 float ref_dim = m_display_size_mm / 24;
5977 ref_dim = wxMin(ref_dim, 12);
5978 ref_dim = wxMax(ref_dim, 6);
5979
5980 wxColour cPred;
5981 cPred.Set(g_cog_predictor_color);
5982 if (cPred == wxNullColour) cPred = PredColor();
5983
5984 // Establish some graphic element line widths dependent on the platform
5985 // display resolution
5986 // double nominal_line_width_pix = wxMax(1.0,
5987 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5988 // not less than 1 pixel
5989 double nominal_line_width_pix = wxMax(
5990 1.0,
5991 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5992
5993 // If the calculated value is greater than the config file spec value, then
5994 // use it.
5995 if (nominal_line_width_pix > g_cog_predictor_width)
5996 g_cog_predictor_width = nominal_line_width_pix;
5997
5998 // Calculate ownship Position Predictor
5999 wxPoint lPredPoint, lHeadPoint;
6000
6001 float pCog = std::isnan(gCog) ? 0 : gCog;
6002 float pSog = std::isnan(gSog) ? 0 : gSog;
6003
6004 double pred_lat, pred_lon;
6005 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
6006 &pred_lat, &pred_lon);
6007 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
6008
6009 // test to catch the case where COG/HDG line crosses the screen
6010 LLBBox box;
6011
6012 // Should we draw the Head vector?
6013 // Compare the points lHeadPoint and lPredPoint
6014 // If they differ by more than n pixels, and the head vector is valid, then
6015 // render the head vector
6016
6017 float ndelta_pix = 10.;
6018 double hdg_pred_lat, hdg_pred_lon;
6019 bool b_render_hdt = false;
6020 if (!std::isnan(gHdt)) {
6021 // Calculate ownship Heading pointer as a predictor
6022 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6023 &hdg_pred_lon);
6024 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6025 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6026 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6027 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6028 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6029 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6030 }
6031 }
6032
6033 // draw course over ground if they are longer than the ship
6034 wxPoint lShipMidPoint;
6035 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6036 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6037 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6038 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6039
6040 if (lpp >= img_height / 2) {
6041 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6042 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6043 !std::isnan(gSog)) {
6044 // COG Predictor
6045 float dash_length = ref_dim;
6046 wxDash dash_long[2];
6047 dash_long[0] =
6048 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6049 g_cog_predictor_width); // Long dash , in mm <---------+
6050 dash_long[1] = dash_long[0] / 2.0; // Short gap
6051
6052 // On ultra-hi-res displays, do not allow the dashes to be greater than
6053 // 250, since it is defined as (char)
6054 if (dash_length > 250.) {
6055 dash_long[0] = 250. / g_cog_predictor_width;
6056 dash_long[1] = dash_long[0] / 2;
6057 }
6058
6059 wxPen ppPen2(cPred, g_cog_predictor_width,
6060 (wxPenStyle)g_cog_predictor_style);
6061 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6062 ppPen2.SetDashes(2, dash_long);
6063 dc.SetPen(ppPen2);
6064 dc.StrokeLine(
6065 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6066 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6067
6068 if (g_cog_predictor_width > 1) {
6069 float line_width = g_cog_predictor_width / 3.;
6070
6071 wxDash dash_long3[2];
6072 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6073 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6074
6075 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6076 (wxPenStyle)g_cog_predictor_style);
6077 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6078 ppPen3.SetDashes(2, dash_long3);
6079 dc.SetPen(ppPen3);
6080 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6081 lGPSPoint.m_y + GPSOffsetPixels.y,
6082 lPredPoint.x + GPSOffsetPixels.x,
6083 lPredPoint.y + GPSOffsetPixels.y);
6084 }
6085
6086 if (g_cog_predictor_endmarker) {
6087 // Prepare COG predictor endpoint icon
6088 double png_pred_icon_scale_factor = .4;
6089 if (g_ShipScaleFactorExp > 1.0)
6090 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6091 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6092
6093 wxPoint icon[4];
6094
6095 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6096 (float)(lPredPoint.x - lShipMidPoint.x));
6097 cog_rad += (float)PI;
6098
6099 for (int i = 0; i < 4; i++) {
6100 int j = i * 2;
6101 double pxa = (double)(s_png_pred_icon[j]);
6102 double pya = (double)(s_png_pred_icon[j + 1]);
6103
6104 pya *= png_pred_icon_scale_factor;
6105 pxa *= png_pred_icon_scale_factor;
6106
6107 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6108 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6109
6110 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6111 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6112 }
6113
6114 // Render COG endpoint icon
6115 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6116 wxPENSTYLE_SOLID);
6117 dc.SetPen(ppPen1);
6118 dc.SetBrush(wxBrush(cPred));
6119
6120 dc.StrokePolygon(4, icon);
6121 }
6122 }
6123 }
6124
6125 // HDT Predictor
6126 if (b_render_hdt) {
6127 float hdt_dash_length = ref_dim * 0.4;
6128
6129 cPred.Set(g_ownship_HDTpredictor_color);
6130 if (cPred == wxNullColour) cPred = PredColor();
6131 float hdt_width =
6132 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6133 : g_cog_predictor_width * 0.8);
6134 wxDash dash_short[2];
6135 dash_short[0] =
6136 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6137 hdt_width); // Short dash , in mm <---------+
6138 dash_short[1] =
6139 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6140 hdt_width); // Short gap |
6141
6142 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6143 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6144 ppPen2.SetDashes(2, dash_short);
6145
6146 dc.SetPen(ppPen2);
6147 dc.StrokeLine(
6148 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6149 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6150
6151 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6152 dc.SetPen(ppPen1);
6153 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6154
6155 if (g_ownship_HDTpredictor_endmarker) {
6156 double nominal_circle_size_pixels = wxMax(
6157 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6158
6159 // Scale the circle to ChartScaleFactor, slightly softened....
6160 if (g_ShipScaleFactorExp > 1.0)
6161 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6162
6163 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6164 lHeadPoint.y + GPSOffsetPixels.y,
6165 nominal_circle_size_pixels / 2);
6166 }
6167 }
6168
6169 // Draw radar rings if activated
6170 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6171 double factor = 1.00;
6172 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6173 factor = 1 / 1.852;
6174 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6175 if (std::isnan(gSog))
6176 factor = 0.0;
6177 else
6178 factor = gSog / 60;
6179 }
6180 factor *= g_fNavAidRadarRingsStep;
6181
6182 double tlat, tlon;
6183 wxPoint r;
6184 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6185 GetCanvasPointPix(tlat, tlon, &r);
6186
6187 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6188 pow((double)(lGPSPoint.m_y - r.y), 2));
6189 int pix_radius = (int)lpp;
6190
6191 wxColor rangeringcolour =
6192 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6193
6194 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6195
6196 dc.SetPen(ppPen1);
6197 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6198
6199 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6200 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6201 }
6202}
6203
6204#if ocpnUSE_GL
6205void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6206bool ChartCanvas::CanAccelerateGlPanning() {
6207 return GetglCanvas()->CanAcceleratePanning();
6208}
6209void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6210
6211#else
6212void ChartCanvas::ResetGlGridFont() {}
6213bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6214void ChartCanvas::SetupGlCompression() {}
6215#endif
6216
6217void ChartCanvas::ComputeShipScaleFactor(
6218 float icon_hdt, int ownShipWidth, int ownShipLength,
6219 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6220 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6221 float screenResolution = m_pix_per_mm;
6222
6223 // Calculate the true ship length in exact pixels
6224 double ship_bow_lat, ship_bow_lon;
6225 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6226 &ship_bow_lat, &ship_bow_lon);
6227 wxPoint lShipBowPoint;
6228 wxPoint2DDouble b_point =
6229 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6230 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6231
6232 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6233 powf((float)(b_point.m_y - a_point.m_y), 2));
6234
6235 // And in mm
6236 float shipLength_mm = shipLength_px / screenResolution;
6237
6238 // Set minimum ownship drawing size
6239 float ownship_min_mm = g_n_ownship_min_mm;
6240 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6241
6242 // Calculate Nautical Miles distance from midships to gps antenna
6243 float hdt_ant = icon_hdt + 180.;
6244 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6245 float dx = g_n_gps_antenna_offset_x / 1852.;
6246 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6247 {
6248 hdt_ant = icon_hdt;
6249 dy = -dy;
6250 }
6251
6252 // If the drawn ship size is going to be clamped, adjust the gps antenna
6253 // offsets
6254 if (shipLength_mm < ownship_min_mm) {
6255 dy /= shipLength_mm / ownship_min_mm;
6256 dx /= shipLength_mm / ownship_min_mm;
6257 }
6258
6259 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6260
6261 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6262 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6263 &ship_mid_lon1);
6264
6265 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6266 &lShipMidPoint);
6267
6268 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6269 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6270
6271 float scale_factor = shipLength_px / ownShipLength;
6272
6273 // Calculate a scale factor that would produce a reasonably sized icon
6274 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6275
6276 // And choose the correct one
6277 scale_factor = wxMax(scale_factor, scale_factor_min);
6278
6279 scale_factor_y = scale_factor;
6280 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6281 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6282}
6283
6284void ChartCanvas::ShipDraw(ocpnDC &dc) {
6285 if (!GetVP().IsValid()) return;
6286
6287 wxPoint GPSOffsetPixels(0, 0);
6288 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6289
6290 // COG/SOG may be undefined in NMEA data stream
6291 float pCog = std::isnan(gCog) ? 0 : gCog;
6292 float pSog = std::isnan(gSog) ? 0 : gSog;
6293
6294 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6295
6296 lShipMidPoint = lGPSPoint;
6297
6298 // Draw the icon rotated to the COG
6299 // or to the Hdt if available
6300 float icon_hdt = pCog;
6301 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6302
6303 // COG may be undefined in NMEA data stream
6304 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6305
6306 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6307 // predictor
6308 double osd_head_lat, osd_head_lon;
6309 wxPoint osd_head_point;
6310
6311 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6312 &osd_head_lon);
6313
6314 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6315
6316 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6317 (float)(osd_head_point.x - lShipMidPoint.m_x));
6318 icon_rad += (float)PI;
6319
6320 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6321
6322 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6323 // nominal size and is just barely outside the viewport ....
6324 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6325
6326 // TODO: fix to include actual size of boat that will be rendered
6327 int img_height = 0;
6328 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6329 if (GetVP().chart_scale >
6330 300000) // According to S52, this should be 50,000
6331 {
6332 ShipDrawLargeScale(dc, lShipMidPoint);
6333 img_height = 20;
6334 } else {
6335 wxImage pos_image;
6336
6337 // Substitute user ownship image if found
6338 if (m_pos_image_user)
6339 pos_image = m_pos_image_user->Copy();
6340 else if (SHIP_NORMAL == m_ownship_state)
6341 pos_image = m_pos_image_red->Copy();
6342 if (SHIP_LOWACCURACY == m_ownship_state)
6343 pos_image = m_pos_image_yellow->Copy();
6344 else if (SHIP_NORMAL != m_ownship_state)
6345 pos_image = m_pos_image_grey->Copy();
6346
6347 // Substitute user ownship image if found
6348 if (m_pos_image_user) {
6349 pos_image = m_pos_image_user->Copy();
6350
6351 if (SHIP_LOWACCURACY == m_ownship_state)
6352 pos_image = m_pos_image_user_yellow->Copy();
6353 else if (SHIP_NORMAL != m_ownship_state)
6354 pos_image = m_pos_image_user_grey->Copy();
6355 }
6356
6357 img_height = pos_image.GetHeight();
6358
6359 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6360 g_OwnShipIconType > 0) // use large ship
6361 {
6362 int ownShipWidth = 22; // Default values from s_ownship_icon
6363 int ownShipLength = 84;
6364 if (g_OwnShipIconType == 1) {
6365 ownShipWidth = pos_image.GetWidth();
6366 ownShipLength = pos_image.GetHeight();
6367 }
6368
6369 float scale_factor_x, scale_factor_y;
6370 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6371 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6372 scale_factor_x, scale_factor_y);
6373
6374 if (g_OwnShipIconType == 1) { // Scaled bitmap
6375 pos_image.Rescale(ownShipWidth * scale_factor_x,
6376 ownShipLength * scale_factor_y,
6377 wxIMAGE_QUALITY_HIGH);
6378 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6379 wxImage rot_image =
6380 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6381
6382 // Simple sharpening algorithm.....
6383 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6384 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6385 if (rot_image.GetAlpha(ip, jp) > 64)
6386 rot_image.SetAlpha(ip, jp, 255);
6387
6388 wxBitmap os_bm(rot_image);
6389
6390 int w = os_bm.GetWidth();
6391 int h = os_bm.GetHeight();
6392 img_height = h;
6393
6394 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6395 lShipMidPoint.m_y - h / 2, true);
6396
6397 // Maintain dirty box,, missing in __WXMSW__ library
6398 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6399 lShipMidPoint.m_y - h / 2);
6400 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6401 lShipMidPoint.m_y - h / 2 + h);
6402 }
6403
6404 else if (g_OwnShipIconType == 2) { // Scaled Vector
6405 wxPoint ownship_icon[10];
6406
6407 for (int i = 0; i < 10; i++) {
6408 int j = i * 2;
6409 float pxa = (float)(s_ownship_icon[j]);
6410 float pya = (float)(s_ownship_icon[j + 1]);
6411 pya *= scale_factor_y;
6412 pxa *= scale_factor_x;
6413
6414 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6415 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6416
6417 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6418 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6419 }
6420
6421 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6422 dc.SetPen(ppPen1);
6423 dc.SetBrush(wxBrush(ShipColor()));
6424
6425 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6426
6427 // draw reference point (midships) cross
6428 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6429 ownship_icon[7].y);
6430 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6431 ownship_icon[9].y);
6432 }
6433
6434 img_height = ownShipLength * scale_factor_y;
6435
6436 // Reference point, where the GPS antenna is
6437 int circle_rad = 3;
6438 if (m_pos_image_user) circle_rad = 1;
6439
6440 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6441 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6442 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6443 } else { // Fixed bitmap icon.
6444 /* non opengl, or suboptimal opengl via ocpndc: */
6445 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6446 wxImage rot_image =
6447 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6448
6449 // Simple sharpening algorithm.....
6450 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6451 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6452 if (rot_image.GetAlpha(ip, jp) > 64)
6453 rot_image.SetAlpha(ip, jp, 255);
6454
6455 wxBitmap os_bm(rot_image);
6456
6457 if (g_ShipScaleFactorExp > 1) {
6458 wxImage scaled_image = os_bm.ConvertToImage();
6459 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6460 1.0; // soften the scale factor a bit
6461 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6462 scaled_image.GetHeight() * factor,
6463 wxIMAGE_QUALITY_HIGH));
6464 }
6465 int w = os_bm.GetWidth();
6466 int h = os_bm.GetHeight();
6467 img_height = h;
6468
6469 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6470 lShipMidPoint.m_y - h / 2, true);
6471
6472 // Reference point, where the GPS antenna is
6473 int circle_rad = 3;
6474 if (m_pos_image_user) circle_rad = 1;
6475
6476 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6477 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6478 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6479
6480 // Maintain dirty box,, missing in __WXMSW__ library
6481 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6482 lShipMidPoint.m_y - h / 2);
6483 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6484 lShipMidPoint.m_y - h / 2 + h);
6485 }
6486 } // ownship draw
6487 }
6488
6489 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6490}
6491
6492/* @ChartCanvas::CalcGridSpacing
6493 **
6494 ** Calculate the major and minor spacing between the lat/lon grid
6495 **
6496 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6497 *window
6498 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6499 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6500 ** @return [void]
6501 */
6502void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6503 float &MinorSpacing) {
6504 // table for calculating the distance between the grids
6505 // [0] view_scale ppm
6506 // [1] spacing between major grid lines in degrees
6507 // [2] spacing between minor grid lines in degrees
6508 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6509 {.000001f, 45.0f, 15.0f},
6510 {.0002f, 30.0f, 10.0f},
6511 {.0003f, 10.0f, 2.0f},
6512 {.0008f, 5.0f, 1.0f},
6513 {.001f, 2.0f, 30.0f / 60.0f},
6514 {.003f, 1.0f, 20.0f / 60.0f},
6515 {.006f, 0.5f, 10.0f / 60.0f},
6516 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6517 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6518 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6519 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6520 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6521 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6522 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6523 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6524
6525 unsigned int tabi;
6526 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6527 if (view_scale_ppm < lltab[tabi][0]) break;
6528 MajorSpacing = lltab[tabi][1]; // major latitude distance
6529 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6530 return;
6531}
6532/* @ChartCanvas::CalcGridText *************************************
6533 **
6534 ** Calculates text to display at the major grid lines
6535 **
6536 ** @param [r] latlon [float] latitude or longitude of grid line
6537 ** @param [r] spacing [float] distance between two major grid lines
6538 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6539 **
6540 ** @return
6541 */
6542
6543wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6544 int deg = (int)fabs(latlon); // degrees
6545 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6546 char postfix;
6547
6548 // calculate postfix letter (NSEW)
6549 if (latlon > 0.0) {
6550 if (bPostfix) {
6551 postfix = 'N';
6552 } else {
6553 postfix = 'E';
6554 }
6555 } else if (latlon < 0.0) {
6556 if (bPostfix) {
6557 postfix = 'S';
6558 } else {
6559 postfix = 'W';
6560 }
6561 } else {
6562 postfix = ' '; // no postfix for equator and greenwich
6563 }
6564 // calculate text, display minutes only if spacing is smaller than one degree
6565
6566 wxString ret;
6567 if (spacing >= 1.0) {
6568 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6569 } else if (spacing >= (1.0 / 60.0)) {
6570 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6571 } else {
6572 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6573 }
6574
6575 return ret;
6576}
6577
6578/* @ChartCanvas::GridDraw *****************************************
6579 **
6580 ** Draws major and minor Lat/Lon Grid on the chart
6581 ** - distance between Grid-lm ines are calculated automatic
6582 ** - major grid lines will be across the whole chart window
6583 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6584 **
6585 ** @param [w] dc [wxDC&] the wx drawing context
6586 **
6587 ** @return [void]
6588 ************************************************************************/
6589void ChartCanvas::GridDraw(ocpnDC &dc) {
6590 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6591
6592 double nlat, elon, slat, wlon;
6593 float lat, lon;
6594 float dlon;
6595 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6596 wxCoord w, h;
6597 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6598 dc.SetPen(GridPen);
6599 if (!m_pgridFont) SetupGridFont();
6600 dc.SetFont(*m_pgridFont);
6601 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6602
6603 w = m_canvas_width;
6604 h = m_canvas_height;
6605
6606 GetCanvasPixPoint(0, 0, nlat,
6607 wlon); // get lat/lon of upper left point of the window
6608 GetCanvasPixPoint(w, h, slat,
6609 elon); // get lat/lon of lower right point of the window
6610 dlon =
6611 elon -
6612 wlon; // calculate how many degrees of longitude are shown in the window
6613 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6614 {
6615 dlon = dlon + 360.0;
6616 }
6617 // calculate distance between latitude grid lines
6618 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6619
6620 // calculate position of first major latitude grid line
6621 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6622
6623 // Draw Major latitude grid lines and text
6624 while (lat < nlat) {
6625 wxPoint r;
6626 wxString st =
6627 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6628 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6629 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6630 dc.DrawText(st, 0, r.y); // draw text
6631 lat = lat + gridlatMajor;
6632
6633 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6634 }
6635
6636 // calculate position of first minor latitude grid line
6637 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6638
6639 // Draw minor latitude grid lines
6640 while (lat < nlat) {
6641 wxPoint r;
6642 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6643 dc.DrawLine(0, r.y, 10, r.y, false);
6644 dc.DrawLine(w - 10, r.y, w, r.y, false);
6645 lat = lat + gridlatMinor;
6646 }
6647
6648 // calculate distance between grid lines
6649 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6650
6651 // calculate position of first major latitude grid line
6652 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6653
6654 // draw major longitude grid lines
6655 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6656 wxPoint r;
6657 wxString st = CalcGridText(lon, gridlonMajor, false);
6658 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6659 dc.DrawLine(r.x, 0, r.x, h, false);
6660 dc.DrawText(st, r.x, 0);
6661 lon = lon + gridlonMajor;
6662 if (lon > 180.0) {
6663 lon = lon - 360.0;
6664 }
6665
6666 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6667 }
6668
6669 // calculate position of first minor longitude grid line
6670 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6671 // draw minor longitude grid lines
6672 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6673 wxPoint r;
6674 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6675 dc.DrawLine(r.x, 0, r.x, 10, false);
6676 dc.DrawLine(r.x, h - 10, r.x, h, false);
6677 lon = lon + gridlonMinor;
6678 if (lon > 180.0) {
6679 lon = lon - 360.0;
6680 }
6681 }
6682}
6683
6684void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6685 if (0 ) {
6686 double blat, blon, tlat, tlon;
6687 wxPoint r;
6688
6689 int x_origin = m_bDisplayGrid ? 60 : 20;
6690 int y_origin = m_canvas_height - 50;
6691
6692 float dist;
6693 int count;
6694 wxPen pen1, pen2;
6695
6696 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6697 {
6698 dist = 10.0;
6699 count = 5;
6700 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6701 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6702 } else // Draw 1 mile scale as SCALEB10
6703 {
6704 dist = 1.0;
6705 count = 10;
6706 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6707 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6708 }
6709
6710 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6711 double rotation = -VPoint.rotation;
6712
6713 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6714 GetCanvasPointPix(tlat, tlon, &r);
6715 int l1 = (y_origin - r.y) / count;
6716
6717 for (int i = 0; i < count; i++) {
6718 int y = l1 * i;
6719 if (i & 1)
6720 dc.SetPen(pen1);
6721 else
6722 dc.SetPen(pen2);
6723
6724 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6725 }
6726 } else {
6727 double blat, blon, tlat, tlon;
6728
6729 int x_origin = 5.0 * GetPixPerMM();
6730 int chartbar_height = GetChartbarHeight();
6731 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6732 // if (style->chartStatusWindowTransparent)
6733 // chartbar_height = 0;
6734 int y_origin = m_canvas_height - chartbar_height - 5;
6735#ifdef __WXOSX__
6736 if (!g_bopengl)
6737 y_origin =
6738 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6739#endif
6740
6741 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6742 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6743
6744 double d;
6745 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6746 d /= 2;
6747
6748 int unit = g_iDistanceFormat;
6749 if (d < .5 &&
6750 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6751 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6752
6753 // nice number
6754 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6755 float places = floor(logdist), rem = logdist - places;
6756 dist = pow(10, places);
6757
6758 if (rem < .2)
6759 dist /= 5;
6760 else if (rem < .5)
6761 dist /= 2;
6762
6763 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6764 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6765 double rotation = -VPoint.rotation;
6766
6767 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6768 &tlat, &tlon);
6769 wxPoint r;
6770 GetCanvasPointPix(tlat, tlon, &r);
6771 int l1 = r.x - x_origin;
6772
6773 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6774 12); // Store this for later reference
6775
6776 dc.SetPen(pen1);
6777
6778 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6779 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6780 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6781
6782 if (!m_pgridFont) SetupGridFont();
6783 dc.SetFont(*m_pgridFont);
6784 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6785 int w, h;
6786 dc.GetTextExtent(s, &w, &h);
6787 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6788 if (g_bopengl) {
6789 w /= dpi_factor;
6790 h /= dpi_factor;
6791 }
6792 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6793 }
6794}
6795
6796void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6797 // Constants?
6798 double da_min = 2.;
6799 double da_max = 6.;
6800 double ra_min = 0.;
6801 double ra_max = 40.;
6802
6803 wxPen pen_save = dc.GetPen();
6804
6805 wxDateTime now = wxDateTime::Now();
6806
6807 dc.SetPen(pen);
6808
6809 int x0, y0, x1, y1;
6810
6811 x0 = x1 = x + radius; // Start point
6812 y0 = y1 = y;
6813 double angle = 0.;
6814 int i = 0;
6815
6816 while (angle < 360.) {
6817 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6818 angle += da;
6819
6820 if (angle > 360.) angle = 360.;
6821
6822 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6823
6824 double r;
6825 if (i & 1)
6826 r = radius + ra;
6827 else
6828 r = radius - ra;
6829
6830 x1 = (int)(x + cos(angle * PI / 180.) * r);
6831 y1 = (int)(y + sin(angle * PI / 180.) * r);
6832
6833 dc.DrawLine(x0, y0, x1, y1);
6834
6835 x0 = x1;
6836 y0 = y1;
6837
6838 i++;
6839 }
6840
6841 dc.DrawLine(x + radius, y, x1, y1); // closure
6842
6843 dc.SetPen(pen_save);
6844}
6845
6846static bool bAnchorSoundPlaying = false;
6847
6848static void onAnchorSoundFinished(void *ptr) {
6849 o_sound::g_anchorwatch_sound->UnLoad();
6850 bAnchorSoundPlaying = false;
6851}
6852
6853void ChartCanvas::AlertDraw(ocpnDC &dc) {
6854 using namespace o_sound;
6855 // Visual and audio alert for anchorwatch goes here
6856 bool play_sound = false;
6857 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6858 if (AnchorAlertOn1) {
6859 wxPoint TargetPoint;
6861 &TargetPoint);
6862 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6863 TargetPoint.y, 100);
6864 play_sound = true;
6865 }
6866 } else
6867 AnchorAlertOn1 = false;
6868
6869 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6870 if (AnchorAlertOn2) {
6871 wxPoint TargetPoint;
6873 &TargetPoint);
6874 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6875 TargetPoint.y, 100);
6876 play_sound = true;
6877 }
6878 } else
6879 AnchorAlertOn2 = false;
6880
6881 if (play_sound) {
6882 if (!bAnchorSoundPlaying) {
6883 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6884 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6885 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6886 if (g_anchorwatch_sound->IsOk()) {
6887 bAnchorSoundPlaying = true;
6888 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6889 g_anchorwatch_sound->Play();
6890 }
6891 }
6892 }
6893}
6894
6895void ChartCanvas::UpdateShips() {
6896 // Get the rectangle in the current dc which bounds the "ownship" symbol
6897
6898 wxClientDC dc(this);
6899 // Sometimes during startup the dc x or y size is zero, which causes the
6900 // bitmap creation to fail. Just skip the update in this case.
6901 if (!dc.IsOk() || dc.GetSize().x < 1 || dc.GetSize().y < 1) return;
6902
6903 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6904 if (!test_bitmap.IsOk()) return;
6905
6906 wxMemoryDC temp_dc(test_bitmap);
6907
6908 temp_dc.ResetBoundingBox();
6909 temp_dc.DestroyClippingRegion();
6910 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6911
6912 // Draw the ownship on the temp_dc
6913 ocpnDC ocpndc = ocpnDC(temp_dc);
6914 ShipDraw(ocpndc);
6915
6916 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6917 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6918 if (p) {
6919 wxPoint px;
6920 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6921 ocpndc.CalcBoundingBox(px.x, px.y);
6922 }
6923 }
6924
6925 ship_draw_rect =
6926 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6927 temp_dc.MaxY() - temp_dc.MinY());
6928
6929 wxRect own_ship_update_rect = ship_draw_rect;
6930
6931 if (!own_ship_update_rect.IsEmpty()) {
6932 // The required invalidate rectangle is the union of the last drawn
6933 // rectangle and this drawn rectangle
6934 own_ship_update_rect.Union(ship_draw_last_rect);
6935 own_ship_update_rect.Inflate(2);
6936 }
6937
6938 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6939
6940 ship_draw_last_rect = ship_draw_rect;
6941
6942 temp_dc.SelectObject(wxNullBitmap);
6943}
6944
6945void ChartCanvas::UpdateAlerts() {
6946 // Get the rectangle in the current dc which bounds the detected Alert
6947 // targets
6948
6949 // Use this dc
6950 wxClientDC dc(this);
6951
6952 // Get dc boundary
6953 int sx, sy;
6954 dc.GetSize(&sx, &sy);
6955
6956 // Need a bitmap
6957 wxBitmap test_bitmap(sx, sy, -1);
6958
6959 // Create a memory DC
6960 wxMemoryDC temp_dc;
6961 temp_dc.SelectObject(test_bitmap);
6962
6963 temp_dc.ResetBoundingBox();
6964 temp_dc.DestroyClippingRegion();
6965 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6966
6967 // Draw the Alert Targets on the temp_dc
6968 ocpnDC ocpndc = ocpnDC(temp_dc);
6969 AlertDraw(ocpndc);
6970
6971 // Retrieve the drawing extents
6972 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6973 temp_dc.MaxX() - temp_dc.MinX(),
6974 temp_dc.MaxY() - temp_dc.MinY());
6975
6976 if (!alert_rect.IsEmpty())
6977 alert_rect.Inflate(2); // clear all drawing artifacts
6978
6979 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6980 // The required invalidate rectangle is the union of the last drawn
6981 // rectangle and this drawn rectangle
6982 wxRect alert_update_rect = alert_draw_rect;
6983 alert_update_rect.Union(alert_rect);
6984
6985 // Invalidate the rectangular region
6986 RefreshRect(alert_update_rect, false);
6987 }
6988
6989 // Save this rectangle for next time
6990 alert_draw_rect = alert_rect;
6991
6992 temp_dc.SelectObject(wxNullBitmap); // clean up
6993}
6994
6995void ChartCanvas::UpdateAIS() {
6996 if (!g_pAIS) return;
6997
6998 // Get the rectangle in the current dc which bounds the detected AIS targets
6999
7000 // Use this dc
7001 wxClientDC dc(this);
7002
7003 // Get dc boundary
7004 int sx, sy;
7005 dc.GetSize(&sx, &sy);
7006
7007 wxRect ais_rect;
7008
7009 // How many targets are there?
7010
7011 // If more than "some number", it will be cheaper to refresh the entire
7012 // screen than to build update rectangles for each target.
7013 if (g_pAIS->GetTargetList().size() > 10) {
7014 ais_rect = wxRect(0, 0, sx, sy); // full screen
7015 } else {
7016 // Need a bitmap
7017 wxBitmap test_bitmap(sx, sy, -1);
7018
7019 // Create a memory DC
7020 wxMemoryDC temp_dc;
7021 temp_dc.SelectObject(test_bitmap);
7022
7023 temp_dc.ResetBoundingBox();
7024 temp_dc.DestroyClippingRegion();
7025 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7026
7027 // Draw the AIS Targets on the temp_dc
7028 ocpnDC ocpndc = ocpnDC(temp_dc);
7029 AISDraw(ocpndc, GetVP(), this);
7030 AISDrawAreaNotices(ocpndc, GetVP(), this);
7031
7032 // Retrieve the drawing extents
7033 ais_rect =
7034 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7035 temp_dc.MaxY() - temp_dc.MinY());
7036
7037 if (!ais_rect.IsEmpty())
7038 ais_rect.Inflate(2); // clear all drawing artifacts
7039
7040 temp_dc.SelectObject(wxNullBitmap); // clean up
7041 }
7042
7043 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7044 // The required invalidate rectangle is the union of the last drawn
7045 // rectangle and this drawn rectangle
7046 wxRect ais_update_rect = ais_draw_rect;
7047 ais_update_rect.Union(ais_rect);
7048
7049 // Invalidate the rectangular region
7050 RefreshRect(ais_update_rect, false);
7051 }
7052
7053 // Save this rectangle for next time
7054 ais_draw_rect = ais_rect;
7055}
7056
7057void ChartCanvas::ToggleCPAWarn() {
7058 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7059 wxString mess;
7060 if (g_bCPAWarn) {
7061 g_bTCPA_Max = true;
7062 mess = _("ON");
7063 } else {
7064 g_bTCPA_Max = false;
7065 mess = _("OFF");
7066 }
7067 // Print to status bar if available.
7068 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7069 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7070 } else {
7071 if (!g_AisFirstTimeUse) {
7072 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7073 _("CPA") + " " + mess, 4, 4);
7074 }
7075 }
7076}
7077
7078void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7079
7080void ChartCanvas::OnSize(wxSizeEvent &event) {
7081 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7082 // GetClientSize returns the size of the canvas area in logical pixels.
7083 GetClientSize(&m_canvas_width, &m_canvas_height);
7084
7085#ifdef __WXOSX__
7086 // Support scaled HDPI displays.
7087 m_displayScale = GetContentScaleFactor();
7088#endif
7089
7090 // Convert to physical pixels.
7091 m_canvas_width *= m_displayScale;
7092 m_canvas_height *= m_displayScale;
7093
7094 // Resize the current viewport
7095 VPoint.pix_width = m_canvas_width;
7096 VPoint.pix_height = m_canvas_height;
7097 VPoint.SetPixelScale(m_displayScale);
7098
7099 // Get some canvas metrics
7100
7101 // Rescale to current value, in order to rebuild VPoint data
7102 // structures for new canvas size
7104
7105 m_absolute_min_scale_ppm =
7106 m_canvas_width /
7107 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7108
7109 // Inform the parent Frame that I am being resized...
7110 top_frame::Get()->ProcessCanvasResize();
7111
7112 // if MUIBar is active, size the bar
7113 // if(g_useMUI && !m_muiBar){ // rebuild if
7114 // necessary
7115 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7116 // m_muiBarHOSize = m_muiBar->GetSize();
7117 // }
7118
7119 if (m_muiBar) {
7120 SetMUIBarPosition();
7121 UpdateFollowButtonState();
7122 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7123 }
7124
7125 // Set up the scroll margins
7126 xr_margin = m_canvas_width * 95 / 100;
7127 xl_margin = m_canvas_width * 5 / 100;
7128 yt_margin = m_canvas_height * 5 / 100;
7129 yb_margin = m_canvas_height * 95 / 100;
7130
7131 if (m_pQuilt)
7132 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7133
7134 // Resize the scratch BM
7135 delete pscratch_bm;
7136 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7137 m_brepaint_piano = true;
7138
7139 // Resize the Route Calculation BM
7140 m_dc_route.SelectObject(wxNullBitmap);
7141 delete proute_bm;
7142 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7143 m_dc_route.SelectObject(*proute_bm);
7144
7145 // Resize the saved Bitmap
7146 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7147
7148 // Resize the working Bitmap
7149 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7150
7151 // Rescale again, to capture all the changes for new canvas size
7153
7154#ifdef ocpnUSE_GL
7155 if (/*g_bopengl &&*/ m_glcc) {
7156 // FIXME (dave) This can go away?
7157 m_glcc->OnSize(event);
7158 }
7159#endif
7160
7161 FormatPianoKeys();
7162 // Invalidate the whole window
7163 ReloadVP();
7164}
7165
7166void ChartCanvas::ProcessNewGUIScale() {
7167 // m_muiBar->Hide();
7168 delete m_muiBar;
7169 m_muiBar = 0;
7170
7171 CreateMUIBar();
7172}
7173
7174void ChartCanvas::CreateMUIBar() {
7175 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7176 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7177 m_muiBar->SetColorScheme(m_cs);
7178 m_muiBarHOSize = m_muiBar->m_size;
7179 }
7180
7181 if (m_muiBar) {
7182 // We need to update the m_bENCGroup flag, not least for the initial
7183 // creation of a MUIBar
7184 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7185
7186 SetMUIBarPosition();
7187 UpdateFollowButtonState();
7188 m_muiBar->UpdateDynamicValues();
7189 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7190 }
7191}
7192
7193void ChartCanvas::SetMUIBarPosition() {
7194 // if MUIBar is active, size the bar
7195 if (m_muiBar) {
7196 // We estimate the piano width based on the canvas width
7197 int pianoWidth = GetClientSize().x * 0.6f;
7198 // If the piano already exists, we can use its exact width
7199 // if(m_Piano)
7200 // pianoWidth = m_Piano->GetWidth();
7201
7202 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7203 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7204 delete m_muiBar;
7205 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7206 m_muiBar->SetColorScheme(m_cs);
7207 }
7208 }
7209
7210 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7211 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7212 delete m_muiBar;
7213 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7214 m_muiBar->SetColorScheme(m_cs);
7215 }
7216 }
7217
7218 m_muiBar->SetBestPosition();
7219 }
7220}
7221
7222void ChartCanvas::DestroyMuiBar() {
7223 if (m_muiBar) {
7224 delete m_muiBar;
7225 m_muiBar = NULL;
7226 }
7227}
7228
7229void ChartCanvas::ShowCompositeInfoWindow(
7230 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7231 if (n_charts > 0) {
7232 if (NULL == m_pCIWin) {
7233 m_pCIWin = new ChInfoWin(this);
7234 m_pCIWin->Hide();
7235 }
7236
7237 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7238 wxString s;
7239
7240 s = _("Composite of ");
7241
7242 wxString s1;
7243 s1.Printf("%d ", n_charts);
7244 if (n_charts > 1)
7245 s1 += _("charts");
7246 else
7247 s1 += _("chart");
7248 s += s1;
7249 s += '\n';
7250
7251 s1.Printf(_("Chart scale"));
7252 s1 += ": ";
7253 wxString s2;
7254 s2.Printf("1:%d\n", scale);
7255 s += s1;
7256 s += s2;
7257
7258 s1 = _("Zoom in for more information");
7259 s += s1;
7260 s += '\n';
7261
7262 int char_width = s1.Length();
7263 int char_height = 3;
7264
7265 if (g_bChartBarEx) {
7266 s += '\n';
7267 int j = 0;
7268 for (int i : index_vector) {
7269 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7270 wxString path = cte.GetFullSystemPath();
7271 s += path;
7272 s += '\n';
7273 char_height++;
7274 char_width = wxMax(char_width, path.Length());
7275 if (j++ >= 9) break;
7276 }
7277 if (j >= 9) {
7278 s += " .\n .\n .\n";
7279 char_height += 3;
7280 }
7281 s += '\n';
7282 char_height += 1;
7283
7284 char_width += 4; // Fluff
7285 }
7286
7287 m_pCIWin->SetString(s);
7288
7289 m_pCIWin->FitToChars(char_width, char_height);
7290
7291 wxPoint p;
7292 p.x = x / GetContentScaleFactor();
7293 if ((p.x + m_pCIWin->GetWinSize().x) >
7294 (m_canvas_width / GetContentScaleFactor()))
7295 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7296 m_pCIWin->GetWinSize().x) /
7297 2; // centered
7298
7299 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7300 4 - m_pCIWin->GetWinSize().y;
7301
7302 m_pCIWin->dbIndex = 0;
7303 m_pCIWin->chart_scale = 0;
7304 m_pCIWin->SetPosition(p);
7305 m_pCIWin->SetBitmap();
7306 m_pCIWin->Refresh();
7307 m_pCIWin->Show();
7308 }
7309 } else {
7310 HideChartInfoWindow();
7311 }
7312}
7313
7314void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7315 if (dbIndex >= 0) {
7316 if (NULL == m_pCIWin) {
7317 m_pCIWin = new ChInfoWin(this);
7318 m_pCIWin->Hide();
7319 }
7320
7321 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7322 wxString s;
7323 ChartBase *pc = NULL;
7324
7325 // TOCTOU race but worst case will reload chart.
7326 // need to lock it or the background spooler may evict charts in
7327 // OpenChartFromDBAndLock
7328 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7329 pc = ChartData->OpenChartFromDBAndLock(
7330 dbIndex, FULL_INIT); // this must come from cache
7331
7332 int char_width, char_height;
7333 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7334 if (pc) ChartData->UnLockCacheChart(dbIndex);
7335
7336 m_pCIWin->SetString(s);
7337 m_pCIWin->FitToChars(char_width, char_height);
7338
7339 wxPoint p;
7340 p.x = x / GetContentScaleFactor();
7341 if ((p.x + m_pCIWin->GetWinSize().x) >
7342 (m_canvas_width / GetContentScaleFactor()))
7343 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7344 m_pCIWin->GetWinSize().x) /
7345 2; // centered
7346
7347 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7348 4 - m_pCIWin->GetWinSize().y;
7349
7350 m_pCIWin->dbIndex = dbIndex;
7351 m_pCIWin->SetPosition(p);
7352 m_pCIWin->SetBitmap();
7353 m_pCIWin->Refresh();
7354 m_pCIWin->Show();
7355 }
7356 } else {
7357 HideChartInfoWindow();
7358 }
7359}
7360
7361void ChartCanvas::HideChartInfoWindow() {
7362 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7363 m_pCIWin->Hide();
7364 m_pCIWin->Destroy();
7365 m_pCIWin = NULL;
7366
7367#ifdef __ANDROID__
7368 androidForceFullRepaint();
7369#endif
7370 }
7371}
7372
7373void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7374 wxMouseEvent ev(wxEVT_MOTION);
7375 ev.m_x = mouse_x;
7376 ev.m_y = mouse_y;
7377 ev.m_leftDown = mouse_leftisdown;
7378
7379 wxEvtHandler *evthp = GetEventHandler();
7380
7381 ::wxPostEvent(evthp, ev);
7382}
7383
7384void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7385 if ((m_panx_target_final - m_panx_target_now) ||
7386 (m_pany_target_final - m_pany_target_now)) {
7387 DoTimedMovementTarget();
7388 } else
7389 DoTimedMovement();
7390}
7391
7392void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7393
7394bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7395 int delta) {
7396 if (m_disable_edge_pan) return false;
7397
7398 bool bft = false;
7399 int pan_margin = m_canvas_width * margin / 100;
7400 int pan_timer_set = 200;
7401 double pan_delta = GetVP().pix_width * delta / 100;
7402 int pan_x = 0;
7403 int pan_y = 0;
7404
7405 if (x > m_canvas_width - pan_margin) {
7406 bft = true;
7407 pan_x = pan_delta;
7408 }
7409
7410 else if (x < pan_margin) {
7411 bft = true;
7412 pan_x = -pan_delta;
7413 }
7414
7415 if (y < pan_margin) {
7416 bft = true;
7417 pan_y = -pan_delta;
7418 }
7419
7420 else if (y > m_canvas_height - pan_margin) {
7421 bft = true;
7422 pan_y = pan_delta;
7423 }
7424
7425 // Of course, if dragging, and the mouse left button is not down, we must
7426 // stop the event injection
7427 if (bdragging) {
7428 if (!g_btouch) {
7429 wxMouseState state = ::wxGetMouseState();
7430#if wxCHECK_VERSION(3, 0, 0)
7431 if (!state.LeftIsDown())
7432#else
7433 if (!state.LeftDown())
7434#endif
7435 bft = false;
7436 }
7437 }
7438 if ((bft) && !pPanTimer->IsRunning()) {
7439 PanCanvas(pan_x, pan_y);
7440 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7441 return true;
7442 }
7443
7444 // This mouse event must not be due to pan timer event injector
7445 // Mouse is out of the pan zone, so prevent any orphan event injection
7446 if ((!bft) && pPanTimer->IsRunning()) {
7447 pPanTimer->Stop();
7448 }
7449
7450 return (false);
7451}
7452
7453// Look for waypoints at the current position.
7454// Used to determine what a mouse event should act on.
7455
7456void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7457 bool setBeingEdited) {
7458 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7459 m_pRoutePointEditTarget = NULL;
7460 m_pFoundPoint = NULL;
7461
7462 SelectItem *pFind = NULL;
7463 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7464 SelectableItemList SelList = pSelect->FindSelectionList(
7465 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7466 for (SelectItem *pFind : SelList) {
7467 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7468
7469 // Get an array of all routes using this point
7470 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7471 // TODO: delete m_pEditRouteArray after use?
7472
7473 // Use route array to determine actual visibility for the point
7474 bool brp_viz = false;
7475 if (m_pEditRouteArray) {
7476 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7477 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7478 if (pr->IsVisible()) {
7479 brp_viz = true;
7480 break;
7481 }
7482 }
7483 } else
7484 brp_viz = frp->IsVisible(); // isolated point
7485
7486 if (brp_viz) {
7487 // Use route array to rubberband all affected routes
7488 if (m_pEditRouteArray) // Editing Waypoint as part of route
7489 {
7490 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7491 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7492 pr->m_bIsBeingEdited = setBeingEdited;
7493 }
7494 m_bRouteEditing = setBeingEdited;
7495 } else // editing Mark
7496 {
7497 frp->m_bRPIsBeingEdited = setBeingEdited;
7498 m_bMarkEditing = setBeingEdited;
7499 }
7500
7501 m_pRoutePointEditTarget = frp;
7502 m_pFoundPoint = pFind;
7503 break; // out of the while(node)
7504 }
7505 } // for (SelectItem...
7506}
7507std::shared_ptr<HostApi121::PiPointContext>
7508ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7509 // General Right Click
7510 // Look for selectable objects
7511 double slat, slon;
7512 GetCanvasPixPoint(x, y, slat, slon);
7513
7514 SelectItem *pFindAIS;
7515 SelectItem *pFindRP;
7516 SelectItem *pFindRouteSeg;
7517 SelectItem *pFindTrackSeg;
7518 SelectItem *pFindCurrent = NULL;
7519 SelectItem *pFindTide = NULL;
7520
7521 // Get all the selectable things at the selected point
7522 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7523 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7524 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7525 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7526 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7527
7528 if (m_bShowCurrent)
7529 pFindCurrent =
7530 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7531
7532 if (m_bShowTide) // look for tide stations
7533 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7534
7535 int seltype = 0;
7536
7537 // Try for AIS targets first
7538 int FoundAIS_MMSI = 0;
7539 if (pFindAIS) {
7540 FoundAIS_MMSI = pFindAIS->GetUserData();
7541
7542 // Make sure the target data is available
7543 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7544 seltype |= SELTYPE_AISTARGET;
7545 }
7546
7547 // Now the various Route Parts
7548
7549 RoutePoint *FoundRoutePoint = NULL;
7550 Route *SelectedRoute = NULL;
7551
7552 if (pFindRP) {
7553 RoutePoint *pFirstVizPoint = NULL;
7554 RoutePoint *pFoundActiveRoutePoint = NULL;
7555 RoutePoint *pFoundVizRoutePoint = NULL;
7556 Route *pSelectedActiveRoute = NULL;
7557 Route *pSelectedVizRoute = NULL;
7558
7559 // There is at least one routepoint, so get the whole list
7560 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7561 SelectableItemList SelList =
7562 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7563 for (SelectItem *pFindSel : SelList) {
7564 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7565
7566 // Get an array of all routes using this point
7567 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7568
7569 // Use route array (if any) to determine actual visibility for this point
7570 bool brp_viz = false;
7571 if (proute_array) {
7572 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7573 Route *pr = (Route *)proute_array->Item(ir);
7574 if (pr->IsVisible()) {
7575 brp_viz = true;
7576 break;
7577 }
7578 }
7579 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7580 // but still exists as a waypoint
7581 brp_viz = prp->IsVisible(); // so treat as isolated point
7582
7583 } else
7584 brp_viz = prp->IsVisible(); // isolated point
7585
7586 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7587
7588 // Use route array to choose the appropriate route
7589 // Give preference to any active route, otherwise select the first visible
7590 // route in the array for this point
7591 if (proute_array) {
7592 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7593 Route *pr = (Route *)proute_array->Item(ir);
7594 if (pr->m_bRtIsActive) {
7595 pSelectedActiveRoute = pr;
7596 pFoundActiveRoutePoint = prp;
7597 break;
7598 }
7599 }
7600
7601 if (NULL == pSelectedVizRoute) {
7602 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7603 Route *pr = (Route *)proute_array->Item(ir);
7604 if (pr->IsVisible()) {
7605 pSelectedVizRoute = pr;
7606 pFoundVizRoutePoint = prp;
7607 break;
7608 }
7609 }
7610 }
7611
7612 delete proute_array;
7613 }
7614 }
7615
7616 // Now choose the "best" selections
7617 if (pFoundActiveRoutePoint) {
7618 FoundRoutePoint = pFoundActiveRoutePoint;
7619 SelectedRoute = pSelectedActiveRoute;
7620 } else if (pFoundVizRoutePoint) {
7621 FoundRoutePoint = pFoundVizRoutePoint;
7622 SelectedRoute = pSelectedVizRoute;
7623 } else
7624 // default is first visible point in list
7625 FoundRoutePoint = pFirstVizPoint;
7626
7627 if (SelectedRoute) {
7628 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7629 } else if (FoundRoutePoint) {
7630 seltype |= SELTYPE_MARKPOINT;
7631 }
7632
7633 // Highlight the selected point, to verify the proper right click selection
7634#if 0
7635 if (m_pFoundRoutePoint) {
7636 m_pFoundRoutePoint->m_bPtIsSelected = true;
7637 wxRect wp_rect;
7638 RoutePointGui(*m_pFoundRoutePoint)
7639 .CalculateDCRect(m_dc_route, this, &wp_rect);
7640 RefreshRect(wp_rect, true);
7641 }
7642#endif
7643 }
7644
7645 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7646 // routes But call the popup handler with identifier appropriate to the type
7647 if (pFindRouteSeg) // there is at least one select item
7648 {
7649 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7650 SelectableItemList SelList =
7651 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7652
7653 if (NULL == SelectedRoute) // the case where a segment only is selected
7654 {
7655 // Choose the first visible route containing segment in the list
7656 for (SelectItem *pFindSel : SelList) {
7657 Route *pr = (Route *)pFindSel->m_pData3;
7658 if (pr->IsVisible()) {
7659 SelectedRoute = pr;
7660 break;
7661 }
7662 }
7663 }
7664
7665 if (SelectedRoute) {
7666 if (NULL == FoundRoutePoint)
7667 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7668
7669 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7670 seltype |= SELTYPE_ROUTESEGMENT;
7671 }
7672 }
7673
7674 if (pFindTrackSeg) {
7675 m_pSelectedTrack = NULL;
7676 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7677 SelectableItemList SelList =
7678 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7679
7680 // Choose the first visible track containing segment in the list
7681 for (SelectItem *pFindSel : SelList) {
7682 Track *pt = (Track *)pFindSel->m_pData3;
7683 if (pt->IsVisible()) {
7684 m_pSelectedTrack = pt;
7685 break;
7686 }
7687 }
7688 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7689 }
7690
7691 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7692
7693 // Populate the return struct
7694 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7695 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7696 rstruct->object_ident = "";
7697
7698 if (seltype == SELTYPE_AISTARGET) {
7699 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7700 wxString val;
7701 val.Printf("%d", FoundAIS_MMSI);
7702 rstruct->object_ident = val.ToStdString();
7703 } else if (seltype & SELTYPE_MARKPOINT) {
7704 if (FoundRoutePoint) {
7705 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7706 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7707 }
7708 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7709 if (SelectedRoute) {
7710 rstruct->object_type =
7711 HostApi121::PiContextObjectType::kObjectRoutesegment;
7712 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7713 }
7714 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7715 if (m_pSelectedTrack) {
7716 rstruct->object_type =
7717 HostApi121::PiContextObjectType::kObjectTracksegment;
7718 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7719 }
7720 }
7721
7722 return rstruct;
7723}
7724
7725void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7726 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7727 singleClickEventIsValid = false;
7728 m_DoubleClickTimer->Stop();
7729}
7730
7731bool leftIsDown;
7732
7733bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7734 if (!m_bChartDragging && !m_bDrawingRoute) {
7735 /*
7736 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7737 * mouse event coordinates are in logical pixels.
7738 */
7739 if (m_Compass && m_Compass->IsShown()) {
7740 wxRect logicalRect = m_Compass->GetLogicalRect();
7741 bool isInCompass = logicalRect.Contains(event.GetPosition());
7742 if (isInCompass || m_mouseWasInCompass) {
7743 if (m_Compass->MouseEvent(event)) {
7744 cursor_region = CENTER;
7745 if (!g_btouch) SetCanvasCursor(event);
7746 m_mouseWasInCompass = isInCompass;
7747 return true;
7748 }
7749 }
7750 m_mouseWasInCompass = isInCompass;
7751 }
7752
7753 if (m_notification_button && m_notification_button->IsShown()) {
7754 wxRect logicalRect = m_notification_button->GetLogicalRect();
7755 bool isinButton = logicalRect.Contains(event.GetPosition());
7756 if (isinButton) {
7757 SetCursor(*pCursorArrow);
7758 if (event.LeftDown()) HandleNotificationMouseClick();
7759 return true;
7760 }
7761 }
7762
7763 if (MouseEventToolbar(event)) return true;
7764
7765 if (MouseEventChartBar(event)) return true;
7766
7767 if (MouseEventMUIBar(event)) return true;
7768
7769 if (MouseEventIENCBar(event)) return true;
7770 }
7771 return false;
7772}
7773
7774void ChartCanvas::HandleNotificationMouseClick() {
7775 if (!m_NotificationsList) {
7776 m_NotificationsList = new NotificationsList(this);
7777
7778 // calculate best size for Notification list
7779 m_NotificationsList->RecalculateSize();
7780 m_NotificationsList->Hide();
7781 }
7782
7783 if (m_NotificationsList->IsShown()) {
7784 m_NotificationsList->Hide();
7785 } else {
7786 m_NotificationsList->RecalculateSize();
7787 m_NotificationsList->ReloadNotificationList();
7788 m_NotificationsList->Show();
7789 }
7790}
7791bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7792 if (!g_bShowChartBar) return false;
7793
7794 if (!m_Piano->MouseEvent(event)) return false;
7795
7796 cursor_region = CENTER;
7797 if (!g_btouch) SetCanvasCursor(event);
7798 return true;
7799}
7800
7801bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7802 if (!IsPrimaryCanvas()) return false;
7803
7804 if (g_MainToolbar) {
7805 if (!g_MainToolbar->MouseEvent(event))
7806 return false;
7807 else
7808 g_MainToolbar->RefreshToolbar();
7809 }
7810
7811 cursor_region = CENTER;
7812 if (!g_btouch) SetCanvasCursor(event);
7813 return true;
7814}
7815
7816bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7817 if (!IsPrimaryCanvas()) return false;
7818
7819 if (g_iENCToolbar) {
7820 if (!g_iENCToolbar->MouseEvent(event))
7821 return false;
7822 else {
7823 g_iENCToolbar->RefreshToolbar();
7824 return true;
7825 }
7826 }
7827 return false;
7828}
7829
7830bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7831 if (m_muiBar) {
7832 if (!m_muiBar->MouseEvent(event)) return false;
7833 }
7834
7835 cursor_region = CENTER;
7836 if (!g_btouch) SetCanvasCursor(event);
7837 if (m_muiBar)
7838 return true;
7839 else
7840 return false;
7841}
7842
7843bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7844 int x, y;
7845
7846 bool bret = false;
7847
7848 event.GetPosition(&x, &y);
7849
7850 x *= m_displayScale;
7851 y *= m_displayScale;
7852
7853 m_MouseDragging = event.Dragging();
7854
7855 // Some systems produce null drag events, where the pointer position has not
7856 // changed from the previous value. Detect this case, and abort further
7857 // processing (FS#1748)
7858#ifdef __WXMSW__
7859 if (event.Dragging()) {
7860 if ((x == mouse_x) && (y == mouse_y)) return true;
7861 }
7862#endif
7863
7864 mouse_x = x;
7865 mouse_y = y;
7866 mouse_leftisdown = event.LeftDown();
7868
7869 // Establish the event region
7870 cursor_region = CENTER;
7871
7872 int chartbar_height = GetChartbarHeight();
7873
7874 if (m_Compass && m_Compass->IsShown() &&
7875 m_Compass->GetRect().Contains(event.GetPosition())) {
7876 cursor_region = CENTER;
7877 } else if (x > xr_margin) {
7878 cursor_region = MID_RIGHT;
7879 } else if (x < xl_margin) {
7880 cursor_region = MID_LEFT;
7881 } else if (y > yb_margin - chartbar_height &&
7882 y < m_canvas_height - chartbar_height) {
7883 cursor_region = MID_TOP;
7884 } else if (y < yt_margin) {
7885 cursor_region = MID_BOT;
7886 } else {
7887 cursor_region = CENTER;
7888 }
7889
7890 if (!g_btouch) SetCanvasCursor(event);
7891
7892 // Protect from leftUp's coming from event handlers in child
7893 // windows who return focus to the canvas.
7894 leftIsDown = event.LeftDown();
7895
7896#ifndef __WXOSX__
7897 if (event.LeftDown()) {
7898 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7899 // The menu bar is temporarily visible due to alt having been pressed.
7900 // Clicking will hide it, and do nothing else.
7901 g_bTempShowMenuBar = false;
7902 top_frame::Get()->ApplyGlobalSettings(false);
7903 return (true);
7904 }
7905 }
7906#endif
7907
7908 // Update modifiers here; some window managers never send the key event
7909 m_modkeys = 0;
7910 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7911 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7912
7913#ifdef __WXMSW__
7914 // TODO Test carefully in other platforms, remove ifdef....
7915 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7916 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7917#endif
7918
7919 event.SetEventObject(this);
7920 if (SendMouseEventToPlugins(event))
7921 return (true); // PlugIn did something, and does not want the canvas to
7922 // do anything else
7923
7924 // Capture LeftUp's and time them, unless it already came from the timer.
7925
7926 // Detect end of chart dragging
7927 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7928 StartChartDragInertia();
7929 }
7930
7931 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7932 !singleClickEventIsValid) {
7933 // Ignore the second LeftUp after the DClick.
7934 if (m_DoubleClickTimer->IsRunning()) {
7935 m_DoubleClickTimer->Stop();
7936 return (true);
7937 }
7938
7939 // Save the event for later running if there is no DClick.
7940 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7941 singleClickEvent = event;
7942 singleClickEventIsValid = true;
7943 return (true);
7944 }
7945
7946 // This logic is necessary on MSW to handle the case where
7947 // a context (right-click) menu is dismissed without action
7948 // by clicking on the chart surface.
7949 // We need to avoid an unintentional pan by eating some clicks...
7950#ifdef __WXMSW__
7951 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7952 if (g_click_stop > 0) {
7953 g_click_stop--;
7954 return (true);
7955 }
7956 }
7957#endif
7958
7959 // Kick off the Rotation control timer
7960 if (GetUpMode() == COURSE_UP_MODE) {
7961 m_b_rot_hidef = false;
7962 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7963 } else
7964 pRotDefTimer->Stop();
7965
7966 // Retrigger the route leg / AIS target popup timer
7967 bool bRoll = !g_btouch;
7968#ifdef __ANDROID__
7969 bRoll = g_bRollover;
7970#endif
7971 if (bRoll) {
7972 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7973 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7974 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7975 m_RolloverPopupTimer.Start(
7976 10,
7977 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7978 else
7979 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7980 }
7981
7982 // Retrigger the cursor tracking timer
7983 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7984
7985// Show cursor position on Status Bar, if present
7986// except for GTK, under which status bar updates are very slow
7987// due to Update() call.
7988// In this case, as a workaround, update the status window
7989// after an interval timer (pCurTrackTimer) pops, which will happen
7990// whenever the mouse has stopped moving for specified interval.
7991// See the method OnCursorTrackTimerEvent()
7992#if !defined(__WXGTK__) && !defined(__WXQT__)
7993 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7994#endif
7995
7996 // Send the current cursor lat/lon to all PlugIns requesting it
7997 if (g_pi_manager) {
7998 // Occasionally, MSW will produce nonsense events on right click....
7999 // This results in an error in cursor geo position, so we skip this case
8000 if ((x >= 0) && (y >= 0))
8001 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8002 }
8003
8004 if (!g_btouch) {
8005 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8006 wxPoint p = ClientToScreen(wxPoint(x, y));
8007 }
8008 }
8009
8010 if (1 ) {
8011 // Route Creation Rubber Banding
8012 if (m_routeState >= 2) {
8013 r_rband.x = x;
8014 r_rband.y = y;
8015 m_bDrawingRoute = true;
8016
8017 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8018 Refresh(false);
8019 }
8020
8021 // Measure Tool Rubber Banding
8022 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8023 r_rband.x = x;
8024 r_rband.y = y;
8025 m_bDrawingRoute = true;
8026
8027 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8028 Refresh(false);
8029 }
8030 }
8031 return bret;
8032}
8033
8034int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8035 // On general Right Click
8036 // Look for selectable objects
8037 double slat = lat;
8038 double slon = lon;
8039
8040#if defined(__WXMAC__) || defined(__ANDROID__)
8041 wxScreenDC sdc;
8042 ocpnDC dc(sdc);
8043#else
8044 wxClientDC cdc(GetParent());
8045 ocpnDC dc(cdc);
8046#endif
8047
8048 SelectItem *pFindAIS;
8049 SelectItem *pFindRP;
8050 SelectItem *pFindRouteSeg;
8051 SelectItem *pFindTrackSeg;
8052 SelectItem *pFindCurrent = NULL;
8053 SelectItem *pFindTide = NULL;
8054
8055 // Deselect any current objects
8056 if (m_pSelectedRoute) {
8057 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8058 m_pSelectedRoute->DeSelectRoute();
8059#ifdef ocpnUSE_GL
8060 if (g_bopengl && m_glcc) {
8061 InvalidateGL();
8062 Update();
8063 } else
8064#endif
8065 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8066 }
8067
8068 if (m_pFoundRoutePoint) {
8069 m_pFoundRoutePoint->m_bPtIsSelected = false;
8070 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8071 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8072 }
8073
8076 if (g_btouch && m_pRoutePointEditTarget) {
8077 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8078 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8079 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8080 }
8081
8082 // Get all the selectable things at the cursor
8083 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8084 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8085 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8086 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8087 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8088
8089 if (m_bShowCurrent)
8090 pFindCurrent =
8091 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8092
8093 if (m_bShowTide) // look for tide stations
8094 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8095
8096 int seltype = 0;
8097
8098 // Try for AIS targets first
8099 if (pFindAIS) {
8100 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8101
8102 // Make sure the target data is available
8103 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8104 seltype |= SELTYPE_AISTARGET;
8105 }
8106
8107 // Now examine the various Route parts
8108
8109 m_pFoundRoutePoint = NULL;
8110 if (pFindRP) {
8111 RoutePoint *pFirstVizPoint = NULL;
8112 RoutePoint *pFoundActiveRoutePoint = NULL;
8113 RoutePoint *pFoundVizRoutePoint = NULL;
8114 Route *pSelectedActiveRoute = NULL;
8115 Route *pSelectedVizRoute = NULL;
8116
8117 // There is at least one routepoint, so get the whole list
8118 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8119 SelectableItemList SelList =
8120 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8121 for (SelectItem *pFindSel : SelList) {
8122 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8123
8124 // Get an array of all routes using this point
8125 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8126
8127 // Use route array (if any) to determine actual visibility for this point
8128 bool brp_viz = false;
8129 if (proute_array) {
8130 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8131 Route *pr = (Route *)proute_array->Item(ir);
8132 if (pr->IsVisible()) {
8133 brp_viz = true;
8134 break;
8135 }
8136 }
8137 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8138 // but still exists as a waypoint
8139 brp_viz = prp->IsVisible(); // so treat as isolated point
8140
8141 } else
8142 brp_viz = prp->IsVisible(); // isolated point
8143
8144 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8145
8146 // Use route array to choose the appropriate route
8147 // Give preference to any active route, otherwise select the first visible
8148 // route in the array for this point
8149 m_pSelectedRoute = NULL;
8150 if (proute_array) {
8151 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8152 Route *pr = (Route *)proute_array->Item(ir);
8153 if (pr->m_bRtIsActive) {
8154 pSelectedActiveRoute = pr;
8155 pFoundActiveRoutePoint = prp;
8156 break;
8157 }
8158 }
8159
8160 if (NULL == pSelectedVizRoute) {
8161 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8162 Route *pr = (Route *)proute_array->Item(ir);
8163 if (pr->IsVisible()) {
8164 pSelectedVizRoute = pr;
8165 pFoundVizRoutePoint = prp;
8166 break;
8167 }
8168 }
8169 }
8170
8171 delete proute_array;
8172 }
8173 }
8174
8175 // Now choose the "best" selections
8176 if (pFoundActiveRoutePoint) {
8177 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8178 m_pSelectedRoute = pSelectedActiveRoute;
8179 } else if (pFoundVizRoutePoint) {
8180 m_pFoundRoutePoint = pFoundVizRoutePoint;
8181 m_pSelectedRoute = pSelectedVizRoute;
8182 } else
8183 // default is first visible point in list
8184 m_pFoundRoutePoint = pFirstVizPoint;
8185
8186 if (m_pSelectedRoute) {
8187 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8188 } else if (m_pFoundRoutePoint) {
8189 seltype |= SELTYPE_MARKPOINT;
8190 }
8191
8192 // Highlight the selected point, to verify the proper right click selection
8193 if (m_pFoundRoutePoint) {
8194 m_pFoundRoutePoint->m_bPtIsSelected = true;
8195 wxRect wp_rect;
8196 RoutePointGui(*m_pFoundRoutePoint)
8197 .CalculateDCRect(m_dc_route, this, &wp_rect);
8198 RefreshRect(wp_rect, true);
8199 }
8200 }
8201
8202 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8203 // routes But call the popup handler with identifier appropriate to the type
8204 if (pFindRouteSeg) // there is at least one select item
8205 {
8206 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8207 SelectableItemList SelList =
8208 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8209
8210 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8211 {
8212 // Choose the first visible route containing segment in the list
8213 for (SelectItem *pFindSel : SelList) {
8214 Route *pr = (Route *)pFindSel->m_pData3;
8215 if (pr->IsVisible()) {
8216 m_pSelectedRoute = pr;
8217 break;
8218 }
8219 }
8220 }
8221
8222 if (m_pSelectedRoute) {
8223 if (NULL == m_pFoundRoutePoint)
8224 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8225
8226 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8227 if (m_pSelectedRoute->m_bRtIsSelected) {
8228#ifdef ocpnUSE_GL
8229 if (g_bopengl && m_glcc) {
8230 InvalidateGL();
8231 Update();
8232 } else
8233#endif
8234 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8235 }
8236 seltype |= SELTYPE_ROUTESEGMENT;
8237 }
8238 }
8239
8240 if (pFindTrackSeg) {
8241 m_pSelectedTrack = NULL;
8242 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8243 SelectableItemList SelList =
8244 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8245
8246 // Choose the first visible track containing segment in the list
8247 for (SelectItem *pFindSel : SelList) {
8248 Track *pt = (Track *)pFindSel->m_pData3;
8249 if (pt->IsVisible()) {
8250 m_pSelectedTrack = pt;
8251 break;
8252 }
8253 }
8254 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8255 }
8256
8257#if 0 // disable tide and current graph on right click
8258 {
8259 if (pFindCurrent) {
8260 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8261 seltype |= SELTYPE_CURRENTPOINT;
8262 }
8263
8264 else if (pFindTide) {
8265 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8266 seltype |= SELTYPE_TIDEPOINT;
8267 }
8268 }
8269#endif
8270
8271 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8272
8273 return seltype;
8274}
8275
8276IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8277 // There may be multiple current entries at the same point.
8278 // For example, there often is a current substation (with directions
8279 // specified) co-located with its master. We want to select the
8280 // substation, so that the direction will be properly indicated on the
8281 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8282 // substation)
8283 IDX_entry *pIDX_best_candidate;
8284
8285 SelectItem *pFind = NULL;
8286 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8287 SelectableItemList SelList =
8288 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8289
8290 // Default is first entry
8291 pFind = *SelList.begin();
8292 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8293
8294 auto node = SelList.begin();
8295 if (SelList.size() > 1) {
8296 for (++node; node != SelList.end(); ++node) {
8297 pFind = *node;
8298 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8299 if (pIDX_candidate->IDX_type == 'c') {
8300 pIDX_best_candidate = pIDX_candidate;
8301 break;
8302 }
8303 } // while (node)
8304 } else {
8305 pFind = *SelList.begin();
8306 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8307 }
8308
8309 return pIDX_best_candidate;
8310}
8311void ChartCanvas::CallPopupMenu(int x, int y) {
8312 last_drag.x = x;
8313 last_drag.y = y;
8314 if (m_routeState) { // creating route?
8315 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8316 return;
8317 }
8318
8320
8321 // If tide or current point is selected, then show the TC dialog immediately
8322 // without context menu
8323 if (SELTYPE_CURRENTPOINT == seltype) {
8324 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8325 Refresh(false);
8326 return;
8327 }
8328
8329 if (SELTYPE_TIDEPOINT == seltype) {
8330 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8331 Refresh(false);
8332 return;
8333 }
8334
8335 InvokeCanvasMenu(x, y, seltype);
8336
8337 // Clean up if not deleted in InvokeCanvasMenu
8338 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8339 m_pSelectedRoute->m_bRtIsSelected = false;
8340 }
8341
8342 m_pSelectedRoute = NULL;
8343
8344 if (m_pFoundRoutePoint) {
8345 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8346 m_pFoundRoutePoint->m_bPtIsSelected = false;
8347 }
8348 m_pFoundRoutePoint = NULL;
8349
8350 Refresh(true);
8351 // Refresh(false); // needed for MSW, not GTK Why??
8352}
8353
8354bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8355 // For now just bail out completely if the point clicked is not on the chart
8356 if (std::isnan(m_cursor_lat)) return false;
8357
8358 // Mouse Clicks
8359 bool ret = false; // return true if processed
8360
8361 int x, y, mx, my;
8362 event.GetPosition(&x, &y);
8363 mx = x;
8364 my = y;
8365
8366 // Calculate meaningful SelectRadius
8367 float SelectRadius;
8368 SelectRadius = g_Platform->GetSelectRadiusPix() /
8369 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8370
8372 // We start with Double Click processing. The first left click just starts a
8373 // timer and is remembered, then we actually do something if there is a
8374 // LeftDClick. If there is, the two single clicks are ignored.
8375
8376 if (event.LeftDClick() && (cursor_region == CENTER)) {
8377 m_DoubleClickTimer->Start();
8378 singleClickEventIsValid = false;
8379
8380 double zlat, zlon;
8382 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8383
8384 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8385 if (m_bShowAIS) {
8386 SelectItem *pFindAIS;
8387 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8388
8389 if (pFindAIS) {
8390 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8391 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8392 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8393 }
8394 return true;
8395 }
8396 }
8397
8398 SelectableItemList rpSelList =
8399 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8400 bool b_onRPtarget = false;
8401 for (SelectItem *pFind : rpSelList) {
8402 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8403 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8404 b_onRPtarget = true;
8405 break;
8406 }
8407 }
8408
8409 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8410
8411 // Get and honor the plugin API ContextMenuMask
8412 std::unique_ptr<HostApi> host_api = GetHostApi();
8413 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8414
8415 if (m_pRoutePointEditTarget) {
8416 if (b_onRPtarget) {
8417 if ((api_121->GetContextMenuMask() &
8418 api_121->kContextMenuDisableWaypoint))
8419 return true;
8420 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8421 return true;
8422 } else {
8423 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8424 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8425 if (g_btouch)
8426 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8427 wxRect wp_rect;
8428 RoutePointGui(*m_pRoutePointEditTarget)
8429 .CalculateDCRect(m_dc_route, this, &wp_rect);
8430 m_pRoutePointEditTarget = NULL; // cancel selection
8431 RefreshRect(wp_rect, true);
8432 return true;
8433 }
8434 } else {
8435 auto node = rpSelList.begin();
8436 if (node != rpSelList.end()) {
8437 SelectItem *pFind = *node;
8438 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8439 if (frp) {
8440 wxArrayPtrVoid *proute_array =
8442
8443 // Use route array (if any) to determine actual visibility for this
8444 // point
8445 bool brp_viz = false;
8446 if (proute_array) {
8447 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8448 Route *pr = (Route *)proute_array->Item(ir);
8449 if (pr->IsVisible()) {
8450 brp_viz = true;
8451 break;
8452 }
8453 }
8454 delete proute_array;
8455 if (!brp_viz &&
8456 frp->IsShared()) // is not visible as part of route, but
8457 // still exists as a waypoint
8458 brp_viz = frp->IsVisible(); // so treat as isolated point
8459 } else
8460 brp_viz = frp->IsVisible(); // isolated point
8461
8462 if (brp_viz) {
8463 if ((api_121->GetContextMenuMask() &
8464 api_121->kContextMenuDisableWaypoint))
8465 return true;
8466
8467 ShowMarkPropertiesDialog(frp);
8468 return true;
8469 }
8470 }
8471 }
8472 }
8473
8474 SelectItem *cursorItem;
8475
8476 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8477 if (cursorItem) {
8478 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8479 return true;
8480 Route *pr = (Route *)cursorItem->m_pData3;
8481 if (pr->IsVisible()) {
8482 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8483 return true;
8484 }
8485 }
8486
8487 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8488 if (cursorItem) {
8489 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8490 return true;
8491 Track *pt = (Track *)cursorItem->m_pData3;
8492 if (pt->IsVisible()) {
8493 ShowTrackPropertiesDialog(pt);
8494 return true;
8495 }
8496 }
8497
8498 // Tide and current points
8499 SelectItem *pFindCurrent = NULL;
8500 SelectItem *pFindTide = NULL;
8501
8502 if (m_bShowCurrent) { // look for current stations
8503 pFindCurrent =
8504 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8505 if (pFindCurrent) {
8506 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8507 // Check for plugin graphic override
8508 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8509 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8510 PlugInContainer *pic = plugin_array->Item(i);
8511 if (pic->m_enabled && pic->m_init_state &&
8512 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8513 if (ptcmgr) {
8514 TCClickInfo info;
8515 if (m_pIDXCandidate) {
8516 info.point_type = CURRENT_STATION;
8517 info.index = m_pIDXCandidate->IDX_rec_num;
8518 info.name = m_pIDXCandidate->IDX_station_name;
8519 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8520 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8521 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8522 };
8523 auto plugin =
8524 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8525 if (plugin) plugin->OnTideCurrentClick(info);
8526 return true;
8527 }
8528 }
8529 }
8530 }
8531
8532 // Default action
8533 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8534 Refresh(false);
8535 return true;
8536 }
8537 }
8538
8539 if (m_bShowTide) { // look for tide stations
8540 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8541 if (pFindTide) {
8542 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8543 // Check for plugin graphic override
8544 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8545 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8546 PlugInContainer *pic = plugin_array->Item(i);
8547 if (pic->m_enabled && pic->m_init_state &&
8548 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8549 if (ptcmgr) {
8550 TCClickInfo info;
8551 if (m_pIDXCandidate) {
8552 info.point_type = TIDE_STATION;
8553 info.index = m_pIDXCandidate->IDX_rec_num;
8554 info.name = m_pIDXCandidate->IDX_station_name;
8555 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8556 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8557 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8558 };
8559 auto plugin =
8560 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8561 if (plugin) plugin->OnTideCurrentClick(info);
8562 return true;
8563 }
8564 }
8565 }
8566 }
8567
8568 // Default action
8569 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8570 Refresh(false);
8571 return true;
8572 }
8573 }
8574
8575 // Found no object to act on, so show chart info.
8576 ShowObjectQueryWindow(x, y, zlat, zlon);
8577 return true;
8578 }
8579
8581 if (event.LeftDown()) {
8582 // This really should not be needed, but....
8583 // on Windows, when using wxAUIManager, sometimes the focus is lost
8584 // when clicking into another pane, e.g.the AIS target list, and then back
8585 // to this pane. Oddly, some mouse events are not lost, however. Like this
8586 // one....
8587 SetFocus();
8588
8589 last_drag.x = mx;
8590 last_drag.y = my;
8591 leftIsDown = true;
8592
8593 if (!g_btouch) {
8594 if (m_routeState) // creating route?
8595 {
8596 double rlat, rlon;
8597 bool appending = false;
8598 bool inserting = false;
8599 Route *tail = 0;
8600
8601 SetCursor(*pCursorPencil);
8602 rlat = m_cursor_lat;
8603 rlon = m_cursor_lon;
8604
8605 m_bRouteEditing = true;
8606
8607 if (m_routeState == 1) {
8608 m_pMouseRoute = new Route();
8609 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8610 pRouteList->push_back(m_pMouseRoute);
8611 r_rband.x = x;
8612 r_rband.y = y;
8613 }
8614
8615 // Check to see if there is a nearby point which may be reused
8616 RoutePoint *pMousePoint = NULL;
8617
8618 // Calculate meaningful SelectRadius
8619 double nearby_radius_meters =
8620 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8621
8622 RoutePoint *pNearbyPoint =
8623 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8624 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8625 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8626 wxArrayPtrVoid *proute_array =
8627 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8628
8629 // Use route array (if any) to determine actual visibility for this
8630 // point
8631 bool brp_viz = false;
8632 if (proute_array) {
8633 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8634 Route *pr = (Route *)proute_array->Item(ir);
8635 if (pr->IsVisible()) {
8636 brp_viz = true;
8637 break;
8638 }
8639 }
8640 delete proute_array;
8641 if (!brp_viz &&
8642 pNearbyPoint->IsShared()) // is not visible as part of route,
8643 // but still exists as a waypoint
8644 brp_viz =
8645 pNearbyPoint->IsVisible(); // so treat as isolated point
8646 } else
8647 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8648
8649 if (brp_viz) {
8650 wxString msg = _("Use nearby waypoint?");
8651 // Don't add a mark without name to the route. Name it if needed
8652 const bool noname(pNearbyPoint->GetName() == "");
8653 if (noname) {
8654 msg =
8655 _("Use nearby nameless waypoint and name it M with"
8656 " a unique number?");
8657 }
8658 // Avoid route finish on focus change for message dialog
8659 m_FinishRouteOnKillFocus = false;
8660 int dlg_return =
8661 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8662 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8663 m_FinishRouteOnKillFocus = true;
8664 if (dlg_return == wxID_YES) {
8665 if (noname) {
8666 if (m_pMouseRoute) {
8667 int last_wp_num = m_pMouseRoute->GetnPoints();
8668 // AP-ECRMB will truncate to 6 characters
8669 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8670 wxString wp_name = wxString::Format(
8671 "M%002i-%s", last_wp_num + 1, guid_short);
8672 pNearbyPoint->SetName(wp_name);
8673 } else
8674 pNearbyPoint->SetName("WPXX");
8675 }
8676 pMousePoint = pNearbyPoint;
8677
8678 // Using existing waypoint, so nothing to delete for undo.
8679 if (m_routeState > 1)
8680 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8681 Undo_HasParent, NULL);
8682
8683 tail =
8684 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8685 bool procede = false;
8686 if (tail) {
8687 procede = true;
8688 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8689 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8690 procede = false;
8691 }
8692
8693 if (procede) {
8694 int dlg_return;
8695 m_FinishRouteOnKillFocus = false;
8696 if (m_routeState ==
8697 1) { // first point in new route, preceeding route to be
8698 // added? Not touch case
8699
8700 wxString dmsg =
8701 _("Insert first part of this route in the new route?");
8702 if (tail->GetIndexOf(pMousePoint) ==
8703 tail->GetnPoints()) // Starting on last point of another
8704 // route?
8705 dmsg = _("Insert this route in the new route?");
8706
8707 if (tail->GetIndexOf(pMousePoint) > 0) { // Anything to do?
8708 dlg_return = OCPNMessageBox(
8709 this, dmsg, _("OpenCPN Route Create"),
8710 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8711 m_FinishRouteOnKillFocus = true;
8712
8713 if (dlg_return == wxID_YES) {
8714 inserting = true; // part of the other route will be
8715 // preceeding the new route
8716 }
8717 }
8718 } else {
8719 wxString dmsg =
8720 _("Append last part of this route to the new route?");
8721 if (tail->GetIndexOf(pMousePoint) == 1)
8722 dmsg = _(
8723 "Append this route to the new route?"); // Picking the
8724 // first point
8725 // of another
8726 // route?
8727
8728 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8729 dlg_return = OCPNMessageBox(
8730 this, dmsg, _("OpenCPN Route Create"),
8731 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8732 m_FinishRouteOnKillFocus = true;
8733
8734 if (dlg_return == wxID_YES) {
8735 appending = true; // part of the other route will be
8736 // appended to the new route
8737 }
8738 }
8739 }
8740 }
8741
8742 // check all other routes to see if this point appears in any
8743 // other route If it appears in NO other route, then it should e
8744 // considered an isolated mark
8745 if (!FindRouteContainingWaypoint(pMousePoint))
8746 pMousePoint->SetShared(true);
8747 }
8748 }
8749 }
8750
8751 if (NULL == pMousePoint) { // need a new point
8752 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8753 "", wxEmptyString);
8754 pMousePoint->SetNameShown(false);
8755
8756 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8757
8758 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8759
8760 if (m_routeState > 1)
8761 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8762 Undo_IsOrphanded, NULL);
8763 }
8764
8765 if (m_pMouseRoute) {
8766 if (m_routeState == 1) {
8767 // First point in the new route.
8768 m_pMouseRoute->AddPoint(pMousePoint);
8769 } else {
8770 if (m_pMouseRoute->m_NextLegGreatCircle) {
8771 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8772 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8773 &rhumbBearing, &rhumbDist);
8774 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8775 rlat, &gcDist, &gcBearing, NULL);
8776 double gcDistNM = gcDist / 1852.0;
8777
8778 // Empirically found expression to get reasonable route segments.
8779 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8780 pow(rhumbDist - gcDistNM - 1, 0.5);
8781
8782 wxString msg;
8783 msg << _("For this leg the Great Circle route is ")
8784 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8785 << _(" shorter than rhumbline.\n\n")
8786 << _("Would you like include the Great Circle routing points "
8787 "for this leg?");
8788
8789 m_FinishRouteOnKillFocus = false;
8790 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8791 // does not fully capture mouse
8792
8793 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8794 wxYES_NO | wxNO_DEFAULT);
8795
8796 m_disable_edge_pan = false;
8797 m_FinishRouteOnKillFocus = true;
8798
8799 if (answer == wxID_YES) {
8800 RoutePoint *gcPoint;
8801 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8802 wxRealPoint gcCoord;
8803
8804 for (int i = 1; i <= segmentCount; i++) {
8805 double fraction = (double)i * (1.0 / (double)segmentCount);
8806 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8807 gcDist * fraction, gcBearing,
8808 &gcCoord.x, &gcCoord.y, NULL);
8809
8810 if (i < segmentCount) {
8811 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8812 wxEmptyString);
8813 gcPoint->SetNameShown(false);
8814 // pConfig->AddNewWayPoint(gcPoint, -1);
8815 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8816
8817 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8818 gcPoint);
8819 } else {
8820 gcPoint = pMousePoint; // Last point, previously exsisting!
8821 }
8822
8823 m_pMouseRoute->AddPoint(gcPoint);
8824 pSelect->AddSelectableRouteSegment(
8825 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8826 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8827 prevGcPoint = gcPoint;
8828 }
8829
8830 undo->CancelUndoableAction(true);
8831
8832 } else {
8833 m_pMouseRoute->AddPoint(pMousePoint);
8834 pSelect->AddSelectableRouteSegment(
8835 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8836 pMousePoint, m_pMouseRoute);
8837 undo->AfterUndoableAction(m_pMouseRoute);
8838 }
8839 } else {
8840 // Ordinary rhumblinesegment.
8841 m_pMouseRoute->AddPoint(pMousePoint);
8842 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8843 rlon, m_prev_pMousePoint,
8844 pMousePoint, m_pMouseRoute);
8845 undo->AfterUndoableAction(m_pMouseRoute);
8846 }
8847 }
8848 }
8849 m_prev_rlat = rlat;
8850 m_prev_rlon = rlon;
8851 m_prev_pMousePoint = pMousePoint;
8852 if (m_pMouseRoute)
8853 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8854
8855 m_routeState++;
8856
8857 if (appending ||
8858 inserting) { // Appending a route or making a new route
8859 int connect = tail->GetIndexOf(pMousePoint);
8860 if (connect == 0) {
8861 inserting = false; // there is nothing to insert
8862 appending = true; // so append
8863 }
8864 int length = tail->GetnPoints();
8865
8866 int i;
8867 int start, stop;
8868 if (appending) {
8869 start = connect + 1;
8870 stop = length;
8871 } else { // inserting
8872 start = 1;
8873 stop = connect + 1;
8874 // Remove the first and only point of the new route
8875 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8876 }
8877 for (i = start; i <= stop; i++) {
8878 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8879 if (m_pMouseRoute)
8880 m_pMouseRoute->m_lastMousePointIndex =
8881 m_pMouseRoute->GetnPoints();
8882 m_routeState++;
8883 top_frame::Get()->RefreshAllCanvas();
8884 ret = true;
8885 }
8886 m_prev_rlat =
8887 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8888 m_prev_rlon =
8889 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8890 m_pMouseRoute->FinalizeForRendering();
8891 }
8892 top_frame::Get()->RefreshAllCanvas();
8893 ret = true;
8894 }
8895
8896 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8897 {
8898 SetCursor(*pCursorPencil);
8899
8900 if (!m_pMeasureRoute) {
8901 m_pMeasureRoute = new Route();
8902 pRouteList->push_back(m_pMeasureRoute);
8903 }
8904
8905 if (m_nMeasureState == 1) {
8906 r_rband.x = x;
8907 r_rband.y = y;
8908 }
8909
8910 RoutePoint *pMousePoint =
8911 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8912 wxEmptyString, wxEmptyString);
8913 pMousePoint->m_bShowName = false;
8914 pMousePoint->SetShowWaypointRangeRings(false);
8915
8916 m_pMeasureRoute->AddPoint(pMousePoint);
8917
8918 m_prev_rlat = m_cursor_lat;
8919 m_prev_rlon = m_cursor_lon;
8920 m_prev_pMousePoint = pMousePoint;
8921 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8922
8923 m_nMeasureState++;
8924 top_frame::Get()->RefreshAllCanvas();
8925 ret = true;
8926 }
8927
8928 else {
8929 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8930 }
8931 } // !g_btouch
8932 else { // g_btouch
8933 m_last_touch_down_pos = event.GetPosition();
8934
8935 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8936 // if near screen edge, pan with injection
8937 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8938 // return;
8939 // }
8940 }
8941 }
8942
8943 if (ret) return true;
8944 }
8945
8946 if (event.Dragging()) {
8947 // in touch screen mode ensure the finger/cursor is on the selected point's
8948 // radius to allow dragging
8949 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8950 if (g_btouch) {
8951 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8952 SelectItem *pFind = NULL;
8953 SelectableItemList SelList = pSelect->FindSelectionList(
8954 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8955 for (SelectItem *pFind : SelList) {
8956 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8957 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8958 }
8959 }
8960
8961 // Check for use of dragHandle
8962 if (m_pRoutePointEditTarget &&
8963 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8964 SelectItem *pFind = NULL;
8965 SelectableItemList SelList = pSelect->FindSelectionList(
8966 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8967 for (SelectItem *pFind : SelList) {
8968 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8969 if (m_pRoutePointEditTarget == frp) {
8970 m_bIsInRadius = true;
8971 break;
8972 }
8973 }
8974
8975 if (!m_dragoffsetSet) {
8976 RoutePointGui(*m_pRoutePointEditTarget)
8977 .PresetDragOffset(this, mouse_x, mouse_y);
8978 m_dragoffsetSet = true;
8979 }
8980 }
8981 }
8982
8983 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8984 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8985
8986 if (NULL == g_pMarkInfoDialog) {
8987 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8988 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8989 DraggingAllowed = false;
8990
8991 if (m_pRoutePointEditTarget &&
8992 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8993 DraggingAllowed = false;
8994
8995 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8996
8997 if (DraggingAllowed) {
8998 if (!undo->InUndoableAction()) {
8999 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9000 Undo_NeedsCopy, m_pFoundPoint);
9001 }
9002
9003 // Get the update rectangle for the union of the un-edited routes
9004 wxRect pre_rect;
9005
9006 if (!g_bopengl && m_pEditRouteArray) {
9007 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9008 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9009 // Need to validate route pointer
9010 // Route may be gone due to drgging close to ownship with
9011 // "Delete On Arrival" state set, as in the case of
9012 // navigating to an isolated waypoint on a temporary route
9013 if (g_pRouteMan->IsRouteValid(pr)) {
9014 wxRect route_rect;
9015 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9016 pre_rect.Union(route_rect);
9017 }
9018 }
9019 }
9020
9021 double new_cursor_lat = m_cursor_lat;
9022 double new_cursor_lon = m_cursor_lon;
9023
9024 if (CheckEdgePan(x, y, true, 5, 2))
9025 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9026
9027 // update the point itself
9028 if (g_btouch) {
9029 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9030 // new_cursor_lat, new_cursor_lon);
9031 RoutePointGui(*m_pRoutePointEditTarget)
9032 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9033 // update the Drag Handle entry in the pSelect list
9034 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9035 m_pRoutePointEditTarget,
9036 SELTYPE_DRAGHANDLE);
9037 m_pFoundPoint->m_slat =
9038 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9039 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9040 } else {
9041 m_pRoutePointEditTarget->m_lat =
9042 new_cursor_lat; // update the RoutePoint entry
9043 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9044 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9045 m_pFoundPoint->m_slat =
9046 new_cursor_lat; // update the SelectList entry
9047 m_pFoundPoint->m_slon = new_cursor_lon;
9048 }
9049
9050 // Update the MarkProperties Dialog, if currently shown
9051 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9052 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9053 g_pMarkInfoDialog->UpdateProperties(true);
9054 }
9055
9056 if (g_bopengl) {
9057 // InvalidateGL();
9058 Refresh(false);
9059 } else {
9060 // Get the update rectangle for the edited route
9061 wxRect post_rect;
9062
9063 if (m_pEditRouteArray) {
9064 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9065 ir++) {
9066 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9067 if (g_pRouteMan->IsRouteValid(pr)) {
9068 wxRect route_rect;
9069 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9070 post_rect.Union(route_rect);
9071 }
9072 }
9073 }
9074
9075 // Invalidate the union region
9076 pre_rect.Union(post_rect);
9077 RefreshRect(pre_rect, false);
9078 }
9079 top_frame::Get()->RefreshCanvasOther(this);
9080 m_bRoutePoinDragging = true;
9081 }
9082 ret = true;
9083 } // if Route Editing
9084
9085 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9086 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9087
9088 if (NULL == g_pMarkInfoDialog) {
9089 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9090 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9091 DraggingAllowed = false;
9092
9093 if (m_pRoutePointEditTarget &&
9094 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9095 DraggingAllowed = false;
9096
9097 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9098
9099 if (DraggingAllowed) {
9100 if (!undo->InUndoableAction()) {
9101 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9102 Undo_NeedsCopy, m_pFoundPoint);
9103 }
9104
9105 // The mark may be an anchorwatch
9106 double lpp1 = 0.;
9107 double lpp2 = 0.;
9108 double lppmax;
9109
9110 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9111 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9112 }
9113 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9114 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9115 }
9116 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9117
9118 // Get the update rectangle for the un-edited mark
9119 wxRect pre_rect;
9120 if (!g_bopengl) {
9121 RoutePointGui(*m_pRoutePointEditTarget)
9122 .CalculateDCRect(m_dc_route, this, &pre_rect);
9123 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9124 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9125 (int)(lppmax - (pre_rect.height / 2)));
9126 }
9127
9128 // update the point itself
9129 if (g_btouch) {
9130 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9131 // m_cursor_lat, m_cursor_lon);
9132 RoutePointGui(*m_pRoutePointEditTarget)
9133 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9134 // update the Drag Handle entry in the pSelect list
9135 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9136 m_pRoutePointEditTarget,
9137 SELTYPE_DRAGHANDLE);
9138 m_pFoundPoint->m_slat =
9139 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9140 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9141 } else {
9142 m_pRoutePointEditTarget->m_lat =
9143 m_cursor_lat; // update the RoutePoint entry
9144 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9145 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9146 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9147 m_pFoundPoint->m_slon = m_cursor_lon;
9148 }
9149
9150 // Update the MarkProperties Dialog, if currently shown
9151 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9152 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9153 g_pMarkInfoDialog->UpdateProperties(true);
9154 }
9155
9156 // Invalidate the union region
9157 if (g_bopengl) {
9158 if (!g_btouch) InvalidateGL();
9159 Refresh(false);
9160 } else {
9161 // Get the update rectangle for the edited mark
9162 wxRect post_rect;
9163 RoutePointGui(*m_pRoutePointEditTarget)
9164 .CalculateDCRect(m_dc_route, this, &post_rect);
9165 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9166 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9167 (int)(lppmax - (post_rect.height / 2)));
9168
9169 // Invalidate the union region
9170 pre_rect.Union(post_rect);
9171 RefreshRect(pre_rect, false);
9172 }
9173 top_frame::Get()->RefreshCanvasOther(this);
9174 m_bRoutePoinDragging = true;
9175 }
9176 ret = g_btouch ? m_bRoutePoinDragging : true;
9177 }
9178
9179 if (ret) return true;
9180 } // dragging
9181
9182 if (event.LeftUp()) {
9183 bool b_startedit_route = false;
9184 m_dragoffsetSet = false;
9185
9186 if (g_btouch) {
9187 m_bChartDragging = false;
9188 m_bIsInRadius = false;
9189
9190 if (m_routeState) // creating route?
9191 {
9192 if (m_ignore_next_leftup) {
9193 m_ignore_next_leftup = false;
9194 return false;
9195 }
9196
9197 if (m_bedge_pan) {
9198 m_bedge_pan = false;
9199 return false;
9200 }
9201
9202 double rlat, rlon;
9203 bool appending = false;
9204 bool inserting = false;
9205 Route *tail = 0;
9206
9207 rlat = m_cursor_lat;
9208 rlon = m_cursor_lon;
9209
9210 if (m_pRoutePointEditTarget) {
9211 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9212 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9213 if (!g_bopengl) {
9214 wxRect wp_rect;
9215 RoutePointGui(*m_pRoutePointEditTarget)
9216 .CalculateDCRect(m_dc_route, this, &wp_rect);
9217 RefreshRect(wp_rect, true);
9218 }
9219 m_pRoutePointEditTarget = NULL;
9220 }
9221 m_bRouteEditing = true;
9222
9223 if (m_routeState == 1) {
9224 m_pMouseRoute = new Route();
9225 m_pMouseRoute->SetHiLite(50);
9226 pRouteList->push_back(m_pMouseRoute);
9227 r_rband.x = x;
9228 r_rband.y = y;
9229 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9230 }
9231
9232 // Check to see if there is a nearby point which may be reused
9233 RoutePoint *pMousePoint = NULL;
9234
9235 // Calculate meaningful SelectRadius
9236 double nearby_radius_meters =
9237 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9238
9239 RoutePoint *pNearbyPoint =
9240 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9241 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9242 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9243 int dlg_return;
9244#ifndef __WXOSX__
9245 m_FinishRouteOnKillFocus =
9246 false; // Avoid route finish on focus change for message dialog
9247 dlg_return = OCPNMessageBox(
9248 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9249 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9250 m_FinishRouteOnKillFocus = true;
9251#else
9252 dlg_return = wxID_YES;
9253#endif
9254 if (dlg_return == wxID_YES) {
9255 pMousePoint = pNearbyPoint;
9256
9257 // Using existing waypoint, so nothing to delete for undo.
9258 if (m_routeState > 1)
9259 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9260 Undo_HasParent, NULL);
9261 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9262
9263 bool procede = false;
9264 if (tail) {
9265 procede = true;
9266 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9267 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9268 procede = false;
9269 }
9270
9271 if (procede) {
9272 int dlg_return;
9273 m_FinishRouteOnKillFocus = false;
9274 if (m_routeState == 1) { // first point in new route, preceeding
9275 // route to be added? touch case
9276
9277 wxString dmsg =
9278 _("Insert first part of this route in the new route?");
9279 if (tail->GetIndexOf(pMousePoint) ==
9280 tail->GetnPoints()) // Starting on last point of another
9281 // route?
9282 dmsg = _("Insert this route in the new route?");
9283
9284 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9285 dlg_return =
9286 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9287 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9288 m_FinishRouteOnKillFocus = true;
9289
9290 if (dlg_return == wxID_YES) {
9291 inserting = true; // part of the other route will be
9292 // preceeding the new route
9293 }
9294 }
9295 } else {
9296 wxString dmsg =
9297 _("Append last part of this route to the new route?");
9298 if (tail->GetIndexOf(pMousePoint) == 1)
9299 dmsg = _(
9300 "Append this route to the new route?"); // Picking the
9301 // first point of
9302 // another route?
9303
9304 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9305 dlg_return =
9306 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9307 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9308 m_FinishRouteOnKillFocus = true;
9309
9310 if (dlg_return == wxID_YES) {
9311 appending = true; // part of the other route will be
9312 // appended to the new route
9313 }
9314 }
9315 }
9316 }
9317
9318 // check all other routes to see if this point appears in any other
9319 // route If it appears in NO other route, then it should e
9320 // considered an isolated mark
9321 if (!FindRouteContainingWaypoint(pMousePoint))
9322 pMousePoint->SetShared(true);
9323 }
9324 }
9325
9326 if (NULL == pMousePoint) { // need a new point
9327 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9328 "", wxEmptyString);
9329 pMousePoint->SetNameShown(false);
9330
9331 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9332
9333 if (m_routeState > 1)
9334 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9335 Undo_IsOrphanded, NULL);
9336 }
9337
9338 if (m_routeState == 1) {
9339 // First point in the route.
9340 m_pMouseRoute->AddPoint(pMousePoint);
9341 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9342
9343 } else {
9344 if (m_pMouseRoute->m_NextLegGreatCircle) {
9345 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9346 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9347 &rhumbBearing, &rhumbDist);
9348 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9349 &gcDist, &gcBearing, NULL);
9350 double gcDistNM = gcDist / 1852.0;
9351
9352 // Empirically found expression to get reasonable route segments.
9353 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9354 pow(rhumbDist - gcDistNM - 1, 0.5);
9355
9356 wxString msg;
9357 msg << _("For this leg the Great Circle route is ")
9358 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9359 << _(" shorter than rhumbline.\n\n")
9360 << _("Would you like include the Great Circle routing points "
9361 "for this leg?");
9362
9363#ifndef __WXOSX__
9364 m_FinishRouteOnKillFocus = false;
9365 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9366 wxYES_NO | wxNO_DEFAULT);
9367 m_FinishRouteOnKillFocus = true;
9368#else
9369 int answer = wxID_NO;
9370#endif
9371
9372 if (answer == wxID_YES) {
9373 RoutePoint *gcPoint;
9374 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9375 wxRealPoint gcCoord;
9376
9377 for (int i = 1; i <= segmentCount; i++) {
9378 double fraction = (double)i * (1.0 / (double)segmentCount);
9379 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9380 gcDist * fraction, gcBearing,
9381 &gcCoord.x, &gcCoord.y, NULL);
9382
9383 if (i < segmentCount) {
9384 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9385 wxEmptyString);
9386 gcPoint->SetNameShown(false);
9387 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9388 gcPoint);
9389 } else {
9390 gcPoint = pMousePoint; // Last point, previously exsisting!
9391 }
9392
9393 m_pMouseRoute->AddPoint(gcPoint);
9394 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9395
9396 pSelect->AddSelectableRouteSegment(
9397 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9398 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9399 prevGcPoint = gcPoint;
9400 }
9401
9402 undo->CancelUndoableAction(true);
9403
9404 } else {
9405 m_pMouseRoute->AddPoint(pMousePoint);
9406 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9407 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9408 rlon, m_prev_pMousePoint,
9409 pMousePoint, m_pMouseRoute);
9410 undo->AfterUndoableAction(m_pMouseRoute);
9411 }
9412 } else {
9413 // Ordinary rhumblinesegment.
9414 m_pMouseRoute->AddPoint(pMousePoint);
9415 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9416
9417 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9418 rlon, m_prev_pMousePoint,
9419 pMousePoint, m_pMouseRoute);
9420 undo->AfterUndoableAction(m_pMouseRoute);
9421 }
9422 }
9423
9424 m_prev_rlat = rlat;
9425 m_prev_rlon = rlon;
9426 m_prev_pMousePoint = pMousePoint;
9427 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9428
9429 m_routeState++;
9430
9431 if (appending ||
9432 inserting) { // Appending a route or making a new route
9433 int connect = tail->GetIndexOf(pMousePoint);
9434 if (connect == 1) {
9435 inserting = false; // there is nothing to insert
9436 appending = true; // so append
9437 }
9438 int length = tail->GetnPoints();
9439
9440 int i;
9441 int start, stop;
9442 if (appending) {
9443 start = connect + 1;
9444 stop = length;
9445 } else { // inserting
9446 start = 1;
9447 stop = connect;
9448 m_pMouseRoute->RemovePoint(
9449 m_pMouseRoute
9450 ->GetLastPoint()); // Remove the first and only point
9451 }
9452 for (i = start; i <= stop; i++) {
9453 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9454 if (m_pMouseRoute)
9455 m_pMouseRoute->m_lastMousePointIndex =
9456 m_pMouseRoute->GetnPoints();
9457 m_routeState++;
9458 top_frame::Get()->RefreshAllCanvas();
9459 ret = true;
9460 }
9461 m_prev_rlat =
9462 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9463 m_prev_rlon =
9464 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9465 m_pMouseRoute->FinalizeForRendering();
9466 }
9467
9468 Refresh(true);
9469 ret = true;
9470 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9471 {
9472 if (m_bedge_pan) {
9473 m_bedge_pan = false;
9474 return false;
9475 }
9476
9477 if (m_ignore_next_leftup) {
9478 m_ignore_next_leftup = false;
9479 return false;
9480 }
9481
9482 if (m_nMeasureState == 1) {
9483 m_pMeasureRoute = new Route();
9484 pRouteList->push_back(m_pMeasureRoute);
9485 r_rband.x = x;
9486 r_rband.y = y;
9487 }
9488
9489 if (m_pMeasureRoute) {
9490 RoutePoint *pMousePoint =
9491 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9492 wxEmptyString, wxEmptyString);
9493 pMousePoint->m_bShowName = false;
9494
9495 m_pMeasureRoute->AddPoint(pMousePoint);
9496
9497 m_prev_rlat = m_cursor_lat;
9498 m_prev_rlon = m_cursor_lon;
9499 m_prev_pMousePoint = pMousePoint;
9500 m_pMeasureRoute->m_lastMousePointIndex =
9501 m_pMeasureRoute->GetnPoints();
9502
9503 m_nMeasureState++;
9504 } else {
9505 CancelMeasureRoute();
9506 }
9507
9508 Refresh(true);
9509 ret = true;
9510 } else {
9511 bool bSelectAllowed = true;
9512 if (NULL == g_pMarkInfoDialog) {
9513 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9514 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9515 bSelectAllowed = false;
9516
9517 // Avoid accidental selection of routepoint if last touchdown started
9518 // a significant chart drag operation
9519 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9520 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9521 significant_drag) ||
9522 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9523 significant_drag)) {
9524 bSelectAllowed = false;
9525 }
9526
9527 /*if this left up happens at the end of a route point dragging and if
9528 the cursor/thumb is on the draghandle icon, not on the point iself a new
9529 selection will select nothing and the drag will never be ended, so the
9530 legs around this point never selectable. At this step we don't need a
9531 new selection, just keep the previoulsly selected and dragged point */
9532 if (m_bRoutePoinDragging) bSelectAllowed = false;
9533
9534 if (bSelectAllowed) {
9535 bool b_was_editing_mark = m_bMarkEditing;
9536 bool b_was_editing_route = m_bRouteEditing;
9537 FindRoutePointsAtCursor(SelectRadius,
9538 true); // Possibly selecting a point in a
9539 // route for later dragging
9540
9541 /*route and a mark points in layer can't be dragged so should't be
9542 * selected and no draghandle icon*/
9543 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9544 m_pRoutePointEditTarget = NULL;
9545
9546 if (!b_was_editing_route) {
9547 if (m_pEditRouteArray) {
9548 b_startedit_route = true;
9549
9550 // Hide the track and route rollover during route point edit, not
9551 // needed, and may be confusing
9552 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9553 m_pTrackRolloverWin->IsActive(false);
9554 }
9555 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9556 m_pRouteRolloverWin->IsActive(false);
9557 }
9558
9559 wxRect pre_rect;
9560 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9561 ir++) {
9562 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9563 // Need to validate route pointer
9564 // Route may be gone due to drgging close to ownship with
9565 // "Delete On Arrival" state set, as in the case of
9566 // navigating to an isolated waypoint on a temporary route
9567 if (g_pRouteMan->IsRouteValid(pr)) {
9568 // pr->SetHiLite(50);
9569 wxRect route_rect;
9570 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9571 pre_rect.Union(route_rect);
9572 }
9573 }
9574 RefreshRect(pre_rect, true);
9575 }
9576 } else {
9577 b_startedit_route = false;
9578 }
9579
9580 // Mark editing in touch mode, left-up event.
9581 if (m_pRoutePointEditTarget) {
9582 if (b_was_editing_mark ||
9583 b_was_editing_route) { // kill previous hilight
9584 if (m_lastRoutePointEditTarget) {
9585 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9586 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9587 RoutePointGui(*m_lastRoutePointEditTarget)
9588 .EnableDragHandle(false);
9589 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9590 SELTYPE_DRAGHANDLE);
9591 }
9592 }
9593
9594 if (m_pRoutePointEditTarget) {
9595 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9596 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9597 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9598 wxPoint2DDouble dragHandlePoint =
9599 RoutePointGui(*m_pRoutePointEditTarget)
9600 .GetDragHandlePoint(this);
9601 pSelect->AddSelectablePoint(
9602 dragHandlePoint.m_y, dragHandlePoint.m_x,
9603 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9604 }
9605 } else { // Deselect everything
9606 if (m_lastRoutePointEditTarget) {
9607 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9608 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9609 RoutePointGui(*m_lastRoutePointEditTarget)
9610 .EnableDragHandle(false);
9611 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9612 SELTYPE_DRAGHANDLE);
9613
9614 // Clear any routes being edited, probably orphans
9615 wxArrayPtrVoid *lastEditRouteArray =
9617 m_lastRoutePointEditTarget);
9618 if (lastEditRouteArray) {
9619 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9620 ir++) {
9621 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9622 if (g_pRouteMan->IsRouteValid(pr)) {
9623 pr->m_bIsBeingEdited = false;
9624 }
9625 }
9626 delete lastEditRouteArray;
9627 }
9628 }
9629 }
9630
9631 // Do the refresh
9632
9633 if (g_bopengl) {
9634 InvalidateGL();
9635 Refresh(false);
9636 } else {
9637 if (m_lastRoutePointEditTarget) {
9638 wxRect wp_rect;
9639 RoutePointGui(*m_lastRoutePointEditTarget)
9640 .CalculateDCRect(m_dc_route, this, &wp_rect);
9641 RefreshRect(wp_rect, true);
9642 }
9643
9644 if (m_pRoutePointEditTarget) {
9645 wxRect wp_rect;
9646 RoutePointGui(*m_pRoutePointEditTarget)
9647 .CalculateDCRect(m_dc_route, this, &wp_rect);
9648 RefreshRect(wp_rect, true);
9649 }
9650 }
9651 }
9652 } // bSelectAllowed
9653
9654 // Check to see if there is a route or AIS target under the cursor
9655 // If so, start the rollover timer which creates the popup
9656 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9657 bool b_start_rollover = false;
9658 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9659 SelectItem *pFind = pSelectAIS->FindSelection(
9660 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9661 if (pFind) b_start_rollover = true;
9662 }
9663
9664 if (!b_start_rollover && !b_startedit_route) {
9665 SelectableItemList SelList = pSelect->FindSelectionList(
9666 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9667 for (SelectItem *pFindSel : SelList) {
9668 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9669 if (pr && pr->IsVisible()) {
9670 b_start_rollover = true;
9671 break;
9672 }
9673 } // while
9674 }
9675
9676 if (!b_start_rollover && !b_startedit_route) {
9677 SelectableItemList SelList = pSelect->FindSelectionList(
9678 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9679 for (SelectItem *pFindSel : SelList) {
9680 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9681 if (tr && tr->IsVisible()) {
9682 b_start_rollover = true;
9683 break;
9684 }
9685 } // while
9686 }
9687
9688 if (b_start_rollover)
9689 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9690 wxTIMER_ONE_SHOT);
9691 Route *tail = 0;
9692 Route *current = 0;
9693 bool appending = false;
9694 bool inserting = false;
9695 int connect = 0;
9696 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9697 // drag
9698 if (m_pRoutePointEditTarget) {
9699 // Check to see if there is a nearby point which may replace the
9700 // dragged one
9701 RoutePoint *pMousePoint = NULL;
9702
9703 int index_last;
9704 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9705 double nearby_radius_meters =
9706 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9707 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9708 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9709 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9710 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9711 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9712 bool duplicate =
9713 false; // ensure we won't create duplicate point in routes
9714 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9715 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9716 ir++) {
9717 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9718 if (pr && pr->pRoutePointList) {
9719 auto *list = pr->pRoutePointList;
9720 auto pos =
9721 std::find(list->begin(), list->end(), pNearbyPoint);
9722 if (pos != list->end()) {
9723 duplicate = true;
9724 break;
9725 }
9726 }
9727 }
9728 }
9729
9730 // Special case:
9731 // Allow "re-use" of a route's waypoints iff it is a simple
9732 // isolated route. This allows, for instance, creation of a closed
9733 // polygon route
9734 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9735
9736 if (!duplicate) {
9737 int dlg_return;
9738 dlg_return =
9739 OCPNMessageBox(this,
9740 _("Replace this RoutePoint by the nearby "
9741 "Waypoint?"),
9742 _("OpenCPN RoutePoint change"),
9743 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9744 if (dlg_return == wxID_YES) {
9745 /*double confirmation if the dragged point has been manually
9746 * created which can be important and could be deleted
9747 * unintentionally*/
9748
9749 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9750 pNearbyPoint);
9751 current =
9752 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9753
9754 if (tail && current && (tail != current)) {
9755 int dlg_return1;
9756 connect = tail->GetIndexOf(pNearbyPoint);
9757 int index_current_route =
9758 current->GetIndexOf(m_pRoutePointEditTarget);
9759 index_last = current->GetIndexOf(current->GetLastPoint());
9760 dlg_return1 = wxID_NO;
9761 if (index_last ==
9762 index_current_route) { // we are dragging the last
9763 // point of the route
9764 if (connect != tail->GetnPoints()) { // anything to do?
9765
9766 wxString dmsg(
9767 _("Last part of route to be appended to dragged "
9768 "route?"));
9769 if (connect == 1)
9770 dmsg =
9771 _("Full route to be appended to dragged route?");
9772
9773 dlg_return1 = OCPNMessageBox(
9774 this, dmsg, _("OpenCPN Route Create"),
9775 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9776 if (dlg_return1 == wxID_YES) {
9777 appending = true;
9778 }
9779 }
9780 } else if (index_current_route ==
9781 1) { // dragging the first point of the route
9782 if (connect != 1) { // anything to do?
9783
9784 wxString dmsg(
9785 _("First part of route to be inserted into dragged "
9786 "route?"));
9787 if (connect == tail->GetnPoints())
9788 dmsg = _(
9789 "Full route to be inserted into dragged route?");
9790
9791 dlg_return1 = OCPNMessageBox(
9792 this, dmsg, _("OpenCPN Route Create"),
9793 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9794 if (dlg_return1 == wxID_YES) {
9795 inserting = true;
9796 }
9797 }
9798 }
9799 }
9800
9801 if (m_pRoutePointEditTarget->IsShared()) {
9802 // dlg_return = wxID_NO;
9803 dlg_return = OCPNMessageBox(
9804 this,
9805 _("Do you really want to delete and replace this "
9806 "WayPoint") +
9807 "\n" + _("which has been created manually?"),
9808 ("OpenCPN RoutePoint warning"),
9809 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9810 }
9811 }
9812 if (dlg_return == wxID_YES) {
9813 pMousePoint = pNearbyPoint;
9814 if (pMousePoint->m_bIsolatedMark) {
9815 pMousePoint->SetShared(true);
9816 }
9817 pMousePoint->m_bIsolatedMark =
9818 false; // definitely no longer isolated
9819 pMousePoint->m_bIsInRoute = true;
9820 }
9821 }
9822 }
9823 }
9824 if (!pMousePoint)
9825 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9826
9827 if (m_pEditRouteArray) {
9828 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9829 ir++) {
9830 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9831 if (g_pRouteMan->IsRouteValid(pr)) {
9832 if (pMousePoint) { // remove the dragged point and insert the
9833 // nearby
9834 auto *list = pr->pRoutePointList;
9835 auto pos = std::find(list->begin(), list->end(),
9836 m_pRoutePointEditTarget);
9837
9838 pSelect->DeleteAllSelectableRoutePoints(pr);
9839 pSelect->DeleteAllSelectableRouteSegments(pr);
9840
9841 pr->pRoutePointList->insert(pos, pMousePoint);
9842 pos = std::find(list->begin(), list->end(),
9843 m_pRoutePointEditTarget);
9844 pr->pRoutePointList->erase(pos);
9845
9846 pSelect->AddAllSelectableRouteSegments(pr);
9847 pSelect->AddAllSelectableRoutePoints(pr);
9848 }
9849 pr->FinalizeForRendering();
9850 pr->UpdateSegmentDistances();
9851 if (m_bRoutePoinDragging) {
9852 // pConfig->UpdateRoute(pr);
9853 NavObj_dB::GetInstance().UpdateRoute(pr);
9854 }
9855 }
9856 }
9857 }
9858
9859 // Update the RouteProperties Dialog, if currently shown
9860 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9861 if (m_pEditRouteArray) {
9862 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9863 ir++) {
9864 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9865 if (g_pRouteMan->IsRouteValid(pr)) {
9866 if (pRoutePropDialog->GetRoute() == pr) {
9867 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9868 }
9869 /* cannot edit track points anyway
9870 else if ( ( NULL !=
9871 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9872 pTrackPropDialog->m_pTrack == pr ) {
9873 pTrackPropDialog->SetTrackAndUpdate(
9874 pr );
9875 }
9876 */
9877 }
9878 }
9879 }
9880 }
9881 if (pMousePoint) { // clear all about the dragged point
9882 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9883 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9884 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9885 // Hide mark properties dialog if open on the replaced point
9886 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9887 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9888 g_pMarkInfoDialog->Hide();
9889
9890 delete m_pRoutePointEditTarget;
9891 m_lastRoutePointEditTarget = NULL;
9892 m_pRoutePointEditTarget = NULL;
9893 undo->AfterUndoableAction(pMousePoint);
9894 undo->InvalidateUndo();
9895 }
9896 }
9897 }
9898
9899 else if (m_bMarkEditing) { // End of way point drag
9900 if (m_pRoutePointEditTarget)
9901 if (m_bRoutePoinDragging) {
9902 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9903 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9904 }
9905 }
9906
9907 if (m_pRoutePointEditTarget)
9908 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9909
9910 if (!m_pRoutePointEditTarget) {
9911 delete m_pEditRouteArray;
9912 m_pEditRouteArray = NULL;
9913 m_bRouteEditing = false;
9914 }
9915 m_bRoutePoinDragging = false;
9916
9917 if (appending) { // Appending to the route of which the last point is
9918 // dragged onto another route
9919
9920 // copy tail from connect until length to end of current after dragging
9921
9922 int length = tail->GetnPoints();
9923 for (int i = connect + 1; i <= length; i++) {
9924 current->AddPointAndSegment(tail->GetPoint(i), false);
9925 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9926 m_routeState++;
9927 top_frame::Get()->RefreshAllCanvas();
9928 ret = true;
9929 }
9930 current->FinalizeForRendering();
9931 current->m_bIsBeingEdited = false;
9932 FinishRoute();
9933 g_pRouteMan->DeleteRoute(tail);
9934 }
9935 if (inserting) {
9936 pSelect->DeleteAllSelectableRoutePoints(current);
9937 pSelect->DeleteAllSelectableRouteSegments(current);
9938 for (int i = 1; i < connect; i++) { // numbering in the tail route
9939 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9940 }
9941 pSelect->AddAllSelectableRouteSegments(current);
9942 pSelect->AddAllSelectableRoutePoints(current);
9943 current->FinalizeForRendering();
9944 current->m_bIsBeingEdited = false;
9945 g_pRouteMan->DeleteRoute(tail);
9946 }
9947
9948 // Update the RouteProperties Dialog, if currently shown
9949 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9950 if (m_pEditRouteArray) {
9951 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9952 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9953 if (g_pRouteMan->IsRouteValid(pr)) {
9954 if (pRoutePropDialog->GetRoute() == pr) {
9955 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9956 }
9957 }
9958 }
9959 }
9960 }
9961
9962 } // g_btouch
9963
9964 else { // !g_btouch
9965 if (m_bRouteEditing) { // End of RoutePoint drag
9966 Route *tail = 0;
9967 Route *current = 0;
9968 bool appending = false;
9969 bool inserting = false;
9970 int connect = 0;
9971 int index_last;
9972 if (m_pRoutePointEditTarget) {
9973 m_pRoutePointEditTarget->m_bBlink = false;
9974 // Check to see if there is a nearby point which may replace the
9975 // dragged one
9976 RoutePoint *pMousePoint = NULL;
9977 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9978 double nearby_radius_meters =
9979 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9980 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9981 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9982 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9983 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9984 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9985 bool duplicate = false; // don't create duplicate point in routes
9986 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9987 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9988 ir++) {
9989 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9990 if (pr && pr->pRoutePointList) {
9991 auto *list = pr->pRoutePointList;
9992 auto pos =
9993 std::find(list->begin(), list->end(), pNearbyPoint);
9994 if (pos != list->end()) {
9995 duplicate = true;
9996 break;
9997 }
9998 }
9999 }
10000 }
10001
10002 // Special case:
10003 // Allow "re-use" of a route's waypoints iff it is a simple
10004 // isolated route. This allows, for instance, creation of a closed
10005 // polygon route
10006 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
10007
10008 if (!duplicate) {
10009 int dlg_return;
10010 dlg_return =
10011 OCPNMessageBox(this,
10012 _("Replace this RoutePoint by the nearby "
10013 "Waypoint?"),
10014 _("OpenCPN RoutePoint change"),
10015 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10016 if (dlg_return == wxID_YES) {
10017 /*double confirmation if the dragged point has been manually
10018 * created which can be important and could be deleted
10019 * unintentionally*/
10020 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10021 pNearbyPoint);
10022 current =
10023 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10024
10025 if (tail && current && (tail != current)) {
10026 int dlg_return1;
10027 connect = tail->GetIndexOf(pNearbyPoint);
10028 int index_current_route =
10029 current->GetIndexOf(m_pRoutePointEditTarget);
10030 index_last = current->GetIndexOf(current->GetLastPoint());
10031 dlg_return1 = wxID_NO;
10032 if (index_last ==
10033 index_current_route) { // we are dragging the last
10034 // point of the route
10035 if (connect != tail->GetnPoints()) { // anything to do?
10036
10037 wxString dmsg(
10038 _("Last part of route to be appended to dragged "
10039 "route?"));
10040 if (connect == 1)
10041 dmsg =
10042 _("Full route to be appended to dragged route?");
10043
10044 dlg_return1 = OCPNMessageBox(
10045 this, dmsg, _("OpenCPN Route Create"),
10046 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10047 if (dlg_return1 == wxID_YES) {
10048 appending = true;
10049 }
10050 }
10051 } else if (index_current_route ==
10052 1) { // dragging the first point of the route
10053 if (connect != 1) { // anything to do?
10054
10055 wxString dmsg(
10056 _("First part of route to be inserted into dragged "
10057 "route?"));
10058 if (connect == tail->GetnPoints())
10059 dmsg = _(
10060 "Full route to be inserted into dragged route?");
10061
10062 dlg_return1 = OCPNMessageBox(
10063 this, dmsg, _("OpenCPN Route Create"),
10064 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10065 if (dlg_return1 == wxID_YES) {
10066 inserting = true;
10067 }
10068 }
10069 }
10070 }
10071
10072 if (m_pRoutePointEditTarget->IsShared()) {
10073 dlg_return = wxID_NO;
10074 dlg_return = OCPNMessageBox(
10075 this,
10076 _("Do you really want to delete and replace this "
10077 "WayPoint") +
10078 "\n" + _("which has been created manually?"),
10079 ("OpenCPN RoutePoint warning"),
10080 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10081 }
10082 }
10083 if (dlg_return == wxID_YES) {
10084 pMousePoint = pNearbyPoint;
10085 if (pMousePoint->m_bIsolatedMark) {
10086 pMousePoint->SetShared(true);
10087 }
10088 pMousePoint->m_bIsolatedMark =
10089 false; // definitely no longer isolated
10090 pMousePoint->m_bIsInRoute = true;
10091 }
10092 }
10093 }
10094 }
10095 if (!pMousePoint)
10096 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10097
10098 if (m_pEditRouteArray) {
10099 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10100 ir++) {
10101 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10102 if (g_pRouteMan->IsRouteValid(pr)) {
10103 if (pMousePoint) { // replace dragged point by nearby one
10104 auto *list = pr->pRoutePointList;
10105 auto pos = std::find(list->begin(), list->end(),
10106 m_pRoutePointEditTarget);
10107
10108 pSelect->DeleteAllSelectableRoutePoints(pr);
10109 pSelect->DeleteAllSelectableRouteSegments(pr);
10110
10111 pr->pRoutePointList->insert(pos, pMousePoint);
10112 pos = std::find(list->begin(), list->end(),
10113 m_pRoutePointEditTarget);
10114 if (pos != list->end()) list->erase(pos);
10115 // pr->pRoutePointList->erase(pos + 1);
10116
10117 pSelect->AddAllSelectableRouteSegments(pr);
10118 pSelect->AddAllSelectableRoutePoints(pr);
10119 }
10120 pr->FinalizeForRendering();
10121 pr->UpdateSegmentDistances();
10122 pr->m_bIsBeingEdited = false;
10123
10124 if (m_bRoutePoinDragging) {
10125 // Special case optimization.
10126 // Dragging a single point of a route
10127 // without any point additions or re-ordering
10128 if (!pMousePoint)
10129 NavObj_dB::GetInstance().UpdateRoutePoint(
10130 m_pRoutePointEditTarget);
10131 else
10132 NavObj_dB::GetInstance().UpdateRoute(pr);
10133 }
10134 pr->SetHiLite(0);
10135 }
10136 }
10137 Refresh(false);
10138 }
10139
10140 if (appending) {
10141 // copy tail from connect until length to end of current after
10142 // dragging
10143
10144 int length = tail->GetnPoints();
10145 for (int i = connect + 1; i <= length; i++) {
10146 current->AddPointAndSegment(tail->GetPoint(i), false);
10147 if (current)
10148 current->m_lastMousePointIndex = current->GetnPoints();
10149 m_routeState++;
10150 top_frame::Get()->RefreshAllCanvas();
10151 ret = true;
10152 }
10153 current->FinalizeForRendering();
10154 current->m_bIsBeingEdited = false;
10155 FinishRoute();
10156 g_pRouteMan->DeleteRoute(tail);
10157 }
10158 if (inserting) {
10159 pSelect->DeleteAllSelectableRoutePoints(current);
10160 pSelect->DeleteAllSelectableRouteSegments(current);
10161 for (int i = 1; i < connect; i++) { // numbering in the tail route
10162 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10163 }
10164 pSelect->AddAllSelectableRouteSegments(current);
10165 pSelect->AddAllSelectableRoutePoints(current);
10166 current->FinalizeForRendering();
10167 current->m_bIsBeingEdited = false;
10168 g_pRouteMan->DeleteRoute(tail);
10169 }
10170
10171 // Update the RouteProperties Dialog, if currently shown
10172 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10173 if (m_pEditRouteArray) {
10174 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10175 ir++) {
10176 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10177 if (g_pRouteMan->IsRouteValid(pr)) {
10178 if (pRoutePropDialog->GetRoute() == pr) {
10179 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10180 }
10181 }
10182 }
10183 }
10184 }
10185
10186 if (pMousePoint) {
10187 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10188 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10189 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10190 // Hide mark properties dialog if open on the replaced point
10191 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10192 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10193 g_pMarkInfoDialog->Hide();
10194
10195 delete m_pRoutePointEditTarget;
10196 m_lastRoutePointEditTarget = NULL;
10197 undo->AfterUndoableAction(pMousePoint);
10198 undo->InvalidateUndo();
10199 } else {
10200 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10201 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10202
10203 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10204 }
10205
10206 delete m_pEditRouteArray;
10207 m_pEditRouteArray = NULL;
10208 }
10209
10210 InvalidateGL();
10211 m_bRouteEditing = false;
10212 m_pRoutePointEditTarget = NULL;
10213
10214 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10215 ret = true;
10216 }
10217
10218 else if (m_bMarkEditing) { // end of Waypoint drag
10219 if (m_pRoutePointEditTarget) {
10220 if (m_bRoutePoinDragging) {
10221 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10222 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10223 }
10224 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10225 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10226 if (!g_bopengl) {
10227 wxRect wp_rect;
10228 RoutePointGui(*m_pRoutePointEditTarget)
10229 .CalculateDCRect(m_dc_route, this, &wp_rect);
10230 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10231 RefreshRect(wp_rect, true);
10232 }
10233 }
10234 m_pRoutePointEditTarget = NULL;
10235 m_bMarkEditing = false;
10236 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10237 ret = true;
10238 }
10239
10240 else if (leftIsDown) { // left click for chart center
10241 leftIsDown = false;
10242 ret = false;
10243
10244 if (!g_btouch) {
10245 if (!m_bChartDragging && !m_bMeasure_Active) {
10246 } else {
10247 m_bChartDragging = false;
10248 }
10249 }
10250 }
10251 m_bRoutePoinDragging = false;
10252 } // !btouch
10253
10254 if (ret) return true;
10255 } // left up
10256
10257 if (event.RightDown()) {
10258 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10259 last_drag.x = mx;
10260 last_drag.y = my;
10261
10262 if (g_btouch) {
10263 // if( m_pRoutePointEditTarget )
10264 // return false;
10265 }
10266
10267 ret = true;
10268 m_FinishRouteOnKillFocus = false;
10269 CallPopupMenu(mx, my);
10270 m_FinishRouteOnKillFocus = true;
10271 } // Right down
10272
10273 return ret;
10274}
10275
10276bool panleftIsDown;
10277bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10278 // Skip all mouse processing if shift is held.
10279 // This allows plugins to implement shift+drag behaviors.
10280 if (event.ShiftDown()) {
10281 return false;
10282 }
10283 int x, y;
10284 event.GetPosition(&x, &y);
10285
10286 x *= m_displayScale;
10287 y *= m_displayScale;
10288
10289 // Check for wheel rotation
10290 // ideally, should be just longer than the time between
10291 // processing accumulated mouse events from the event queue
10292 // as would happen during screen redraws.
10293 int wheel_dir = event.GetWheelRotation();
10294
10295 if (wheel_dir) {
10296 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10297 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10298
10299 double factor = g_mouse_zoom_sensitivity;
10300 if (wheel_dir < 0) factor = 1 / factor;
10301
10302 if (g_bsmoothpanzoom) {
10303 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10304 if (wheel_dir == m_last_wheel_dir) {
10305 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10306 // m_zoom_target /= factor;
10307 } else
10308 StopMovement();
10309 } else {
10310 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10311 m_wheelstopwatch.Start(0);
10312 // m_zoom_target = VPoint.chart_scale / factor;
10313 }
10314 }
10315
10316 m_last_wheel_dir = wheel_dir;
10317
10318 ZoomCanvas(factor, true, false);
10319 }
10320
10321 if (event.LeftDown()) {
10322 // Skip the first left click if it will cause a canvas focus shift
10323 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10324 return false;
10325 }
10326
10327 last_drag.x = x, last_drag.y = y;
10328 panleftIsDown = true;
10329 }
10330
10331 if (event.LeftUp()) {
10332 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10333 // seen here.
10334 panleftIsDown = false;
10335
10336 if (!g_btouch) {
10337 if (!m_bChartDragging && !m_bMeasure_Active) {
10338 switch (cursor_region) {
10339 case MID_RIGHT: {
10340 PanCanvas(100, 0);
10341 break;
10342 }
10343
10344 case MID_LEFT: {
10345 PanCanvas(-100, 0);
10346 break;
10347 }
10348
10349 case MID_TOP: {
10350 PanCanvas(0, 100);
10351 break;
10352 }
10353
10354 case MID_BOT: {
10355 PanCanvas(0, -100);
10356 break;
10357 }
10358
10359 case CENTER: {
10360 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10361 break;
10362 }
10363 }
10364 } else {
10365 m_bChartDragging = false;
10366 }
10367 }
10368 }
10369 }
10370
10371 if (event.Dragging() && event.LeftIsDown()) {
10372 /*
10373 * fixed dragging.
10374 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10375 * before the drag event. Hence, as there is no mouse down event, last_drag
10376 * is not reset before the drag. And that results in one single drag
10377 * session, meaning you cannot drag the map a few miles north, lift your
10378 * finger, and the go even further north. Instead, the map resets itself
10379 * always to the very first drag start (since there is not reset of
10380 * last_drag).
10381 *
10382 * Besides, should not left down and dragging be enough of a situation to
10383 * start a drag procedure?
10384 *
10385 * Anyways, guarded it to be active in touch situations only.
10386 */
10387 if (g_btouch && !m_inPinch) {
10388 struct timespec now;
10389 clock_gettime(CLOCK_MONOTONIC, &now);
10390 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10391
10392 bool trigger_hold = false;
10393 if (false == m_bChartDragging) {
10394 if (m_DragTrigger < 0) {
10395 // printf("\ntrigger1\n");
10396 m_DragTrigger = 0;
10397 m_DragTriggerStartTime = tnow;
10398 trigger_hold = true;
10399 } else {
10400 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10401 m_DragTrigger = -1; // Reset trigger
10402 // printf("trigger fired\n");
10403 }
10404 }
10405 }
10406 if (trigger_hold) return true;
10407
10408 if (false == m_bChartDragging) {
10409 // printf("starting drag\n");
10410 // Reset drag calculation members
10411 last_drag.x = x - 1, last_drag.y = y - 1;
10412 m_bChartDragging = true;
10413 m_chart_drag_total_time = 0;
10414 m_chart_drag_total_x = 0;
10415 m_chart_drag_total_y = 0;
10416 m_inertia_last_drag_x = x;
10417 m_inertia_last_drag_y = y;
10418 m_drag_vec_x.clear();
10419 m_drag_vec_y.clear();
10420 m_drag_vec_t.clear();
10421 m_last_drag_time = tnow;
10422 }
10423
10424 // Calculate and store drag dynamics.
10425 uint64_t delta_t = tnow - m_last_drag_time;
10426 double delta_tf = delta_t / 1e9;
10427
10428 m_chart_drag_total_time += delta_tf;
10429 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10430 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10431
10432 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10433 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10434 m_drag_vec_t.push_back(delta_tf);
10435
10436 m_inertia_last_drag_x = x;
10437 m_inertia_last_drag_y = y;
10438 m_last_drag_time = tnow;
10439
10440 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10441 m_bChartDragging = true;
10442 StartTimedMovement();
10443 m_pan_drag.x += last_drag.x - x;
10444 m_pan_drag.y += last_drag.y - y;
10445 last_drag.x = x, last_drag.y = y;
10446 }
10447 } else if (!g_btouch) {
10448 if ((last_drag.x != x) || (last_drag.y != y)) {
10449 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10450 // dragging on route create.
10451 // github #2994
10452 m_bChartDragging = true;
10453 StartTimedMovement();
10454 m_pan_drag.x += last_drag.x - x;
10455 m_pan_drag.y += last_drag.y - y;
10456 last_drag.x = x, last_drag.y = y;
10457 }
10458 }
10459 }
10460
10461 // Handle some special cases
10462 if (g_btouch) {
10463 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10464 // deactivate next LeftUp to ovoid creating an unexpected point
10465 m_ignore_next_leftup = true;
10466 m_DoubleClickTimer->Start();
10467 singleClickEventIsValid = false;
10468 }
10469 }
10470 }
10471
10472 return true;
10473}
10474
10475void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10476 if (MouseEventOverlayWindows(event)) return;
10477
10478 if (MouseEventSetup(event)) return; // handled, no further action required
10479
10480 bool nm = MouseEventProcessObjects(event);
10481 if (!nm) MouseEventProcessCanvas(event);
10482}
10483
10484void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10485 // Switch to the appropriate cursor on mouse movement
10486
10487 wxCursor *ptarget_cursor = pCursorArrow;
10488 if (!pPlugIn_Cursor) {
10489 ptarget_cursor = pCursorArrow;
10490 if ((!m_routeState) &&
10491 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10492 if (cursor_region == MID_RIGHT) {
10493 ptarget_cursor = pCursorRight;
10494 } else if (cursor_region == MID_LEFT) {
10495 ptarget_cursor = pCursorLeft;
10496 } else if (cursor_region == MID_TOP) {
10497 ptarget_cursor = pCursorDown;
10498 } else if (cursor_region == MID_BOT) {
10499 ptarget_cursor = pCursorUp;
10500 } else {
10501 ptarget_cursor = pCursorArrow;
10502 }
10503 } else if (m_bMeasure_Active ||
10504 m_routeState) // If Measure tool use Pencil Cursor
10505 ptarget_cursor = pCursorPencil;
10506 } else {
10507 ptarget_cursor = pPlugIn_Cursor;
10508 }
10509
10510 SetCursor(*ptarget_cursor);
10511}
10512
10513void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10514 SetCursor(*pCursorArrow);
10515}
10516
10517void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10518 ChartPlugInWrapper *target_plugin_chart = NULL;
10519 s57chart *Chs57 = NULL;
10520 wxFileName file;
10521 wxArrayString files;
10522
10523 ChartBase *target_chart = GetChartAtCursor();
10524 if (target_chart) {
10525 file.Assign(target_chart->GetFullPath());
10526 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10527 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10528 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10529 else
10530 Chs57 = dynamic_cast<s57chart *>(target_chart);
10531 } else { // target_chart = null, might be mbtiles
10532 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10533 unsigned int im = stackIndexArray.size();
10534 int scale = 2147483647; // max 32b integer
10535 if (VPoint.b_quilt && im > 0) {
10536 for (unsigned int is = 0; is < im; is++) {
10537 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10538 CHART_TYPE_MBTILES) {
10539 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10540 double lat, lon;
10541 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10542 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10543 .GetBBox()
10544 .Contains(lat, lon)) {
10545 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10546 scale) {
10547 scale =
10548 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10549 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10550 }
10551 }
10552 }
10553 }
10554 }
10555 }
10556
10557 std::vector<Ais8_001_22 *> area_notices;
10558
10559 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10560 float vp_scale = GetVPScale();
10561
10562 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10563 auto target_data = target.second;
10564 if (!target_data->area_notices.empty()) {
10565 for (auto &ani : target_data->area_notices) {
10566 Ais8_001_22 &area_notice = ani.second;
10567
10568 BoundingBox bbox;
10569
10570 for (Ais8_001_22_SubAreaList::iterator sa =
10571 area_notice.sub_areas.begin();
10572 sa != area_notice.sub_areas.end(); ++sa) {
10573 switch (sa->shape) {
10574 case AIS8_001_22_SHAPE_CIRCLE: {
10575 wxPoint target_point;
10576 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10577 bbox.Expand(target_point);
10578 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10579 break;
10580 }
10581 case AIS8_001_22_SHAPE_RECT: {
10582 wxPoint target_point;
10583 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10584 bbox.Expand(target_point);
10585 if (sa->e_dim_m > sa->n_dim_m)
10586 bbox.EnLarge(sa->e_dim_m * vp_scale);
10587 else
10588 bbox.EnLarge(sa->n_dim_m * vp_scale);
10589 break;
10590 }
10591 case AIS8_001_22_SHAPE_POLYGON:
10592 case AIS8_001_22_SHAPE_POLYLINE: {
10593 for (int i = 0; i < 4; ++i) {
10594 double lat = sa->latitude;
10595 double lon = sa->longitude;
10596 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10597 &lat, &lon);
10598 wxPoint target_point;
10599 GetCanvasPointPix(lat, lon, &target_point);
10600 bbox.Expand(target_point);
10601 }
10602 break;
10603 }
10604 case AIS8_001_22_SHAPE_SECTOR: {
10605 double lat1 = sa->latitude;
10606 double lon1 = sa->longitude;
10607 double lat, lon;
10608 wxPoint target_point;
10609 GetCanvasPointPix(lat1, lon1, &target_point);
10610 bbox.Expand(target_point);
10611 for (int i = 0; i < 18; ++i) {
10612 ll_gc_ll(
10613 lat1, lon1,
10614 sa->left_bound_deg +
10615 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10616 sa->radius_m / 1852.0, &lat, &lon);
10617 GetCanvasPointPix(lat, lon, &target_point);
10618 bbox.Expand(target_point);
10619 }
10620 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10621 &lat, &lon);
10622 GetCanvasPointPix(lat, lon, &target_point);
10623 bbox.Expand(target_point);
10624 break;
10625 }
10626 }
10627 }
10628
10629 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10630 area_notices.push_back(&area_notice);
10631 }
10632 }
10633 }
10634 }
10635 }
10636
10637 if (target_chart || !area_notices.empty() || file.HasName()) {
10638 // Go get the array of all objects at the cursor lat/lon
10639 int sel_rad_pix = 5;
10640 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10641
10642 // Make sure we always get the lights from an object, even if we are
10643 // currently not displaying lights on the chart.
10644
10645 SetCursor(wxCURSOR_WAIT);
10646 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10647 if (!lightsVis) SetShowENCLights(true);
10648 ;
10649
10650 ListOfObjRazRules *rule_list = NULL;
10651 ListOfPI_S57Obj *pi_rule_list = NULL;
10652 if (Chs57)
10653 rule_list =
10654 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10655 else if (target_plugin_chart)
10656 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10657 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10658
10659 ListOfObjRazRules *overlay_rule_list = NULL;
10660 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10661 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10662
10663 if (CHs57_Overlay) {
10664 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10665 zlat, zlon, SelectRadius, &GetVP());
10666 }
10667
10668 if (!lightsVis) SetShowENCLights(false);
10669
10670 wxString objText;
10671 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10672 wxString face = dFont->GetFaceName();
10673
10674 if (NULL == g_pObjectQueryDialog) {
10676 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10677 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10678 }
10679
10680 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10681 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10682
10683#ifdef __WXOSX__
10684 // Auto Adjustment for dark mode
10685 fg = g_pObjectQueryDialog->GetForegroundColour();
10686#endif
10687
10688 objText.Printf(
10689 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10690 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10691
10692#ifdef __WXOSX__
10693 int points = dFont->GetPointSize();
10694#else
10695 int points = dFont->GetPointSize() + 1;
10696#endif
10697
10698 int sizes[7];
10699 for (int i = -2; i < 5; i++) {
10700 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10701 }
10702 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10703
10704 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10705
10706 if (overlay_rule_list && CHs57_Overlay) {
10707 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10708 objText << "<hr noshade>";
10709 }
10710
10711 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10712 an != area_notices.end(); ++an) {
10713 objText << "<b>AIS Area Notice:</b> ";
10714 objText << ais8_001_22_notice_names[(*an)->notice_type];
10715 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10716 (*an)->sub_areas.begin();
10717 sa != (*an)->sub_areas.end(); ++sa)
10718 if (!sa->text.empty()) objText << sa->text;
10719 objText << "<br>expires: " << (*an)->expiry_time.Format();
10720 objText << "<hr noshade>";
10721 }
10722
10723 if (Chs57)
10724 objText << Chs57->CreateObjDescriptions(rule_list);
10725 else if (target_plugin_chart)
10726 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10727 pi_rule_list);
10728
10729 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10730
10731 // Add the additional info files
10732 wxString AddFiles, filenameOK;
10733 int filecount = 0;
10734 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10735 // plugin
10736
10737 AddFiles = wxString::Format(
10738 "<hr noshade><br><b>Additional info files attached to: </b> "
10739 "<font "
10740 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10741 "cellpadding=3>",
10742 file.GetFullName());
10743 file.Normalize();
10744 file.Assign(file.GetPath(), "");
10745 wxDir dir(file.GetFullPath());
10746 wxString filename;
10747 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10748 while (cont) {
10749 file.Assign(dir.GetNameWithSep().append(filename));
10750 wxString FormatString =
10751 "<td valign=top><font size=-2><a "
10752 "href=\"%s\">%s</a></font></td>";
10753 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10754 filenameOK = file.GetFullPath(); // remember last valid name
10755 // we are making a 3 columns table. New row only every third file
10756 if (3 * ((int)filecount / 3) == filecount)
10757 FormatString.Prepend("<tr>"); // new row
10758 else
10759 FormatString.Prepend(
10760 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10761 // spacer column
10762
10763 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10764 file.GetFullName());
10765 filecount++;
10766 }
10767 cont = dir.GetNext(&filename);
10768 }
10769 objText << AddFiles << "</table>";
10770 }
10771 objText << "</font>";
10772 objText << "</body></html>";
10773
10774 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10775 g_pObjectQueryDialog->SetHTMLPage(objText);
10776 g_pObjectQueryDialog->Show();
10777 }
10778 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10779 // generate an event to avoid double code
10780 wxHtmlLinkInfo hli(filenameOK);
10781 wxHtmlLinkEvent hle(1, hli);
10782 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10783 }
10784
10785 if (rule_list) rule_list->Clear();
10786 delete rule_list;
10787
10788 if (overlay_rule_list) overlay_rule_list->Clear();
10789 delete overlay_rule_list;
10790
10791 if (pi_rule_list) pi_rule_list->Clear();
10792 delete pi_rule_list;
10793
10794 SetCursor(wxCURSOR_ARROW);
10795 }
10796}
10797
10798void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10799 bool bNew = false;
10800 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10801 // Dialog
10802 g_pMarkInfoDialog = new MarkInfoDlg(this);
10803 bNew = true;
10804 }
10805
10806 if (1 /*g_bresponsive*/) {
10807 wxSize canvas_size = GetSize();
10808
10809 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10810 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10811
10812 g_pMarkInfoDialog->Layout();
10813
10814 wxPoint canvas_pos = GetPosition();
10815 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10816
10817 bool newFit = false;
10818 if (canvas_size.x < fitted_size.x) {
10819 fitted_size.x = canvas_size.x - 40;
10820 if (canvas_size.y < fitted_size.y)
10821 fitted_size.y -= 40; // scrollbar added
10822 }
10823 if (canvas_size.y < fitted_size.y) {
10824 fitted_size.y = canvas_size.y - 40;
10825 if (canvas_size.x < fitted_size.x)
10826 fitted_size.x -= 40; // scrollbar added
10827 }
10828
10829 if (newFit) {
10830 g_pMarkInfoDialog->SetSize(fitted_size);
10831 g_pMarkInfoDialog->Centre();
10832 }
10833 }
10834
10835 markPoint->m_bRPIsBeingEdited = false;
10836
10837 wxString title_base = _("Mark Properties");
10838 if (markPoint->m_bIsInRoute) {
10839 title_base = _("Waypoint Properties");
10840 }
10841 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10842 g_pMarkInfoDialog->UpdateProperties();
10843 if (markPoint->m_bIsInLayer) {
10844 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10845 GetLayerName(markPoint->m_LayerID)));
10846 g_pMarkInfoDialog->SetDialogTitle(caption);
10847 } else
10848 g_pMarkInfoDialog->SetDialogTitle(title_base);
10849
10850 g_pMarkInfoDialog->Show();
10851 g_pMarkInfoDialog->Raise();
10852 g_pMarkInfoDialog->InitialFocus();
10853 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10854}
10855
10856void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10857 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10858 pRoutePropDialog->SetRouteAndUpdate(selected);
10859 // pNew->UpdateProperties();
10860 pRoutePropDialog->Show();
10861 pRoutePropDialog->Raise();
10862 return;
10863 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10864 this); // There is one global instance of the RouteProp Dialog
10865
10866 if (g_bresponsive) {
10867 wxSize canvas_size = GetSize();
10868 wxPoint canvas_pos = GetPosition();
10869 wxSize fitted_size = pRoutePropDialog->GetSize();
10870 ;
10871
10872 if (canvas_size.x < fitted_size.x) {
10873 fitted_size.x = canvas_size.x;
10874 if (canvas_size.y < fitted_size.y)
10875 fitted_size.y -= 20; // scrollbar added
10876 }
10877 if (canvas_size.y < fitted_size.y) {
10878 fitted_size.y = canvas_size.y;
10879 if (canvas_size.x < fitted_size.x)
10880 fitted_size.x -= 20; // scrollbar added
10881 }
10882
10883 pRoutePropDialog->SetSize(fitted_size);
10884 pRoutePropDialog->Centre();
10885
10886 // int xp = (canvas_size.x - fitted_size.x)/2;
10887 // int yp = (canvas_size.y - fitted_size.y)/2;
10888
10889 wxPoint xxp = ClientToScreen(canvas_pos);
10890 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10891 }
10892
10893 pRoutePropDialog->SetRouteAndUpdate(selected);
10894
10895 pRoutePropDialog->Show();
10896
10897 Refresh(false);
10898}
10899
10900void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10901 pTrackPropDialog = TrackPropDlg::getInstance(
10902 this); // There is one global instance of the RouteProp Dialog
10903
10904 pTrackPropDialog->SetTrackAndUpdate(selected);
10906
10907 pTrackPropDialog->Show();
10908
10909 Refresh(false);
10910}
10911
10912void pupHandler_PasteWaypoint() {
10913 Kml kml;
10914
10915 int pasteBuffer = kml.ParsePasteBuffer();
10916 RoutePoint *pasted = kml.GetParsedRoutePoint();
10917 if (!pasted) return;
10918
10919 double nearby_radius_meters =
10920 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10921
10922 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10923 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10924
10925 int answer = wxID_NO;
10926 if (nearPoint && !nearPoint->m_bIsInLayer) {
10927 wxString msg;
10928 msg << _(
10929 "There is an existing waypoint at the same location as the one you are "
10930 "pasting. Would you like to merge the pasted data with it?\n\n");
10931 msg << _("Answering 'No' will create a new waypoint at the same location.");
10932 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10933 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10934 }
10935
10936 if (answer == wxID_YES) {
10937 nearPoint->SetName(pasted->GetName());
10938 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10939 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10940 pRouteManagerDialog->UpdateWptListCtrl();
10941 }
10942
10943 if (answer == wxID_NO) {
10944 RoutePoint *newPoint = new RoutePoint(pasted);
10945 newPoint->m_bIsolatedMark = true;
10946 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10947 newPoint);
10948 // pConfig->AddNewWayPoint(newPoint, -1);
10949 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10950
10951 pWayPointMan->AddRoutePoint(newPoint);
10952 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10953 pRouteManagerDialog->UpdateWptListCtrl();
10954 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10955 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10956 }
10957
10958 top_frame::Get()->InvalidateAllGL();
10959 top_frame::Get()->RefreshAllCanvas(false);
10960}
10961
10962void pupHandler_PasteRoute() {
10963 Kml kml;
10964
10965 int pasteBuffer = kml.ParsePasteBuffer();
10966 Route *pasted = kml.GetParsedRoute();
10967 if (!pasted) return;
10968
10969 double nearby_radius_meters =
10970 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10971
10972 RoutePoint *curPoint;
10973 RoutePoint *nearPoint;
10974 RoutePoint *prevPoint = NULL;
10975
10976 bool mergepoints = false;
10977 bool createNewRoute = true;
10978 int existingWaypointCounter = 0;
10979
10980 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10981 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10982 nearPoint = pWayPointMan->GetNearbyWaypoint(
10983 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10984 if (nearPoint) {
10985 mergepoints = true;
10986 existingWaypointCounter++;
10987 // Small hack here to avoid both extending RoutePoint and repeating all
10988 // the GetNearbyWaypoint calculations. Use existin data field in
10989 // RoutePoint as temporary storage.
10990 curPoint->m_bPtIsSelected = true;
10991 }
10992 }
10993
10994 int answer = wxID_NO;
10995 if (mergepoints) {
10996 wxString msg;
10997 msg << _(
10998 "There are existing waypoints at the same location as some of the ones "
10999 "you are pasting. Would you like to just merge the pasted data into "
11000 "them?\n\n");
11001 msg << _("Answering 'No' will create all new waypoints for this route.");
11002 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
11003 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
11004
11005 if (answer == wxID_CANCEL) {
11006 return;
11007 }
11008 }
11009
11010 // If all waypoints exist since before, and a route with the same name, we
11011 // don't create a new route.
11012 if (mergepoints && answer == wxID_YES &&
11013 existingWaypointCounter == pasted->GetnPoints()) {
11014 for (Route *proute : *pRouteList) {
11015 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11016 createNewRoute = false;
11017 break;
11018 }
11019 }
11020 }
11021
11022 Route *newRoute = 0;
11023 RoutePoint *newPoint = 0;
11024
11025 if (createNewRoute) {
11026 newRoute = new Route();
11027 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11028 }
11029
11030 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11031 curPoint = pasted->GetPoint(i);
11032 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11033 curPoint->m_bPtIsSelected = false;
11034 newPoint = pWayPointMan->GetNearbyWaypoint(
11035 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11036 newPoint->SetName(curPoint->GetName());
11037 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11038
11039 if (createNewRoute) newRoute->AddPoint(newPoint);
11040 } else {
11041 curPoint->m_bPtIsSelected = false;
11042
11043 newPoint = new RoutePoint(curPoint);
11044 newPoint->m_bIsolatedMark = false;
11045 newPoint->SetIconName("circle");
11046 newPoint->m_bIsVisible = true;
11047 newPoint->m_bShowName = false;
11048 newPoint->SetShared(false);
11049
11050 newRoute->AddPoint(newPoint);
11051 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11052 newPoint);
11053 // pConfig->AddNewWayPoint(newPoint, -1);
11054 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11055 pWayPointMan->AddRoutePoint(newPoint);
11056 }
11057 if (i > 1 && createNewRoute)
11058 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11059 curPoint->m_lat, curPoint->m_lon,
11060 prevPoint, newPoint, newRoute);
11061 prevPoint = newPoint;
11062 }
11063
11064 if (createNewRoute) {
11065 pRouteList->push_back(newRoute);
11066 // pConfig->AddNewRoute(newRoute); // use auto next num
11067 NavObj_dB::GetInstance().InsertRoute(newRoute);
11068
11069 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11070 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11071 }
11072
11073 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11074 pRouteManagerDialog->UpdateRouteListCtrl();
11075 pRouteManagerDialog->UpdateWptListCtrl();
11076 }
11077 top_frame::Get()->InvalidateAllGL();
11078 top_frame::Get()->RefreshAllCanvas(false);
11079 }
11080 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11081 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11082}
11083
11084void pupHandler_PasteTrack() {
11085 Kml kml;
11086
11087 int pasteBuffer = kml.ParsePasteBuffer();
11088 Track *pasted = kml.GetParsedTrack();
11089 if (!pasted) return;
11090
11091 TrackPoint *curPoint;
11092
11093 Track *newTrack = new Track();
11094 TrackPoint *newPoint;
11095 TrackPoint *prevPoint = NULL;
11096
11097 newTrack->SetName(pasted->GetName());
11098
11099 for (int i = 0; i < pasted->GetnPoints(); i++) {
11100 curPoint = pasted->GetPoint(i);
11101
11102 newPoint = new TrackPoint(curPoint);
11103
11104 wxDateTime now = wxDateTime::Now();
11105 newPoint->SetCreateTime(curPoint->GetCreateTime());
11106
11107 newTrack->AddPoint(newPoint);
11108
11109 if (prevPoint)
11110 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11111 newPoint->m_lat, newPoint->m_lon,
11112 prevPoint, newPoint, newTrack);
11113
11114 prevPoint = newPoint;
11115 }
11116
11117 g_TrackList.push_back(newTrack);
11118 // pConfig->AddNewTrack(newTrack);
11119 NavObj_dB::GetInstance().InsertTrack(newTrack);
11120
11121 top_frame::Get()->InvalidateAllGL();
11122 top_frame::Get()->RefreshAllCanvas(false);
11123}
11124
11125bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11126 wxJSONValue v;
11127 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11128 v["CursorPosition_x"] = x;
11129 v["CursorPosition_y"] = y;
11130 // Send a limited set of selection types depending on what is
11131 // found under the mouse point.
11132 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11133 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11134 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11135
11136 wxJSONWriter w;
11137 wxString out;
11138 w.Write(v, out);
11139 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11140
11141 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11142
11143#if 0
11144#define SELTYPE_UNKNOWN 0x0001
11145#define SELTYPE_ROUTEPOINT 0x0002
11146#define SELTYPE_ROUTESEGMENT 0x0004
11147#define SELTYPE_TIDEPOINT 0x0008
11148#define SELTYPE_CURRENTPOINT 0x0010
11149#define SELTYPE_ROUTECREATE 0x0020
11150#define SELTYPE_AISTARGET 0x0040
11151#define SELTYPE_MARKPOINT 0x0080
11152#define SELTYPE_TRACKSEGMENT 0x0100
11153#define SELTYPE_DRAGHANDLE 0x0200
11154#endif
11155
11156 if (g_bhide_context_menus) return true;
11157 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11158 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11159 m_pIDXCandidate, m_nmea_log);
11160
11161 Connect(
11162 wxEVT_COMMAND_MENU_SELECTED,
11163 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11164
11165#ifdef __WXGTK__
11166 // Funny requirement here for gtk, to clear the menu trigger event
11167 // TODO
11168 // Causes a slight "flasH" of the menu,
11169 if (m_inLongPress) {
11170 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11171 m_inLongPress = false;
11172 }
11173#endif
11174
11175 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11176
11177 Disconnect(
11178 wxEVT_COMMAND_MENU_SELECTED,
11179 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11180
11181 delete m_canvasMenu;
11182 m_canvasMenu = NULL;
11183
11184#ifdef __WXQT__
11185 // gFrame->SurfaceToolbar();
11186 // g_MainToolbar->Raise();
11187#endif
11188
11189 return true;
11190}
11191
11192void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11193 // Pass menu events from the canvas to the menu handler
11194 // This is necessarily in ChartCanvas since that is the menu's parent.
11195 if (m_canvasMenu) {
11196 m_canvasMenu->PopupMenuHandler(event);
11197 }
11198 return;
11199}
11200
11201void ChartCanvas::StartRoute() {
11202 // Do not allow more than one canvas to create a route at one time.
11203 if (g_brouteCreating) return;
11204
11205 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11206
11207 g_brouteCreating = true;
11208 m_routeState = 1;
11209 m_bDrawingRoute = false;
11210 SetCursor(*pCursorPencil);
11211 // SetCanvasToolbarItemState(ID_ROUTE, true);
11212 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11213
11214 HideGlobalToolbar();
11215
11216#ifdef __ANDROID__
11217 androidSetRouteAnnunciator(true);
11218#endif
11219}
11220
11221wxString ChartCanvas::FinishRoute() {
11222 m_routeState = 0;
11223 m_prev_pMousePoint = NULL;
11224 m_bDrawingRoute = false;
11225 wxString rv = "";
11226 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11227
11228 // SetCanvasToolbarItemState(ID_ROUTE, false);
11229 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11230#ifdef __ANDROID__
11231 androidSetRouteAnnunciator(false);
11232#endif
11233
11234 SetCursor(*pCursorArrow);
11235
11236 if (m_pMouseRoute) {
11237 if (m_bAppendingRoute) {
11238 // pConfig->UpdateRoute(m_pMouseRoute);
11239 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11240 } else {
11241 if (m_pMouseRoute->GetnPoints() > 1) {
11242 // pConfig->AddNewRoute(m_pMouseRoute);
11243 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11244 } else {
11245 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11246 m_pMouseRoute = NULL;
11247 }
11248 }
11249 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11250
11251 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11252 (pRoutePropDialog->IsShown())) {
11253 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11254 }
11255
11256 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11257 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11258 pRouteManagerDialog->UpdateRouteListCtrl();
11259 }
11260 }
11261 m_bAppendingRoute = false;
11262 m_pMouseRoute = NULL;
11263
11264 m_pSelectedRoute = NULL;
11265
11266 undo->InvalidateUndo();
11267 top_frame::Get()->RefreshAllCanvas(true);
11268
11269 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11270
11271 ShowGlobalToolbar();
11272
11273 g_brouteCreating = false;
11274
11275 return rv;
11276}
11277
11278void ChartCanvas::HideGlobalToolbar() {
11279 if (m_canvasIndex == 0) {
11280 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11281 }
11282}
11283
11284void ChartCanvas::ShowGlobalToolbar() {
11285 if (m_canvasIndex == 0) {
11286 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11287 }
11288}
11289
11290void ChartCanvas::ShowAISTargetList() {
11291 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11292 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11293 }
11294
11295 g_pAISTargetList->UpdateAISTargetList();
11296}
11297
11298void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11299 if (!m_bShowOutlines) return;
11300
11301 if (!ChartData) return;
11302
11303 int nEntry = ChartData->GetChartTableEntries();
11304
11305 for (int i = 0; i < nEntry; i++) {
11306 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11307
11308 // Check to see if the candidate chart is in the currently active group
11309 bool b_group_draw = false;
11310 if (m_groupIndex > 0) {
11311 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11312 int index = pt->GetGroupArray()[ig];
11313 if (m_groupIndex == index) {
11314 b_group_draw = true;
11315 break;
11316 }
11317 }
11318 } else
11319 b_group_draw = true;
11320
11321 if (b_group_draw) RenderChartOutline(dc, i, vp);
11322 }
11323
11324 // On CM93 Composite Charts, draw the outlines of the next smaller
11325 // scale cell
11326 cm93compchart *pcm93 = NULL;
11327 if (VPoint.b_quilt) {
11328 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11329 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11330 pcm93 = (cm93compchart *)pch;
11331 break;
11332 }
11333 } else if (m_singleChart &&
11334 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11335 pcm93 = (cm93compchart *)m_singleChart;
11336
11337 if (pcm93) {
11338 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11339 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11340
11341 if (zoom_factor > 8.0) {
11342 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11343 dc.SetPen(mPen);
11344 } else {
11345 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11346 dc.SetPen(mPen);
11347 }
11348
11349 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11350 }
11351}
11352
11353void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11354#ifdef ocpnUSE_GL
11355 if (g_bopengl && m_glcc) {
11356 /* opengl version specially optimized */
11357 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11358 return;
11359 }
11360#endif
11361
11362 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11363 if (!ChartData->IsChartAvailable(dbIndex)) return;
11364 }
11365
11366 float plylat, plylon;
11367 float plylat1, plylon1;
11368
11369 int pixx, pixy, pixx1, pixy1;
11370
11371 LLBBox box;
11372 ChartData->GetDBBoundingBox(dbIndex, box);
11373
11374 // Don't draw an outline in the case where the chart covers the entire world
11375 // */
11376 if (box.GetLonRange() == 360) return;
11377
11378 double lon_bias = 0;
11379 // chart is outside of viewport lat/lon bounding box
11380 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11381
11382 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11383
11384 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11385 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11386
11387 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11388 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11389
11390 else
11391 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11392
11393 // Are there any aux ply entries?
11394 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11395 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11396 {
11397 wxPoint r, r1;
11398
11399 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11400 plylon += lon_bias;
11401
11402 GetCanvasPointPix(plylat, plylon, &r);
11403 pixx = r.x;
11404 pixy = r.y;
11405
11406 for (int i = 0; i < nPly - 1; i++) {
11407 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11408 plylon1 += lon_bias;
11409
11410 GetCanvasPointPix(plylat1, plylon1, &r1);
11411 pixx1 = r1.x;
11412 pixy1 = r1.y;
11413
11414 int pixxs1 = pixx1;
11415 int pixys1 = pixy1;
11416
11417 bool b_skip = false;
11418
11419 if (vp.chart_scale > 5e7) {
11420 // calculate projected distance between these two points in meters
11421 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11422 pow((double)(pixy1 - pixy), 2)) /
11423 vp.view_scale_ppm;
11424
11425 if (dist > 0.0) {
11426 // calculate GC distance between these two points in meters
11427 double distgc =
11428 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11429
11430 // If the distances are nonsense, it means that the scale is very
11431 // small and the segment wrapped the world So skip it....
11432 // TODO improve this to draw two segments
11433 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11434 b_skip = true;
11435 } else
11436 b_skip = true;
11437 }
11438
11439 ClipResult res = cohen_sutherland_line_clip_i(
11440 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11441 if (res != Invisible && !b_skip)
11442 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11443
11444 plylat = plylat1;
11445 plylon = plylon1;
11446 pixx = pixxs1;
11447 pixy = pixys1;
11448 }
11449
11450 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11451 plylon1 += lon_bias;
11452
11453 GetCanvasPointPix(plylat1, plylon1, &r1);
11454 pixx1 = r1.x;
11455 pixy1 = r1.y;
11456
11457 ClipResult res = cohen_sutherland_line_clip_i(
11458 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11459 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11460 }
11461
11462 else // Use Aux PlyPoints
11463 {
11464 wxPoint r, r1;
11465
11466 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11467 for (int j = 0; j < nAuxPlyEntries; j++) {
11468 int nAuxPly =
11469 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11470 GetCanvasPointPix(plylat, plylon, &r);
11471 pixx = r.x;
11472 pixy = r.y;
11473
11474 for (int i = 0; i < nAuxPly - 1; i++) {
11475 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11476
11477 GetCanvasPointPix(plylat1, plylon1, &r1);
11478 pixx1 = r1.x;
11479 pixy1 = r1.y;
11480
11481 int pixxs1 = pixx1;
11482 int pixys1 = pixy1;
11483
11484 bool b_skip = false;
11485
11486 if (vp.chart_scale > 5e7) {
11487 // calculate projected distance between these two points in meters
11488 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11489 ((pixy1 - pixy) * (pixy1 - pixy))) /
11490 vp.view_scale_ppm;
11491 if (dist > 0.0) {
11492 // calculate GC distance between these two points in meters
11493 double distgc =
11494 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11495
11496 // If the distances are nonsense, it means that the scale is very
11497 // small and the segment wrapped the world So skip it....
11498 // TODO improve this to draw two segments
11499 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11500 b_skip = true;
11501 } else
11502 b_skip = true;
11503 }
11504
11505 ClipResult res = cohen_sutherland_line_clip_i(
11506 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11507 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11508
11509 plylat = plylat1;
11510 plylon = plylon1;
11511 pixx = pixxs1;
11512 pixy = pixys1;
11513 }
11514
11515 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11516 GetCanvasPointPix(plylat1, plylon1, &r1);
11517 pixx1 = r1.x;
11518 pixy1 = r1.y;
11519
11520 ClipResult res = cohen_sutherland_line_clip_i(
11521 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11522 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11523 }
11524 }
11525}
11526
11527static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11528 const wxArrayString &legend) {
11529 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11530
11531 int pointsize = dFont->GetPointSize();
11532 pointsize /= OCPN_GetWinDIPScaleFactor();
11533
11534 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11535 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11536 false, dFont->GetFaceName());
11537
11538 dc.SetFont(*psRLI_font);
11539
11540 int h = 0;
11541 int w = 0;
11542 int hl, wl;
11543
11544 int xp, yp;
11545 int hilite_offset = 3;
11546
11547 for (wxString line : legend) {
11548#ifdef __WXMAC__
11549 wxScreenDC sdc;
11550 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11551#else
11552 dc.GetTextExtent(line, &wl, &hl);
11553 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11554 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11555#endif
11556 h += hl;
11557 w = wxMax(w, wl);
11558 }
11559 w += (hl / 2); // Add a little right pad
11560
11561 xp = ref_point.x - w;
11562 yp = ref_point.y;
11563 yp += hilite_offset;
11564
11565 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11566
11567 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11568 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11569
11570 for (wxString line : legend) {
11571 dc.DrawText(line, xp, yp);
11572 yp += hl;
11573 }
11574}
11575
11576void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11577 if (!g_bAllowShipToActive) return;
11578
11579 Route *rt = g_pRouteMan->GetpActiveRoute();
11580 if (!rt) return;
11581
11582 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11583 wxPoint2DDouble pa, pb;
11585 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11586
11587 // set pen
11588 int width =
11589 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11590 if (rt->m_width != wxPENSTYLE_INVALID)
11591 width = rt->m_width; // set route pen style if any
11592 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11593 g_shipToActiveStyle, 5)]; // get setting pen style
11594 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11595 wxColour color =
11596 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11597 : // set setting route pen color
11598 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11599 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11600
11601 dc.SetPen(*mypen);
11602 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11603
11604 if (!Use_Opengl)
11605 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11606 (int)pb.m_y, GetVP(), true);
11607
11608#ifdef ocpnUSE_GL
11609 else {
11610#ifdef USE_ANDROID_GLES2
11611 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11612#else
11613 if (style != wxPENSTYLE_SOLID) {
11614 if (glChartCanvas::dash_map.find(style) !=
11615 glChartCanvas::dash_map.end()) {
11616 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11617 dc.SetPen(*mypen);
11618 }
11619 }
11620 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11621#endif
11622
11623 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11624 (int)pb.m_x, (int)pb.m_y, GetVP());
11625 }
11626#endif
11627 }
11628}
11629
11630void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11631 Route *route = 0;
11632 if (m_routeState >= 2) route = m_pMouseRoute;
11633 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11634 route = m_pMeasureRoute;
11635
11636 if (!route) return;
11637
11638 // Validate route pointer
11639 if (!g_pRouteMan->IsRouteValid(route)) return;
11640
11641 double render_lat = m_cursor_lat;
11642 double render_lon = m_cursor_lon;
11643
11644 int np = route->GetnPoints();
11645 if (np) {
11646 if (g_btouch && (np > 1)) np--;
11647 RoutePoint rp = route->GetPoint(np);
11648 render_lat = rp.m_lat;
11649 render_lon = rp.m_lon;
11650 }
11651
11652 double rhumbBearing, rhumbDist;
11653 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11654 &rhumbBearing, &rhumbDist);
11655 double brg = rhumbBearing;
11656 double dist = rhumbDist;
11657
11658 // Skip GreatCircle rubberbanding on touch devices.
11659 if (!g_btouch) {
11660 double gcBearing, gcBearing2, gcDist;
11661 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11662 m_cursor_lat, &gcDist, &gcBearing,
11663 &gcBearing2);
11664 double gcDistm = gcDist / 1852.0;
11665
11666 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11667 rhumbBearing = 90.;
11668
11669 wxPoint destPoint, lastPoint;
11670
11671 route->m_NextLegGreatCircle = false;
11672 int milesDiff = rhumbDist - gcDistm;
11673 if (milesDiff > 1) {
11674 brg = gcBearing;
11675 dist = gcDistm;
11676 route->m_NextLegGreatCircle = true;
11677 }
11678
11679 // FIXME (MacOS, the first segment is rendered wrong)
11680 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11681 &lastPoint);
11682
11683 if (route->m_NextLegGreatCircle) {
11684 for (int i = 1; i <= milesDiff; i++) {
11685 double p = (double)i * (1.0 / (double)milesDiff);
11686 double pLat, pLon;
11687 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11688 &pLon, &pLat, &gcBearing2);
11689 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11690 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11691 false);
11692 lastPoint = destPoint;
11693 }
11694 } else {
11695 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11696 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11697 false);
11698 if (m_bMeasure_DistCircle) {
11699 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11700 powf((float)(r_rband.y - lastPoint.y), 2));
11701
11702 dc.SetPen(*g_pRouteMan->GetRoutePen());
11703 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11704 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11705 }
11706 }
11707 }
11708 }
11709
11710 wxString routeInfo;
11711 wxArrayString infoArray;
11712 double varBrg = 0;
11713 if (g_bShowTrue)
11714 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11715 0x00B0);
11716
11717 if (g_bShowMag) {
11718 double latAverage = (m_cursor_lat + render_lat) / 2;
11719 double lonAverage = (m_cursor_lon + render_lon) / 2;
11720 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11721
11722 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11723 (int)varBrg, 0x00B0);
11724 }
11725 routeInfo << " " << FormatDistanceAdaptive(dist);
11726 infoArray.Add(routeInfo);
11727 routeInfo.Clear();
11728
11729 // To make it easier to use a route as a bearing on a charted object add for
11730 // the first leg also the reverse bearing.
11731 if (np == 1) {
11732 routeInfo << "Reverse: ";
11733 if (g_bShowTrue)
11734 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11735 (int)(brg + 180.) % 360, 0x00B0);
11736 if (g_bShowMag)
11737 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11738 (int)(varBrg + 180.) % 360, 0x00B0);
11739 infoArray.Add(routeInfo);
11740 routeInfo.Clear();
11741 }
11742
11743 wxString s0;
11744 if (!route->m_bIsInLayer)
11745 s0.Append(_("Route") + ": ");
11746 else
11747 s0.Append(_("Layer Route: "));
11748
11749 double disp_length = route->m_route_length;
11750 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11751 s0 += FormatDistanceAdaptive(disp_length);
11752
11753 infoArray.Add(s0);
11754 routeInfo.Clear();
11755
11756 RouteLegInfo(dc, r_rband, infoArray);
11757
11758 m_brepaint_piano = true;
11759}
11760
11761void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11762 if (!m_bShowVisibleSectors) return;
11763
11764 if (g_bDeferredInitDone) {
11765 // need to re-evaluate sectors?
11766 double rhumbBearing, rhumbDist;
11767 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11768 &rhumbBearing, &rhumbDist);
11769
11770 if (rhumbDist > 0.05) // miles
11771 {
11772 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11773 m_sectorlegsVisible);
11774 m_sector_glat = gLat;
11775 m_sector_glon = gLon;
11776 }
11777 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11778 }
11779}
11780
11781void ChartCanvas::WarpPointerDeferred(int x, int y) {
11782 warp_x = x;
11783 warp_y = y;
11784 warp_flag = true;
11785}
11786
11787int s_msg;
11788
11789void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11790 if (!ps52plib) return;
11791
11792 if (VPoint.b_quilt) { // quilted
11793 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11794
11795 if (m_pQuilt->IsQuiltVector()) {
11796 if (ps52plib->GetStateHash() != m_s52StateHash) {
11797 UpdateS52State();
11798 m_s52StateHash = ps52plib->GetStateHash();
11799 }
11800 }
11801 } else {
11802 if (ps52plib->GetStateHash() != m_s52StateHash) {
11803 UpdateS52State();
11804 m_s52StateHash = ps52plib->GetStateHash();
11805 }
11806 }
11807
11808 // Plugin charts
11809 bool bSendPlibState = true;
11810 if (VPoint.b_quilt) { // quilted
11811 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11812 }
11813
11814 if (bSendPlibState) {
11815 wxJSONValue v;
11816 v["OpenCPN Version Major"] = VERSION_MAJOR;
11817 v["OpenCPN Version Minor"] = VERSION_MINOR;
11818 v["OpenCPN Version Patch"] = VERSION_PATCH;
11819 v["OpenCPN Version Date"] = VERSION_DATE;
11820 v["OpenCPN Version Full"] = VERSION_FULL;
11821
11822 // S52PLIB state
11823 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11824 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11825 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11826 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11827 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11828 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11829 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11830
11831 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11832
11833 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11834 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11835
11836 // Global S52 options
11837
11838 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11839 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11840 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11841 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11842 ps52plib->m_bShowS57ImportantTextOnly;
11843 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11844 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11845 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11846 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11847 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11848
11849 // Some global GUI parameters, for completeness
11850 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11851 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11852 v["OpenCPN Scale Factor Exp"] =
11853 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11854 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11855
11856 wxJSONWriter w;
11857 wxString out;
11858 w.Write(v, out);
11859
11860 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11861 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11862 g_lastS52PLIBPluginMessage = out;
11863 }
11864 }
11865}
11866int spaint;
11867int s_in_update;
11868void ChartCanvas::OnPaint(wxPaintEvent &event) {
11869 wxPaintDC dc(this);
11870
11871 // GetToolbar()->Show( m_bToolbarEnable );
11872
11873 // Paint updates may have been externally disabled (temporarily, to avoid
11874 // Yield() recursion performance loss) It is important that the wxPaintDC is
11875 // built, even if we elect to not process this paint message. Otherwise, the
11876 // paint message may not be removed from the message queue, esp on Windows.
11877 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11878
11879 if (!m_b_paint_enable) {
11880 return;
11881 }
11882
11883 // If necessary, reconfigure the S52 PLIB
11885
11886#ifdef ocpnUSE_GL
11887 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11888
11889 if (m_glcc && g_bopengl) {
11890 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11891 s_in_update++;
11892 m_glcc->Update();
11893 s_in_update--;
11894 }
11895
11896 return;
11897 }
11898#endif
11899
11900 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11901
11902 wxRegion ru = GetUpdateRegion();
11903
11904 int rx, ry, rwidth, rheight;
11905 ru.GetBox(rx, ry, rwidth, rheight);
11906
11907#ifdef ocpnUSE_DIBSECTION
11908 ocpnMemDC temp_dc;
11909#else
11910 wxMemoryDC temp_dc;
11911#endif
11912
11913 long height = GetVP().pix_height;
11914
11915#ifdef __WXMAC__
11916 // On OS X we have to explicitly extend the region for the piano area
11917 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11918 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11919 height += m_Piano->GetHeight();
11920#endif // __WXMAC__
11921 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11922
11923 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11924 if (pthumbwin) {
11925 int thumbx, thumby, thumbsx, thumbsy;
11926 pthumbwin->GetPosition(&thumbx, &thumby);
11927 pthumbwin->GetSize(&thumbsx, &thumbsy);
11928 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11929
11930 if (pthumbwin->IsShown()) {
11931 rgn_chart.Subtract(rgn_thumbwin);
11932 ru.Subtract(rgn_thumbwin);
11933 }
11934 }
11935
11936 // subtract the chart bar if it isn't transparent, and determine if we need to
11937 // paint it
11938 wxRegion rgn_blit = ru;
11939 if (g_bShowChartBar) {
11940 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11941 GetClientSize().x, m_Piano->GetHeight());
11942
11943 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11944 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11945 if (style->chartStatusWindowTransparent)
11946 m_brepaint_piano = true;
11947 else
11948 ru.Subtract(chart_bar_rect);
11949 }
11950 }
11951
11952 if (m_Compass && m_Compass->IsShown()) {
11953 wxRect compassRect = m_Compass->GetRect();
11954 if (ru.Contains(compassRect) != wxOutRegion) {
11955 ru.Subtract(compassRect);
11956 }
11957 }
11958
11959 if (m_notification_button) {
11960 wxRect noteRect = m_notification_button->GetRect();
11961 if (ru.Contains(noteRect) != wxOutRegion) {
11962 ru.Subtract(noteRect);
11963 }
11964 }
11965
11966 // Is this viewpoint the same as the previously painted one?
11967 bool b_newview = true;
11968
11969 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11970 (m_cache_vp.rotation == VPoint.rotation) &&
11971 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11972 m_cache_vp.IsValid()) {
11973 b_newview = false;
11974 }
11975
11976 // If the ViewPort is skewed or rotated, we may be able to use the cached
11977 // rotated bitmap.
11978 bool b_rcache_ok = false;
11979 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11980 b_rcache_ok = !b_newview;
11981
11982 // Make a special VP
11983 if (VPoint.b_MercatorProjectionOverride)
11984 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11985 ViewPort svp = VPoint;
11986
11987 svp.pix_width = svp.rv_rect.width;
11988 svp.pix_height = svp.rv_rect.height;
11989
11990 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11991 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11992 // VPoint.rv_rect.height);
11993
11994 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11995
11996 // If we are going to use the cached rotated image, there is no need to fetch
11997 // any chart data and this will do it...
11998 if (b_rcache_ok) chart_get_region.Clear();
11999
12000 // Blit pan acceleration
12001 if (VPoint.b_quilt) // quilted
12002 {
12003 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12004
12005 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12006
12007 bool busy = false;
12008 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12009 m_cache_vp.rotation != VPoint.rotation)) {
12010 AbstractPlatform::ShowBusySpinner();
12011 busy = true;
12012 }
12013
12014 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12015 (m_working_bm.GetHeight() != svp.pix_height))
12016 m_working_bm.Create(svp.pix_width, svp.pix_height,
12017 -1); // make sure the target is big enoug
12018
12019 if (fabs(VPoint.rotation) < 0.01) {
12020 bool b_save = true;
12021
12022 if (g_SencThreadManager) {
12023 if (g_SencThreadManager->GetJobCount()) {
12024 b_save = false;
12025 m_cache_vp.Invalidate();
12026 }
12027 }
12028
12029 // If the saved wxBitmap from last OnPaint is useable
12030 // calculate the blit parameters
12031
12032 // We can only do screen blit painting if subsequent ViewPorts differ by
12033 // whole pixels So, in small scale bFollow mode, force the full screen
12034 // render. This seems a hack....There may be better logic here.....
12035
12036 // if(m_bFollow)
12037 // b_save = false;
12038
12039 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12040 if (b_newview) {
12041 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12042 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12043
12044 int dy = c_new.y - c_old.y;
12045 int dx = c_new.x - c_old.x;
12046
12047 // printf("In OnPaint Trying Blit dx: %d
12048 // dy:%d\n\n", dx, dy);
12049
12050 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12051 if (dx || dy) {
12052 // Blit the reuseable portion of the cached wxBitmap to a working
12053 // bitmap
12054 temp_dc.SelectObject(m_working_bm);
12055
12056 wxMemoryDC cache_dc;
12057 cache_dc.SelectObject(m_cached_chart_bm);
12058
12059 if (dy > 0) {
12060 if (dx > 0) {
12061 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12062 VPoint.pix_height - dy, &cache_dc, dx, dy);
12063 } else {
12064 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12065 VPoint.pix_height - dy, &cache_dc, 0, dy);
12066 }
12067
12068 } else {
12069 if (dx > 0) {
12070 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12071 VPoint.pix_height + dy, &cache_dc, dx, 0);
12072 } else {
12073 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12074 VPoint.pix_height + dy, &cache_dc, 0, 0);
12075 }
12076 }
12077
12078 OCPNRegion update_region;
12079 if (dy) {
12080 if (dy > 0)
12081 update_region.Union(
12082 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12083 else
12084 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12085 }
12086
12087 if (dx) {
12088 if (dx > 0)
12089 update_region.Union(
12090 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12091 else
12092 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12093 }
12094
12095 // Render the new region
12096 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12097 update_region);
12098 cache_dc.SelectObject(wxNullBitmap);
12099 } else {
12100 // No sensible (dx, dy) change in the view, so use the cached
12101 // member bitmap
12102 temp_dc.SelectObject(m_cached_chart_bm);
12103 b_save = false;
12104 }
12105 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12106
12107 } else // not blitable
12108 {
12109 temp_dc.SelectObject(m_working_bm);
12110 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12111 chart_get_region);
12112 }
12113 } else {
12114 // No change in the view, so use the cached member bitmap2
12115 temp_dc.SelectObject(m_cached_chart_bm);
12116 b_save = false;
12117 }
12118 } else // cached bitmap is not yet valid
12119 {
12120 temp_dc.SelectObject(m_working_bm);
12121 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12122 chart_get_region);
12123 }
12124
12125 // Save the fully rendered quilt image as a wxBitmap member of this class
12126 if (b_save) {
12127 // if((m_cached_chart_bm.GetWidth() !=
12128 // svp.pix_width) ||
12129 // (m_cached_chart_bm.GetHeight() !=
12130 // svp.pix_height))
12131 // m_cached_chart_bm.Create(svp.pix_width,
12132 // svp.pix_height, -1); // target wxBitmap
12133 // is big enough
12134 wxMemoryDC scratch_dc_0;
12135 scratch_dc_0.SelectObject(m_cached_chart_bm);
12136 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12137
12138 scratch_dc_0.SelectObject(wxNullBitmap);
12139
12140 m_bm_cache_vp =
12141 VPoint; // save the ViewPort associated with the cached wxBitmap
12142 }
12143 }
12144
12145 else // quilted, rotated
12146 {
12147 temp_dc.SelectObject(m_working_bm);
12148 OCPNRegion chart_get_all_region(
12149 wxRect(0, 0, svp.pix_width, svp.pix_height));
12150 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12151 chart_get_all_region);
12152 }
12153
12154 AbstractPlatform::HideBusySpinner();
12155
12156 }
12157
12158 else // not quilted
12159 {
12160 if (!m_singleChart) {
12161 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12162 dc.Clear();
12163 return;
12164 }
12165
12166 if (!chart_get_region.IsEmpty()) {
12167 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12168 }
12169 }
12170
12171 if (temp_dc.IsOk()) {
12172 // Arrange to render the World Chart vector data behind the rendered
12173 // current chart so that uncovered canvas areas show at least the world
12174 // chart.
12175 OCPNRegion chartValidRegion;
12176 if (!VPoint.b_quilt) {
12177 // Make a region covering the current chart on the canvas
12178
12179 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12180 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12181 else {
12182 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12183 // require that the viewport passed here have pix_width and pix_height
12184 // set to the actual display, not the virtual (rv_rect) sizes
12185 // (the vector calculations require the virtual sizes in svp)
12186
12187 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12188 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12189 }
12190 } else
12191 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12192
12193 temp_dc.DestroyClippingRegion();
12194
12195 // Copy current chart region
12196 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12197
12198 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12199
12200 if (!backgroundRegion.IsEmpty()) {
12201 // Draw the Background Chart only in the areas NOT covered by the
12202 // current chart view
12203
12204 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12205 clipping regions with more than 1 rectangle so... */
12206 wxColour water = pWorldBackgroundChart->water;
12207 if (water.IsOk()) {
12208 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12209 temp_dc.SetBrush(wxBrush(water));
12210 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12211 while (upd.HaveRects()) {
12212 wxRect rect = upd.GetRect();
12213 temp_dc.DrawRectangle(rect);
12214 upd.NextRect();
12215 }
12216 }
12217 // Associate with temp_dc
12218 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12219 temp_dc.SetDeviceClippingRegion(*clip_region);
12220 delete clip_region;
12221
12222 ocpnDC bgdc(temp_dc);
12223 double r = VPoint.rotation;
12224 SetVPRotation(VPoint.skew);
12225
12226 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12227 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12228
12229 SetVPRotation(r);
12230 }
12231 } // temp_dc.IsOk();
12232
12233 wxMemoryDC *pChartDC = &temp_dc;
12234 wxMemoryDC rotd_dc;
12235
12236 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12237 // Can we use the current rotated image cache?
12238 if (!b_rcache_ok) {
12239#ifdef __WXMSW__
12240 wxMemoryDC tbase_dc;
12241 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12242 tbase_dc.SelectObject(bm_base);
12243 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12244 tbase_dc.SelectObject(wxNullBitmap);
12245#else
12246 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12247#endif
12248
12249 wxImage base_image;
12250 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12251
12252 // Use a local static image rotator to improve wxWidgets code profile
12253 // Especially, on GTK the wxRound and wxRealPoint functions are very
12254 // expensive.....
12255
12256 double angle = GetVP().skew - GetVP().rotation;
12257 wxImage ri;
12258 bool b_rot_ok = false;
12259 if (base_image.IsOk()) {
12260 ViewPort rot_vp = GetVP();
12261
12262 m_b_rot_hidef = false;
12263
12264 ri = Image_Rotate(
12265 base_image, angle,
12266 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12267 m_b_rot_hidef, &m_roffset);
12268
12269 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12270 (rot_vp.rotation == VPoint.rotation) &&
12271 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12272 rot_vp.IsValid() && (ri.IsOk())) {
12273 b_rot_ok = true;
12274 }
12275 }
12276
12277 if (b_rot_ok) {
12278 delete m_prot_bm;
12279 m_prot_bm = new wxBitmap(ri);
12280 }
12281
12282 m_roffset.x += VPoint.rv_rect.x;
12283 m_roffset.y += VPoint.rv_rect.y;
12284 }
12285
12286 if (m_prot_bm && m_prot_bm->IsOk()) {
12287 rotd_dc.SelectObject(*m_prot_bm);
12288 pChartDC = &rotd_dc;
12289 } else {
12290 pChartDC = &temp_dc;
12291 m_roffset = wxPoint(0, 0);
12292 }
12293 } else { // unrotated
12294 pChartDC = &temp_dc;
12295 m_roffset = wxPoint(0, 0);
12296 }
12297
12298 wxPoint offset = m_roffset;
12299
12300 // Save the PixelCache viewpoint for next time
12301 m_cache_vp = VPoint;
12302
12303 // Set up a scratch DC for overlay objects
12304 wxMemoryDC mscratch_dc;
12305 mscratch_dc.SelectObject(*pscratch_bm);
12306
12307 mscratch_dc.ResetBoundingBox();
12308 mscratch_dc.DestroyClippingRegion();
12309 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12310
12311 // Blit the externally invalidated areas of the chart onto the scratch dc
12312 wxRegionIterator upd(rgn_blit); // get the update rect list
12313 while (upd) {
12314 wxRect rect = upd.GetRect();
12315
12316 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12317 rect.x - offset.x, rect.y - offset.y);
12318 upd++;
12319 }
12320
12321 // If multi-canvas, indicate which canvas has keyboard focus
12322 // by drawing a simple blue bar at the top.
12323 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12324 if (this == wxWindow::FindFocus()) {
12325 g_focusCanvas = this;
12326
12327 wxColour colour = GetGlobalColor("BLUE4");
12328 mscratch_dc.SetPen(wxPen(colour));
12329 mscratch_dc.SetBrush(wxBrush(colour));
12330
12331 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12332 mscratch_dc.DrawRectangle(activeRect);
12333 }
12334 }
12335
12336 // Any MBtiles?
12337 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12338 unsigned int im = stackIndexArray.size();
12339 if (VPoint.b_quilt && im > 0) {
12340 std::vector<int> tiles_to_show;
12341 for (unsigned int is = 0; is < im; is++) {
12342 const ChartTableEntry &cte =
12343 ChartData->GetChartTableEntry(stackIndexArray[is]);
12344 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12345 continue;
12346 }
12347 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12348 tiles_to_show.push_back(stackIndexArray[is]);
12349 }
12350 }
12351
12352 if (tiles_to_show.size())
12353 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12354 }
12355
12356 // May get an unexpected OnPaint call while switching display modes
12357 // Guard for that.
12358 if (!g_bopengl) {
12359 ocpnDC scratch_dc(mscratch_dc);
12360 RenderAlertMessage(mscratch_dc, GetVP());
12361 }
12362
12363#if 0
12364 // quiting?
12365 if (g_bquiting) {
12366#ifdef ocpnUSE_DIBSECTION
12367 ocpnMemDC q_dc;
12368#else
12369 wxMemoryDC q_dc;
12370#endif
12371 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12372 q_dc.SelectObject(qbm);
12373
12374 // Get a copy of the screen
12375 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12376
12377 // Draw a rectangle over the screen with a stipple brush
12378 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12379 q_dc.SetBrush(qbr);
12380 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12381
12382 // Blit back into source
12383 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12384 wxCOPY);
12385
12386 q_dc.SelectObject(wxNullBitmap);
12387 }
12388#endif
12389
12390#if 0
12391 // It is possible that this two-step method may be reuired for some platforms.
12392 // So, retain in the code base to aid recovery if necessary
12393
12394 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12395 if( VPoint.b_quilt ) {
12396 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12397 ChartBase *chart = m_pQuilt->GetRefChart();
12398 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12399
12400 // Clear the text Global declutter list
12401 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12402 if(ChPI)
12403 ChPI->ClearPLIBTextList();
12404 else{
12405 if(ps52plib)
12406 ps52plib->ClearTextList();
12407 }
12408
12409 wxMemoryDC t_dc;
12410 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12411
12412 wxColor maskBackground = wxColour(1,0,0);
12413 t_dc.SelectObject( qbm );
12414 t_dc.SetBackground(wxBrush(maskBackground));
12415 t_dc.Clear();
12416
12417 // Copy the scratch DC into the new bitmap
12418 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12419
12420 // Render the text to the new bitmap
12421 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12422 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12423
12424 // Copy the new bitmap back to the scratch dc
12425 wxRegionIterator upd_final( ru );
12426 while( upd_final ) {
12427 wxRect rect = upd_final.GetRect();
12428 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12429 upd_final++;
12430 }
12431
12432 t_dc.SelectObject( wxNullBitmap );
12433 }
12434 }
12435 }
12436#endif
12437 // Direct rendering model...
12438 if (VPoint.b_quilt) {
12439 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12440 ChartBase *chart = m_pQuilt->GetRefChart();
12441 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12442 // Clear the text Global declutter list
12443 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12444 if (ChPI)
12445 ChPI->ClearPLIBTextList();
12446 else {
12447 if (ps52plib) ps52plib->ClearTextList();
12448 }
12449
12450 // Render the text directly to the scratch bitmap
12451 OCPNRegion chart_all_text_region(
12452 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12453
12454 if (g_bShowChartBar && m_Piano) {
12455 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12456 GetVP().pix_width, m_Piano->GetHeight());
12457
12458 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12459 if (!style->chartStatusWindowTransparent)
12460 chart_all_text_region.Subtract(chart_bar_rect);
12461 }
12462
12463 if (m_Compass && m_Compass->IsShown()) {
12464 wxRect compassRect = m_Compass->GetRect();
12465 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12466 chart_all_text_region.Subtract(compassRect);
12467 }
12468 }
12469
12470 mscratch_dc.DestroyClippingRegion();
12471
12472 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12473 chart_all_text_region);
12474 }
12475 }
12476 }
12477
12478 // Now that charts are fully rendered, apply the overlay objects as decals.
12479 ocpnDC scratch_dc(mscratch_dc);
12480 DrawOverlayObjects(scratch_dc, ru);
12481
12482 // And finally, blit the scratch dc onto the physical dc
12483 wxRegionIterator upd_final(rgn_blit);
12484 while (upd_final) {
12485 wxRect rect = upd_final.GetRect();
12486 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12487 rect.y);
12488 upd_final++;
12489 }
12490
12491 // Deselect the chart bitmap from the temp_dc, so that it will not be
12492 // destroyed in the temp_dc dtor
12493 temp_dc.SelectObject(wxNullBitmap);
12494 // And for the scratch bitmap
12495 mscratch_dc.SelectObject(wxNullBitmap);
12496
12497 dc.DestroyClippingRegion();
12498
12499 PaintCleanup();
12500}
12501
12502void ChartCanvas::PaintCleanup() {
12503 // Handle the current graphic window, if present
12504 if (m_inPinch) return;
12505
12506 if (pCwin) {
12507 pCwin->Show();
12508 if (m_bTCupdate) {
12509 pCwin->Refresh();
12510 pCwin->Update();
12511 }
12512 }
12513
12514 // And set flags for next time
12515 m_bTCupdate = false;
12516
12517 // Handle deferred WarpPointer
12518 if (warp_flag) {
12519 WarpPointer(warp_x, warp_y);
12520 warp_flag = false;
12521 }
12522
12523 // Start movement timers, this runs nearly immediately.
12524 // the reason we cannot simply call it directly is the
12525 // refresh events it emits may be blocked from this paint event
12526 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12527 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12528}
12529
12530#if 0
12531wxColour GetErrorGraphicColor(double val)
12532{
12533 /*
12534 double valm = wxMin(val_max, val);
12535
12536 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12537 unsigned char red = (unsigned char)(255 * (valm/val_max));
12538
12539 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12540
12541 hv.saturation = 1.0;
12542 hv.value = 1.0;
12543
12544 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12545 return wxColour(rv.red, rv.green, rv.blue);
12546 */
12547
12548 // HTML colors taken from NOAA WW3 Web representation
12549 wxColour c;
12550 if((val > 0) && (val < 1)) c.Set("#002ad9");
12551 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12552 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12553 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12554 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12555 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12556 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12557 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12558 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12559 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12560 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12561 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12562 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12563 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12564 else if((val >= 30) && (val < 36)) c.Set("#870000");
12565 else if((val >= 36) && (val < 42)) c.Set("#690000");
12566 else if((val >= 42) && (val < 48)) c.Set("#550000");
12567 else if( val >= 48) c.Set("#410000");
12568
12569 return c;
12570}
12571
12572void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12573{
12574 wxImage gr_image(vp->pix_width, vp->pix_height);
12575 gr_image.InitAlpha();
12576
12577 double maxval = -10000;
12578 double minval = 10000;
12579
12580 double rlat, rlon;
12581 double glat, glon;
12582
12583 GetCanvasPixPoint(0, 0, rlat, rlon);
12584
12585 for(int i=1; i < vp->pix_height-1; i++)
12586 {
12587 for(int j=0; j < vp->pix_width; j++)
12588 {
12589 // Reference mercator value
12590// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12591
12592 // Georef value
12593 GetCanvasPixPoint(j, i, glat, glon);
12594
12595 maxval = wxMax(maxval, (glat - rlat));
12596 minval = wxMin(minval, (glat - rlat));
12597
12598 }
12599 rlat = glat;
12600 }
12601
12602 GetCanvasPixPoint(0, 0, rlat, rlon);
12603 for(int i=1; i < vp->pix_height-1; i++)
12604 {
12605 for(int j=0; j < vp->pix_width; j++)
12606 {
12607 // Reference mercator value
12608// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12609
12610 // Georef value
12611 GetCanvasPixPoint(j, i, glat, glon);
12612
12613 double f = ((glat - rlat)-minval)/(maxval - minval);
12614
12615 double dy = (f * 40);
12616
12617 wxColour c = GetErrorGraphicColor(dy);
12618 unsigned char r = c.Red();
12619 unsigned char g = c.Green();
12620 unsigned char b = c.Blue();
12621
12622 gr_image.SetRGB(j, i, r,g,b);
12623 if((glat - rlat )!= 0)
12624 gr_image.SetAlpha(j, i, 128);
12625 else
12626 gr_image.SetAlpha(j, i, 255);
12627
12628 }
12629 rlat = glat;
12630 }
12631
12632 // Create a Bitmap
12633 wxBitmap *pbm = new wxBitmap(gr_image);
12634 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12635 pbm->SetMask(gr_mask);
12636
12637 pmdc->DrawBitmap(*pbm, 0,0);
12638
12639 delete pbm;
12640
12641}
12642
12643#endif
12644
12645void ChartCanvas::CancelMouseRoute() {
12646 m_routeState = 0;
12647 m_pMouseRoute = NULL;
12648 m_bDrawingRoute = false;
12649}
12650
12651int ChartCanvas::GetNextContextMenuId() {
12652 return CanvasMenuHandler::GetNextContextMenuId();
12653}
12654
12655bool ChartCanvas::SetCursor(const wxCursor &c) {
12656#ifdef ocpnUSE_GL
12657 if (g_bopengl && m_glcc)
12658 return m_glcc->SetCursor(c);
12659 else
12660#endif
12661 return wxWindow::SetCursor(c);
12662}
12663
12664void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12665 if (g_bquiting) return;
12666 // Keep the mouse position members up to date
12667 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12668
12669 // Retrigger the route leg popup timer
12670 // This handles the case when the chart is moving in auto-follow mode,
12671 // but no user mouse input is made. The timer handler may Hide() the
12672 // popup if the chart moved enough n.b. We use slightly longer oneshot
12673 // value to allow this method's Refresh() to complete before potentially
12674 // getting another Refresh() in the popup timer handler.
12675 if (!m_RolloverPopupTimer.IsRunning() &&
12676 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12677 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12678 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12679 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12680
12681#ifdef ocpnUSE_GL
12682 if (m_glcc && g_bopengl) {
12683 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12684 // overlay objects.
12685 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12686
12687 m_glcc->Refresh(eraseBackground,
12688 NULL); // We always are going to render the entire screen
12689 // anyway, so make
12690 // sure that the window managers understand the invalid area
12691 // is actually the entire client area.
12692
12693 // We need to selectively Refresh some child windows, if they are visible.
12694 // Note that some children are refreshed elsewhere on timer ticks, so don't
12695 // need attention here.
12696
12697 // Thumbnail chart
12698 if (pthumbwin && pthumbwin->IsShown()) {
12699 pthumbwin->Raise();
12700 pthumbwin->Refresh(false);
12701 }
12702
12703 // ChartInfo window
12704 if (m_pCIWin && m_pCIWin->IsShown()) {
12705 m_pCIWin->Raise();
12706 m_pCIWin->Refresh(false);
12707 }
12708
12709 // if(g_MainToolbar)
12710 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12711
12712 } else
12713#endif
12714 wxWindow::Refresh(eraseBackground, rect);
12715}
12716
12717void ChartCanvas::Update() {
12718 if (m_glcc && g_bopengl) {
12719#ifdef ocpnUSE_GL
12720 m_glcc->Update();
12721#endif
12722 } else
12723 wxWindow::Update();
12724}
12725
12726void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12727 if (!pemboss) return;
12728 int x = pemboss->x, y = pemboss->y;
12729 const double factor = 200;
12730
12731 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12732 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12733 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12734
12735 // Grab a snipped image out of the chart
12736 wxMemoryDC snip_dc;
12737 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12738 snip_dc.SelectObject(snip_bmp);
12739
12740 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12741 snip_dc.SelectObject(wxNullBitmap);
12742
12743 wxImage snip_img = snip_bmp.ConvertToImage();
12744
12745 // Apply Emboss map to the snip image
12746 unsigned char *pdata = snip_img.GetData();
12747 if (pdata) {
12748 for (int y = 0; y < pemboss->height; y++) {
12749 int map_index = (y * pemboss->width);
12750 for (int x = 0; x < pemboss->width; x++) {
12751 double val = (pemboss->pmap[map_index] * factor) / 256.;
12752
12753 int nred = (int)((*pdata) + val);
12754 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12755 *pdata++ = (unsigned char)nred;
12756
12757 int ngreen = (int)((*pdata) + val);
12758 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12759 *pdata++ = (unsigned char)ngreen;
12760
12761 int nblue = (int)((*pdata) + val);
12762 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12763 *pdata++ = (unsigned char)nblue;
12764
12765 map_index++;
12766 }
12767 }
12768 }
12769
12770 // Convert embossed snip to a bitmap
12771 wxBitmap emb_bmp(snip_img);
12772
12773 // Map to another memoryDC
12774 wxMemoryDC result_dc;
12775 result_dc.SelectObject(emb_bmp);
12776
12777 // Blit to target
12778 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12779
12780 result_dc.SelectObject(wxNullBitmap);
12781}
12782
12783emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12784 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12785
12786 if (GetQuiltMode()) {
12787 // disable Overzoom indicator for MBTiles
12788 int refIndex = GetQuiltRefChartdbIndex();
12789 if (refIndex >= 0) {
12790 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12791 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12792 if (current_type == CHART_TYPE_MBTILES) {
12793 ChartBase *pChart = m_pQuilt->GetRefChart();
12794 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12795 if (ptc) {
12796 zoom_factor = ptc->GetZoomFactor();
12797 }
12798 }
12799 }
12800
12801 if (zoom_factor <= 3.9) return NULL;
12802 } else {
12803 if (m_singleChart) {
12804 if (zoom_factor <= 3.9) return NULL;
12805 } else
12806 return NULL;
12807 }
12808
12809 if (m_pEM_OverZoom) {
12810 m_pEM_OverZoom->x = 4;
12811 m_pEM_OverZoom->y = 0;
12812 if (g_MainToolbar && IsPrimaryCanvas()) {
12813 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12814 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12815 }
12816 }
12817 return m_pEM_OverZoom;
12818}
12819
12820void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12821 GridDraw(dc);
12822
12823 // bool pluginOverlayRender = true;
12824 //
12825 // if(g_canvasConfig > 0){ // Multi canvas
12826 // if(IsPrimaryCanvas())
12827 // pluginOverlayRender = false;
12828 // }
12829
12830 g_overlayCanvas = this;
12831
12832 if (g_pi_manager) {
12833 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12834 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12836 }
12837
12838 AISDrawAreaNotices(dc, GetVP(), this);
12839
12840 wxDC *pdc = dc.GetDC();
12841 if (pdc) {
12842 pdc->DestroyClippingRegion();
12843 wxDCClipper(*pdc, ru);
12844 }
12845
12846 if (m_bShowNavobjects) {
12847 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12848 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12849 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12850 DrawAnchorWatchPoints(dc);
12851 } else {
12852 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12853 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12854 }
12855
12856 AISDraw(dc, GetVP(), this);
12857 ShipDraw(dc);
12858 AlertDraw(dc);
12859
12860 RenderVisibleSectorLights(dc);
12861
12862 RenderAllChartOutlines(dc, GetVP());
12863 RenderRouteLegs(dc);
12864 RenderShipToActive(dc, false);
12865 ScaleBarDraw(dc);
12866 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12867 if (g_pi_manager) {
12868 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12870 }
12871
12872 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12873 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12874
12875 if (g_pi_manager) {
12876 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12878 }
12879
12880 if (m_bShowTide) {
12881 RebuildTideSelectList(GetVP().GetBBox());
12882 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12883 }
12884
12885 if (m_bShowCurrent) {
12886 RebuildCurrentSelectList(GetVP().GetBBox());
12887 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12888 }
12889
12890 if (!g_PrintingInProgress) {
12891 if (IsPrimaryCanvas()) {
12892 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12893 }
12894
12895 if (IsPrimaryCanvas()) {
12896 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12897 }
12898
12899 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12900
12901 if (m_pTrackRolloverWin) {
12902 m_pTrackRolloverWin->Draw(dc);
12903 m_brepaint_piano = true;
12904 }
12905
12906 if (m_pRouteRolloverWin) {
12907 m_pRouteRolloverWin->Draw(dc);
12908 m_brepaint_piano = true;
12909 }
12910
12911 if (m_pAISRolloverWin) {
12912 m_pAISRolloverWin->Draw(dc);
12913 m_brepaint_piano = true;
12914 }
12915 if (m_brepaint_piano && g_bShowChartBar) {
12916 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12917 }
12918
12919 if (m_Compass) m_Compass->Paint(dc);
12920
12921 if (!g_CanvasHideNotificationIcon) {
12922 if (IsPrimaryCanvas()) {
12923 auto &noteman = NotificationManager::GetInstance();
12924 if (noteman.GetNotificationCount()) {
12925 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12926 if (m_notification_button->UpdateStatus()) Refresh();
12927 m_notification_button->Show(true);
12928 m_notification_button->Paint(dc);
12929 } else {
12930 m_notification_button->Show(false);
12931 }
12932 }
12933 }
12934 }
12935 if (g_pi_manager) {
12936 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12938 }
12939}
12940
12941emboss_data *ChartCanvas::EmbossDepthScale() {
12942 if (!m_bShowDepthUnits) return NULL;
12943
12944 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12945
12946 if (GetQuiltMode()) {
12947 wxString s = m_pQuilt->GetQuiltDepthUnit();
12948 s.MakeUpper();
12949 if (s == "FEET")
12950 depth_unit_type = DEPTH_UNIT_FEET;
12951 else if (s.StartsWith("FATHOMS"))
12952 depth_unit_type = DEPTH_UNIT_FATHOMS;
12953 else if (s.StartsWith("METERS"))
12954 depth_unit_type = DEPTH_UNIT_METERS;
12955 else if (s.StartsWith("METRES"))
12956 depth_unit_type = DEPTH_UNIT_METERS;
12957 else if (s.StartsWith("METRIC"))
12958 depth_unit_type = DEPTH_UNIT_METERS;
12959 else if (s.StartsWith("METER"))
12960 depth_unit_type = DEPTH_UNIT_METERS;
12961
12962 } else {
12963 if (m_singleChart) {
12964 depth_unit_type = m_singleChart->GetDepthUnitType();
12965 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12966 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12967 }
12968 }
12969
12970 emboss_data *ped = NULL;
12971 switch (depth_unit_type) {
12972 case DEPTH_UNIT_FEET:
12973 ped = m_pEM_Feet;
12974 break;
12975 case DEPTH_UNIT_METERS:
12976 ped = m_pEM_Meters;
12977 break;
12978 case DEPTH_UNIT_FATHOMS:
12979 ped = m_pEM_Fathoms;
12980 break;
12981 default:
12982 return NULL;
12983 }
12984
12985 ped->x = (GetVP().pix_width - ped->width);
12986
12987 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12988 wxRect r = m_Compass->GetRect();
12989 ped->y = r.y + r.height;
12990 } else {
12991 ped->y = 40;
12992 }
12993 return ped;
12994}
12995
12996void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12997 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12998 wxFont font;
12999 if (style->embossFont == wxEmptyString) {
13000 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13001 font = *dFont;
13002 font.SetPointSize(60);
13003 font.SetWeight(wxFONTWEIGHT_BOLD);
13004 } else
13005 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13006 wxFONTWEIGHT_BOLD, false, style->embossFont);
13007
13008 int emboss_width = 500;
13009 int emboss_height = 200;
13010
13011 // Free any existing emboss maps
13012 delete m_pEM_Feet;
13013 delete m_pEM_Meters;
13014 delete m_pEM_Fathoms;
13015
13016 // Create the 3 DepthUnit emboss map structures
13017 m_pEM_Feet =
13018 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13019 m_pEM_Meters =
13020 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13021 m_pEM_Fathoms =
13022 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13023}
13024
13025#define OVERZOOM_TEXT _("OverZoom")
13026
13027void ChartCanvas::SetOverzoomFont() {
13028 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13029 int w, h;
13030
13031 wxFont font;
13032 if (style->embossFont == wxEmptyString) {
13033 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13034 font = *dFont;
13035 font.SetPointSize(40);
13036 font.SetWeight(wxFONTWEIGHT_BOLD);
13037 } else
13038 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13039 wxFONTWEIGHT_BOLD, false, style->embossFont);
13040
13041 wxClientDC dc(this);
13042 dc.SetFont(font);
13043 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13044
13045 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13046 font.SetPointSize(font.GetPointSize() - 1);
13047 dc.SetFont(font);
13048 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13049 }
13050 m_overzoomFont = font;
13051 m_overzoomTextWidth = w;
13052 m_overzoomTextHeight = h;
13053}
13054
13055void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13056 delete m_pEM_OverZoom;
13057
13058 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13059 m_pEM_OverZoom =
13060 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13061 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13062}
13063
13064emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13065 int height, const wxString &str,
13066 ColorScheme cs) {
13067 int *pmap;
13068
13069 // Create a temporary bitmap
13070 wxBitmap bmp(width, height, -1);
13071
13072 // Create a memory DC
13073 wxMemoryDC temp_dc;
13074 temp_dc.SelectObject(bmp);
13075
13076 // Paint on it
13077 temp_dc.SetBackground(*wxWHITE_BRUSH);
13078 temp_dc.SetTextBackground(*wxWHITE);
13079 temp_dc.SetTextForeground(*wxBLACK);
13080
13081 temp_dc.Clear();
13082
13083 temp_dc.SetFont(font);
13084
13085 int str_w, str_h;
13086 temp_dc.GetTextExtent(str, &str_w, &str_h);
13087 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13088 temp_dc.DrawText(str, 1, 1);
13089
13090 // Deselect the bitmap
13091 temp_dc.SelectObject(wxNullBitmap);
13092
13093 // Convert bitmap the wxImage for manipulation
13094 wxImage img = bmp.ConvertToImage();
13095
13096 int image_width = str_w * 105 / 100;
13097 int image_height = str_h * 105 / 100;
13098 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13099 wxMin(image_height, img.GetHeight()));
13100 wxImage imgs = img.GetSubImage(r);
13101
13102 double val_factor;
13103 switch (cs) {
13104 case GLOBAL_COLOR_SCHEME_DAY:
13105 default:
13106 val_factor = 1;
13107 break;
13108 case GLOBAL_COLOR_SCHEME_DUSK:
13109 val_factor = .5;
13110 break;
13111 case GLOBAL_COLOR_SCHEME_NIGHT:
13112 val_factor = .25;
13113 break;
13114 }
13115
13116 int val;
13117 int index;
13118 const int w = imgs.GetWidth();
13119 const int h = imgs.GetHeight();
13120 pmap = (int *)calloc(w * h * sizeof(int), 1);
13121 // Create emboss map by differentiating the emboss image
13122 // and storing integer results in pmap
13123 // n.b. since the image is B/W, it is sufficient to check
13124 // one channel (i.e. red) only
13125 for (int y = 1; y < h - 1; y++) {
13126 for (int x = 1; x < w - 1; x++) {
13127 val =
13128 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13129 val = (int)(val * val_factor);
13130 index = (y * w) + x;
13131 pmap[index] = val;
13132 }
13133 }
13134
13135 emboss_data *pret = new emboss_data;
13136 pret->pmap = pmap;
13137 pret->width = w;
13138 pret->height = h;
13139
13140 return pret;
13141}
13142
13143void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13144 Track *active_track = NULL;
13145 for (Track *pTrackDraw : g_TrackList) {
13146 if (g_pActiveTrack == pTrackDraw) {
13147 active_track = pTrackDraw;
13148 continue;
13149 }
13150
13151 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13152 }
13153
13154 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13155}
13156
13157void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13158 Track *active_track = NULL;
13159 for (Track *pTrackDraw : g_TrackList) {
13160 if (g_pActiveTrack == pTrackDraw) {
13161 active_track = pTrackDraw;
13162 break;
13163 }
13164 }
13165 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13166}
13167
13168void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13169 Route *active_route = NULL;
13170 for (Route *pRouteDraw : *pRouteList) {
13171 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13172 active_route = pRouteDraw;
13173 continue;
13174 }
13175
13176 // if(m_canvasIndex == 1)
13177 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13178 }
13179
13180 // Draw any active or selected route (or track) last, so that is is always on
13181 // top
13182 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13183}
13184
13185void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13186 Route *active_route = NULL;
13187
13188 for (Route *pRouteDraw : *pRouteList) {
13189 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13190 active_route = pRouteDraw;
13191 break;
13192 }
13193 }
13194 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13195}
13196
13197void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13198 if (!pWayPointMan) return;
13199
13200 auto node = pWayPointMan->GetWaypointList()->begin();
13201
13202 while (node != pWayPointMan->GetWaypointList()->end()) {
13203 RoutePoint *pWP = *node;
13204 if (pWP) {
13205 if (pWP->m_bIsInRoute) {
13206 ++node;
13207 continue;
13208 }
13209
13210 /* technically incorrect... waypoint has bounding box */
13211 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13212 RoutePointGui(*pWP).Draw(dc, this, NULL);
13213 else {
13214 // Are Range Rings enabled?
13215 if (pWP->GetShowWaypointRangeRings() &&
13216 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13217 double factor = 1.00;
13218 if (pWP->GetWaypointRangeRingsStepUnits() ==
13219 1) // convert kilometers to NMi
13220 factor = 1 / 1.852;
13221
13222 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13223 pWP->GetWaypointRangeRingsStep() / 60.;
13224 radius *= 2; // Fudge factor
13225
13226 LLBBox radar_box;
13227 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13228 pWP->m_lat + radius, pWP->m_lon + radius);
13229 if (!BltBBox.IntersectOut(radar_box)) {
13230 RoutePointGui(*pWP).Draw(dc, this, NULL);
13231 }
13232 }
13233 }
13234 }
13235
13236 ++node;
13237 }
13238}
13239
13240void ChartCanvas::DrawBlinkObjects() {
13241 // All RoutePoints
13242 wxRect update_rect;
13243
13244 if (!pWayPointMan) return;
13245
13246 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13247 if (pWP) {
13248 if (pWP->m_bBlink) {
13249 update_rect.Union(pWP->CurrentRect_in_DC);
13250 }
13251 }
13252 }
13253 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13254}
13255
13256void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13257 // draw anchor watch rings, if activated
13258
13260 wxPoint r1, r2;
13261 wxPoint lAnchorPoint1, lAnchorPoint2;
13262 double lpp1 = 0.0;
13263 double lpp2 = 0.0;
13264 if (pAnchorWatchPoint1) {
13265 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13267 &lAnchorPoint1);
13268 }
13269 if (pAnchorWatchPoint2) {
13270 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13272 &lAnchorPoint2);
13273 }
13274
13275 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13276 wxPen ppPenr(GetGlobalColor("URED"), 2);
13277
13278 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13279 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13280 dc.SetBrush(*ppBrush);
13281
13282 if (lpp1 > 0) {
13283 dc.SetPen(ppPeng);
13284 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13285 }
13286
13287 if (lpp2 > 0) {
13288 dc.SetPen(ppPeng);
13289 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13290 }
13291
13292 if (lpp1 < 0) {
13293 dc.SetPen(ppPenr);
13294 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13295 }
13296
13297 if (lpp2 < 0) {
13298 dc.SetPen(ppPenr);
13299 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13300 }
13301 }
13302}
13303
13304double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13305 double lpp = 0.;
13306 wxPoint r1;
13307 wxPoint lAnchorPoint;
13308 double d1 = 0.0;
13309 double dabs;
13310 double tlat1, tlon1;
13311
13312 if (pAnchorWatchPoint) {
13313 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13314 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13315 dabs = fabs(d1 / 1852.);
13316 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13317 &tlat1, &tlon1);
13318 GetCanvasPointPix(tlat1, tlon1, &r1);
13319 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13320 &lAnchorPoint);
13321 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13322 pow((double)(lAnchorPoint.y - r1.y), 2));
13323
13324 // This is an entry watch
13325 if (d1 < 0) lpp = -lpp;
13326 }
13327 return lpp;
13328}
13329
13330//------------------------------------------------------------------------------------------
13331// Tides Support
13332//------------------------------------------------------------------------------------------
13333void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13334 if (!ptcmgr) return;
13335
13336 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13337
13338 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13339 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13340 double lon = pIDX->IDX_lon;
13341 double lat = pIDX->IDX_lat;
13342
13343 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13344 if ((type == 't') || (type == 'T')) {
13345 if (BBox.Contains(lat, lon)) {
13346 // Manage the point selection list
13347 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13348 }
13349 }
13350 }
13351}
13352
13353void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13354 if (!ptcmgr) return;
13355
13356 wxDateTime this_now = gTimeSource;
13357 bool cur_time = !gTimeSource.IsValid();
13358 if (cur_time) this_now = wxDateTime::Now();
13359 time_t t_this_now = this_now.GetTicks();
13360
13361 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13362 wxPENSTYLE_SOLID);
13363 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13364 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13365 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13366 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13367
13368 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13369 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13370 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13371 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13372 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13373 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13374
13375 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13376 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13377 int font_size = wxMax(10, dFont->GetPointSize());
13378 font_size /= g_Platform->GetDisplayDIPMult(this);
13379 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13380 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13381 false, dFont->GetFaceName());
13382
13383 dc.SetPen(*pblack_pen);
13384 dc.SetBrush(*pgreen_brush);
13385
13386 wxBitmap bm;
13387 switch (m_cs) {
13388 case GLOBAL_COLOR_SCHEME_DAY:
13389 bm = m_bmTideDay;
13390 break;
13391 case GLOBAL_COLOR_SCHEME_DUSK:
13392 bm = m_bmTideDusk;
13393 break;
13394 case GLOBAL_COLOR_SCHEME_NIGHT:
13395 bm = m_bmTideNight;
13396 break;
13397 default:
13398 bm = m_bmTideDay;
13399 break;
13400 }
13401
13402 int bmw = bm.GetWidth();
13403 int bmh = bm.GetHeight();
13404
13405 float scale_factor = 1.0;
13406
13407 // Set the onscreen size of the symbol
13408 // Compensate for various display resolutions
13409 float icon_pixelRefDim = 45;
13410
13411 // Tidal report graphic is scaled by the text size of the label in use
13412 wxScreenDC sdc;
13413 int height;
13414 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13415 height *= g_Platform->GetDisplayDIPMult(this);
13416 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13417
13418 scale_factor *= pix_factor;
13419
13420 float user_scale_factor = g_ChartScaleFactorExp;
13421 if (g_ChartScaleFactorExp > 1.0)
13422 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13423 1.2; // soften the scale factor a bit
13424
13425 scale_factor *= user_scale_factor;
13426 scale_factor *= GetContentScaleFactor();
13427
13428 {
13429 double marge = 0.05;
13430 std::vector<LLBBox> drawn_boxes;
13431 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13432 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13433
13434 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13435 if ((type == 't') || (type == 'T')) // only Tides
13436 {
13437 double lon = pIDX->IDX_lon;
13438 double lat = pIDX->IDX_lat;
13439
13440 if (BBox.ContainsMarge(lat, lon, marge)) {
13441 // Avoid drawing detailed graphic for duplicate tide stations
13442 if (GetVP().chart_scale < 500000) {
13443 bool bdrawn = false;
13444 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13445 if (drawn_boxes[i].Contains(lat, lon)) {
13446 bdrawn = true;
13447 break;
13448 }
13449 }
13450 if (bdrawn) continue; // the station loop
13451
13452 LLBBox this_box;
13453 this_box.Set(lat, lon, lat, lon);
13454 this_box.EnLarge(.005);
13455 drawn_boxes.push_back(this_box);
13456 }
13457
13458 wxPoint r;
13459 GetCanvasPointPix(lat, lon, &r);
13460 // draw standard icons
13461 if (GetVP().chart_scale > 500000) {
13462 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13463 }
13464 // draw "extended" icons
13465 else {
13466 dc.SetFont(*plabelFont);
13467 {
13468 {
13469 float val, nowlev;
13470 float ltleve = 0.;
13471 float htleve = 0.;
13472 time_t tctime;
13473 time_t lttime = 0;
13474 time_t httime = 0;
13475 bool wt;
13476 // define if flood or ebb in the last ten minutes and verify if
13477 // data are useable
13478 if (ptcmgr->GetTideFlowSens(
13479 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13480 pIDX->IDX_rec_num, nowlev, val, wt)) {
13481 // search forward the first HW or LW near "now" ( starting at
13482 // "now" - ten minutes )
13483 ptcmgr->GetHightOrLowTide(
13484 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13485 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13486 wt, pIDX->IDX_rec_num, val, tctime);
13487 if (wt) {
13488 httime = tctime;
13489 htleve = val;
13490 } else {
13491 lttime = tctime;
13492 ltleve = val;
13493 }
13494 wt = !wt;
13495
13496 // then search opposite tide near "now"
13497 if (tctime > t_this_now) // search backward
13498 ptcmgr->GetHightOrLowTide(
13499 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13500 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13501 pIDX->IDX_rec_num, val, tctime);
13502 else
13503 // or search forward
13504 ptcmgr->GetHightOrLowTide(
13505 t_this_now, FORWARD_TEN_MINUTES_STEP,
13506 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13507 val, tctime);
13508 if (wt) {
13509 httime = tctime;
13510 htleve = val;
13511 } else {
13512 lttime = tctime;
13513 ltleve = val;
13514 }
13515
13516 // draw the tide rectangle:
13517
13518 // tide icon rectangle has default pre-scaled width = 12 ,
13519 // height = 45
13520 int width = (int)(12 * scale_factor + 0.5);
13521 int height = (int)(45 * scale_factor + 0.5);
13522 int linew = wxMax(1, (int)(scale_factor));
13523 int xDraw = r.x - (width / 2);
13524 int yDraw = r.y - (height / 2);
13525
13526 // process tide state ( %height and flow sens )
13527 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13528 int hs = (httime > lttime) ? -4 : 4;
13529 hs *= (int)(scale_factor + 0.5);
13530 if (ts > 0.995 || ts < 0.005) hs = 0;
13531 int ht_y = (int)(height * ts);
13532
13533 // draw yellow tide rectangle outlined in black
13534 pblack_pen->SetWidth(linew);
13535 dc.SetPen(*pblack_pen);
13536 dc.SetBrush(*pyelo_brush);
13537 dc.DrawRectangle(xDraw, yDraw, width, height);
13538
13539 // draw blue rectangle as water height, smaller in width than
13540 // yellow rectangle
13541 dc.SetPen(*pblue_pen);
13542 dc.SetBrush(*pblue_brush);
13543 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13544 (width - (4 * linew)), height - ht_y);
13545
13546 // draw sens arrows (ensure they are not "under-drawn" by top
13547 // line of blue rectangle )
13548 int hl;
13549 wxPoint arrow[3];
13550 arrow[0].x = xDraw + 2 * linew;
13551 arrow[1].x = xDraw + width / 2;
13552 arrow[2].x = xDraw + width - 2 * linew;
13553 pyelo_pen->SetWidth(linew);
13554 pblue_pen->SetWidth(linew);
13555 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13556 {
13557 hl = (int)(height * 0.25) + yDraw;
13558 arrow[0].y = hl;
13559 arrow[1].y = hl + hs;
13560 arrow[2].y = hl;
13561 if (ts < 0.15)
13562 dc.SetPen(*pyelo_pen);
13563 else
13564 dc.SetPen(*pblue_pen);
13565 dc.DrawLines(3, arrow);
13566 }
13567 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13568 {
13569 hl = (int)(height * 0.5) + yDraw;
13570 arrow[0].y = hl;
13571 arrow[1].y = hl + hs;
13572 arrow[2].y = hl;
13573 if (ts < 0.40)
13574 dc.SetPen(*pyelo_pen);
13575 else
13576 dc.SetPen(*pblue_pen);
13577 dc.DrawLines(3, arrow);
13578 }
13579 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13580 {
13581 hl = (int)(height * 0.75) + yDraw;
13582 arrow[0].y = hl;
13583 arrow[1].y = hl + hs;
13584 arrow[2].y = hl;
13585 if (ts < 0.65)
13586 dc.SetPen(*pyelo_pen);
13587 else
13588 dc.SetPen(*pblue_pen);
13589 dc.DrawLines(3, arrow);
13590 }
13591 // draw tide level text
13592 wxString s;
13593 s.Printf("%3.1f", nowlev);
13594 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13595 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13596 int wx1;
13597 dc.GetTextExtent(s, &wx1, NULL);
13598 wx1 *= g_Platform->GetDisplayDIPMult(this);
13599 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13600 }
13601 }
13602 }
13603 }
13604 }
13605 }
13606 }
13607 }
13608}
13609
13610//------------------------------------------------------------------------------------------
13611// Currents Support
13612//------------------------------------------------------------------------------------------
13613
13614void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13615 if (!ptcmgr) return;
13616
13617 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13618
13619 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13620 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13621 double lon = pIDX->IDX_lon;
13622 double lat = pIDX->IDX_lat;
13623
13624 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13625 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13626 if ((BBox.Contains(lat, lon))) {
13627 // Manage the point selection list
13628 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13629 }
13630 }
13631 }
13632}
13633
13634void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13635 if (!ptcmgr) return;
13636
13637 float tcvalue, dir;
13638 bool bnew_val;
13639 char sbuf[20];
13640 wxFont *pTCFont;
13641 double lon_last = 0.;
13642 double lat_last = 0.;
13643 // arrow size for Raz Blanchard : 12 knots north
13644 double marge = 0.2;
13645 bool cur_time = !gTimeSource.IsValid();
13646
13647 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13648 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13649
13650 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13651 wxPENSTYLE_SOLID);
13652 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13653 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13654 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13655 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13656 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13657 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13658 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13659 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13660
13661 double skew_angle = GetVPRotation();
13662
13663 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13664 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13665 int font_size = wxMax(10, dFont->GetPointSize());
13666 font_size /= g_Platform->GetDisplayDIPMult(this);
13667 pTCFont = FontMgr::Get().FindOrCreateFont(
13668 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13669 false, dFont->GetFaceName());
13670
13671 float scale_factor = 1.0;
13672
13673 // Set the onscreen size of the symbol
13674 // Current report graphic is scaled by the text size of the label in use
13675 wxScreenDC sdc;
13676 int height;
13677 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13678 height *= g_Platform->GetDisplayDIPMult(this);
13679 float nominal_icon_size_pixels = 15;
13680 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13681
13682 scale_factor *= pix_factor;
13683
13684 float user_scale_factor = g_ChartScaleFactorExp;
13685 if (g_ChartScaleFactorExp > 1.0)
13686 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13687 1.2; // soften the scale factor a bit
13688
13689 scale_factor *= user_scale_factor;
13690
13691 scale_factor *= GetContentScaleFactor();
13692
13693 {
13694 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13695 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13696 double lon = pIDX->IDX_lon;
13697 double lat = pIDX->IDX_lat;
13698
13699 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13700 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13701 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13702 wxPoint r;
13703 GetCanvasPointPix(lat, lon, &r);
13704
13705 wxPoint d[4]; // points of a diamond at the current station location
13706 int dd = (int)(5.0 * scale_factor + 0.5);
13707 d[0].x = r.x;
13708 d[0].y = r.y + dd;
13709 d[1].x = r.x + dd;
13710 d[1].y = r.y;
13711 d[2].x = r.x;
13712 d[2].y = r.y - dd;
13713 d[3].x = r.x - dd;
13714 d[3].y = r.y;
13715
13716 if (1) {
13717 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13718 dc.SetPen(*pblack_pen);
13719 dc.SetBrush(*porange_brush);
13720 dc.DrawPolygon(4, d);
13721
13722 if (type == 'C') {
13723 dc.SetBrush(*pblack_brush);
13724 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13725 }
13726
13727 if (GetVP().chart_scale < 1000000) {
13728 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13729 continue;
13730 } else
13731 continue;
13732
13733 if (1 /*type == 'c'*/) {
13734 {
13735 // Get the display pixel location of the current station
13736 int pixxc, pixyc;
13737 pixxc = r.x;
13738 pixyc = r.y;
13739
13740 // Adjust drawing size using logarithmic scale. tcvalue is
13741 // current in knots
13742 double a1 = fabs(tcvalue) * 10.;
13743 // Current values <= 0.1 knot will have no arrow
13744 a1 = wxMax(1.0, a1);
13745 double a2 = log10(a1);
13746
13747 float cscale = scale_factor * a2 * 0.3;
13748
13749 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13750 dc.SetPen(*porange_pen);
13751 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13752 cscale);
13753 // Draw text, if enabled
13754
13755 if (bDrawCurrentValues) {
13756 dc.SetFont(*pTCFont);
13757 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13758 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13759 }
13760 }
13761 } // scale
13762 }
13763 /* This is useful for debugging the TC database
13764 else
13765 {
13766 dc.SetPen ( *porange_pen );
13767 dc.SetBrush ( *pgray_brush );
13768 dc.DrawPolygon ( 4, d );
13769 }
13770 */
13771 }
13772 lon_last = lon;
13773 lat_last = lat;
13774 }
13775 }
13776 }
13777}
13778
13779void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13780 ShowSingleTideDialog(x, y, pvIDX);
13781}
13782
13783void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13784 if (!pvIDX) return; // Validate input
13785
13786 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13787
13788 // Check if a tide dialog is already open and visible
13789 if (pCwin && pCwin->IsShown()) {
13790 // Same tide station: bring existing dialog to front (preserves user
13791 // context)
13792 if (pCwin->GetCurrentIDX() == pNewIDX) {
13793 pCwin->Raise();
13794 pCwin->SetFocus();
13795
13796 // Provide subtle visual feedback that dialog is already open
13797 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13798 return;
13799 }
13800
13801 // Different tide station: close current dialog before opening new one
13802 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13803 }
13804
13805 if (pCwin) {
13806 // This shouldn't happen but ensures clean state
13807 pCwin->Destroy();
13808 pCwin = NULL;
13809 }
13810
13811 // Create and display new tide dialog
13812 pCwin = new TCWin(this, x, y, pvIDX);
13813
13814 // Ensure the dialog is properly shown and focused
13815 if (pCwin) {
13816 pCwin->Show();
13817 pCwin->Raise();
13818 pCwin->SetFocus();
13819 }
13820}
13821
13822bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13823
13825 if (pCwin) {
13826 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13827 }
13828}
13829
13830#define NUM_CURRENT_ARROW_POINTS 9
13831static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13832 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13833 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13834 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13835
13836void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13837 double scale) {
13838 if (scale > 1e-2) {
13839 float sin_rot = sin(rot_angle * PI / 180.);
13840 float cos_rot = cos(rot_angle * PI / 180.);
13841
13842 // Move to the first point
13843
13844 float xt = CurrentArrowArray[0].x;
13845 float yt = CurrentArrowArray[0].y;
13846
13847 float xp = (xt * cos_rot) - (yt * sin_rot);
13848 float yp = (xt * sin_rot) + (yt * cos_rot);
13849 int x1 = (int)(xp * scale);
13850 int y1 = (int)(yp * scale);
13851
13852 // Walk thru the point list
13853 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13854 xt = CurrentArrowArray[ip].x;
13855 yt = CurrentArrowArray[ip].y;
13856
13857 float xp = (xt * cos_rot) - (yt * sin_rot);
13858 float yp = (xt * sin_rot) + (yt * cos_rot);
13859 int x2 = (int)(xp * scale);
13860 int y2 = (int)(yp * scale);
13861
13862 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13863
13864 x1 = x2;
13865 y1 = y2;
13866 }
13867 }
13868}
13869
13870wxString ChartCanvas::FindValidUploadPort() {
13871 wxString port;
13872 // Try to use the saved persistent upload port first
13873 if (!g_uploadConnection.IsEmpty() &&
13874 g_uploadConnection.StartsWith("Serial")) {
13875 port = g_uploadConnection;
13876 }
13877
13878 else {
13879 // If there is no persistent upload port recorded (yet)
13880 // then use the first available serial connection which has output defined.
13881 for (auto *cp : TheConnectionParams()) {
13882 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13883 port << "Serial:" << cp->Port;
13884 }
13885 }
13886 return port;
13887}
13888
13889void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13890 if (!win) return;
13891
13892 if (NULL == g_pais_query_dialog_active) {
13893 int pos_x = g_ais_query_dialog_x;
13894 int pos_y = g_ais_query_dialog_y;
13895
13896 if (g_pais_query_dialog_active) {
13897 g_pais_query_dialog_active->Destroy();
13898 g_pais_query_dialog_active = new AISTargetQueryDialog();
13899 } else {
13900 g_pais_query_dialog_active = new AISTargetQueryDialog();
13901 }
13902
13903 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13904 wxPoint(pos_x, pos_y));
13905
13906 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13907 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13908 g_pais_query_dialog_active->SetMMSI(mmsi);
13909 g_pais_query_dialog_active->UpdateText();
13910 wxSize sz = g_pais_query_dialog_active->GetSize();
13911
13912 bool b_reset_pos = false;
13913#ifdef __WXMSW__
13914 // Support MultiMonitor setups which an allow negative window positions.
13915 // If the requested window title bar does not intersect any installed
13916 // monitor, then default to simple primary monitor positioning.
13917 RECT frame_title_rect;
13918 frame_title_rect.left = pos_x;
13919 frame_title_rect.top = pos_y;
13920 frame_title_rect.right = pos_x + sz.x;
13921 frame_title_rect.bottom = pos_y + 30;
13922
13923 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13924 b_reset_pos = true;
13925#else
13926
13927 // Make sure drag bar (title bar) of window intersects wxClient Area of
13928 // screen, with a little slop...
13929 wxRect window_title_rect; // conservative estimate
13930 window_title_rect.x = pos_x;
13931 window_title_rect.y = pos_y;
13932 window_title_rect.width = sz.x;
13933 window_title_rect.height = 30;
13934
13935 wxRect ClientRect = wxGetClientDisplayRect();
13936 ClientRect.Deflate(
13937 60, 60); // Prevent the new window from being too close to the edge
13938 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13939
13940#endif
13941
13942 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13943
13944 } else {
13945 g_pais_query_dialog_active->SetMMSI(mmsi);
13946 g_pais_query_dialog_active->UpdateText();
13947 }
13948
13949 g_pais_query_dialog_active->Show();
13950}
13951
13952void ChartCanvas::ToggleCanvasQuiltMode() {
13953 bool cur_mode = GetQuiltMode();
13954
13955 if (!GetQuiltMode())
13956 SetQuiltMode(true);
13957 else if (GetQuiltMode()) {
13958 SetQuiltMode(false);
13959 g_sticky_chart = GetQuiltReferenceChartIndex();
13960 }
13961
13962 if (cur_mode != GetQuiltMode()) {
13963 SetupCanvasQuiltMode();
13964 DoCanvasUpdate();
13965 InvalidateGL();
13966 Refresh();
13967 }
13968 // TODO What to do about this?
13969 // g_bQuiltEnable = GetQuiltMode();
13970
13971 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13972 if (ps52plib) ps52plib->GenerateStateHash();
13973
13974 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13975 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13976}
13977
13978void ChartCanvas::DoCanvasStackDelta(int direction) {
13979 if (!GetQuiltMode()) {
13980 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13981 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13982 if ((current_stack_index + direction) < 0) return;
13983
13984 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13985 int new_dbIndex =
13986 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13987
13988 if (IsChartQuiltableRef(new_dbIndex)) {
13989 ToggleCanvasQuiltMode();
13990 SelectQuiltRefdbChart(new_dbIndex);
13991 m_bpersistent_quilt = false;
13992 }
13993 } else {
13994 SelectChartFromStack(current_stack_index + direction);
13995 }
13996 } else {
13997 std::vector<int> piano_chart_index_array =
13998 GetQuiltExtendedStackdbIndexArray();
13999 int refdb = GetQuiltRefChartdbIndex();
14000
14001 // Find the ref chart in the stack
14002 int current_index = -1;
14003 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14004 if (refdb == piano_chart_index_array[i]) {
14005 current_index = i;
14006 break;
14007 }
14008 }
14009 if (current_index == -1) return;
14010
14011 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14012 int target_family = ctet.GetChartFamily();
14013
14014 int new_index = -1;
14015 int check_index = current_index + direction;
14016 bool found = false;
14017 int check_dbIndex = -1;
14018 int new_dbIndex = -1;
14019
14020 // When quilted. switch within the same chart family
14021 while (!found &&
14022 (unsigned int)check_index < piano_chart_index_array.size() &&
14023 (check_index >= 0)) {
14024 check_dbIndex = piano_chart_index_array[check_index];
14025 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14026 if (target_family == cte.GetChartFamily()) {
14027 found = true;
14028 new_index = check_index;
14029 new_dbIndex = check_dbIndex;
14030 break;
14031 }
14032
14033 check_index += direction;
14034 }
14035
14036 if (!found) return;
14037
14038 if (!IsChartQuiltableRef(new_dbIndex)) {
14039 ToggleCanvasQuiltMode();
14040 SelectdbChart(new_dbIndex);
14041 m_bpersistent_quilt = true;
14042 } else {
14043 SelectQuiltRefChart(new_index);
14044 }
14045 }
14046
14047 // update the state of the menu items (checkmarks etc)
14048 top_frame::Get()->UpdateGlobalMenuItems();
14049 SetQuiltChartHiLiteIndex(-1);
14050
14051 ReloadVP();
14052}
14053
14054//--------------------------------------------------------------------------------------------------------
14055//
14056// Toolbar support
14057//
14058//--------------------------------------------------------------------------------------------------------
14059
14060void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14061 // Handle the per-canvas toolbar clicks here
14062
14063 switch (event.GetId()) {
14064 case ID_ZOOMIN: {
14065 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14066 break;
14067 }
14068
14069 case ID_ZOOMOUT: {
14070 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14071 break;
14072 }
14073
14074 case ID_STKUP:
14075 DoCanvasStackDelta(1);
14076 DoCanvasUpdate();
14077 break;
14078
14079 case ID_STKDN:
14080 DoCanvasStackDelta(-1);
14081 DoCanvasUpdate();
14082 break;
14083
14084 case ID_FOLLOW: {
14085 TogglebFollow();
14086 break;
14087 }
14088
14089 case ID_CURRENT: {
14090 ShowCurrents(!GetbShowCurrent());
14091 ReloadVP();
14092 Refresh(false);
14093 break;
14094 }
14095
14096 case ID_TIDE: {
14097 ShowTides(!GetbShowTide());
14098 ReloadVP();
14099 Refresh(false);
14100 break;
14101 }
14102
14103 case ID_ROUTE: {
14104 if (0 == m_routeState) {
14105 StartRoute();
14106 } else {
14107 FinishRoute();
14108 }
14109
14110#ifdef __ANDROID__
14111 androidSetRouteAnnunciator(m_routeState == 1);
14112#endif
14113 break;
14114 }
14115
14116 case ID_AIS: {
14117 SetAISCanvasDisplayStyle(-1);
14118 break;
14119 }
14120
14121 default:
14122 break;
14123 }
14124
14125 // And then let gFrame handle the rest....
14126 event.Skip();
14127}
14128
14129void ChartCanvas::SetShowAIS(bool show) {
14130 m_bShowAIS = show;
14131 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14132 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14133}
14134
14135void ChartCanvas::SetAttenAIS(bool show) {
14136 m_bShowAISScaled = show;
14137 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14138 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14139}
14140
14141void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14142 // make some arrays to hold the dfferences between cycle steps
14143 // show all, scaled, hide all
14144 bool bShowAIS_Array[3] = {true, true, false};
14145 bool bShowScaled_Array[3] = {false, true, true};
14146 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14147 _("Attenuate less critical AIS targets"),
14148 _("Hide AIS Targets")};
14149 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14150 int ArraySize = 3;
14151 int AIS_Toolbar_Switch = 0;
14152 if (StyleIndx == -1) { // -1 means coming from toolbar button
14153 // find current state of switch
14154 for (int i = 1; i < ArraySize; i++) {
14155 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14156 (bShowScaled_Array[i] == m_bShowAISScaled))
14157 AIS_Toolbar_Switch = i;
14158 }
14159 AIS_Toolbar_Switch++; // we did click so continu with next item
14160 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14161 AIS_Toolbar_Switch++;
14162
14163 } else { // coming from menu bar.
14164 AIS_Toolbar_Switch = StyleIndx;
14165 }
14166 // make sure we are not above array
14167 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14168
14169 int AIS_Toolbar_Switch_Next =
14170 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14171 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14172 AIS_Toolbar_Switch_Next++;
14173 if (AIS_Toolbar_Switch_Next >= ArraySize)
14174 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14175
14176 // Set found values to global and member variables
14177 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14178 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14179}
14180
14181void ChartCanvas::TouchAISToolActive() {}
14182
14183void ChartCanvas::UpdateAISTBTool() {}
14184
14185//---------------------------------------------------------------------------------
14186//
14187// Compass/GPS status icon support
14188//
14189//---------------------------------------------------------------------------------
14190
14191void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14192 // Look for change in overlap or positions
14193 bool b_update = false;
14194 int cc1_edge_comp = 2;
14195 wxRect rect = m_Compass->GetRect();
14196 wxSize parent_size = GetSize();
14197
14198 parent_size *= m_displayScale;
14199
14200 // check to see if it would overlap if it was in its home position (upper
14201 // right)
14202 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14203 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14204 wxRect compass_rect(compass_pt, rect.GetSize());
14205
14206 m_Compass->Move(compass_pt);
14207
14208 if (m_Compass && m_Compass->IsShown())
14209 m_Compass->UpdateStatus(b_force_new | b_update);
14210
14211 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14212 scaler = wxMax(scaler, 1.0);
14213 wxPoint note_point = wxPoint(
14214 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14215 if (m_notification_button) {
14216 m_notification_button->Move(note_point);
14217 m_notification_button->UpdateStatus();
14218 }
14219
14220 if (b_force_new | b_update) Refresh();
14221}
14222
14223void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14224 ChartTypeEnum New_Type,
14225 ChartFamilyEnum New_Family) {
14226 if (!GetpCurrentStack()) return;
14227 if (!ChartData) return;
14228
14229 if (index < GetpCurrentStack()->nEntry) {
14230 // Open the new chart
14231 ChartBase *pTentative_Chart;
14232 pTentative_Chart = ChartData->OpenStackChartConditional(
14233 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14234
14235 if (pTentative_Chart) {
14236 if (m_singleChart) m_singleChart->Deactivate();
14237
14238 m_singleChart = pTentative_Chart;
14239 m_singleChart->Activate();
14240
14241 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14242 GetpCurrentStack(), m_singleChart->GetFullPath());
14243 }
14244
14245 // Setup the view
14246 double zLat, zLon;
14247 if (m_bFollow) {
14248 zLat = gLat;
14249 zLon = gLon;
14250 } else {
14251 zLat = m_vLat;
14252 zLon = m_vLon;
14253 }
14254
14255 double best_scale_ppm = GetBestVPScale(m_singleChart);
14256 double rotation = GetVPRotation();
14257 double oldskew = GetVPSkew();
14258 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14259
14260 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14261 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14262 if (fabs(newskew) > 0.0001) rotation = newskew;
14263 }
14264
14265 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14266
14267 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14268 }
14269
14270 // refresh Piano
14271 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14272 if (idx < 0) return;
14273
14274 std::vector<int> piano_active_chart_index_array;
14275 piano_active_chart_index_array.push_back(
14276 GetpCurrentStack()->GetCurrentEntrydbIndex());
14277 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14278}
14279
14280void ChartCanvas::SelectdbChart(int dbindex) {
14281 if (!GetpCurrentStack()) return;
14282 if (!ChartData) return;
14283
14284 if (dbindex >= 0) {
14285 // Open the new chart
14286 ChartBase *pTentative_Chart;
14287 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14288
14289 if (pTentative_Chart) {
14290 if (m_singleChart) m_singleChart->Deactivate();
14291
14292 m_singleChart = pTentative_Chart;
14293 m_singleChart->Activate();
14294
14295 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14296 GetpCurrentStack(), m_singleChart->GetFullPath());
14297 }
14298
14299 // Setup the view
14300 double zLat, zLon;
14301 if (m_bFollow) {
14302 zLat = gLat;
14303 zLon = gLon;
14304 } else {
14305 zLat = m_vLat;
14306 zLon = m_vLon;
14307 }
14308
14309 double best_scale_ppm = GetBestVPScale(m_singleChart);
14310
14311 if (m_singleChart)
14312 SetViewPoint(zLat, zLon, best_scale_ppm,
14313 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14314
14315 // SetChartUpdatePeriod( );
14316
14317 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14318 }
14319
14320 // TODO refresh_Piano();
14321}
14322
14323void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14324 double target_scale = GetVP().view_scale_ppm;
14325
14326 if (!GetQuiltMode()) {
14327 if (GetpCurrentStack()) {
14328 int stack_index = -1;
14329 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14330 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14331 if (check_dbIndex < 0) continue;
14332 const ChartTableEntry &cte =
14333 ChartData->GetChartTableEntry(check_dbIndex);
14334 if (type == cte.GetChartType()) {
14335 stack_index = i;
14336 break;
14337 } else if (family == cte.GetChartFamily()) {
14338 stack_index = i;
14339 break;
14340 }
14341 }
14342
14343 if (stack_index >= 0) {
14344 SelectChartFromStack(stack_index);
14345 }
14346 }
14347 } else {
14348 int sel_dbIndex = -1;
14349 std::vector<int> piano_chart_index_array =
14350 GetQuiltExtendedStackdbIndexArray();
14351 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14352 int check_dbIndex = piano_chart_index_array[i];
14353 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14354 if (type == cte.GetChartType()) {
14355 if (IsChartQuiltableRef(check_dbIndex)) {
14356 sel_dbIndex = check_dbIndex;
14357 break;
14358 }
14359 } else if (family == cte.GetChartFamily()) {
14360 if (IsChartQuiltableRef(check_dbIndex)) {
14361 sel_dbIndex = check_dbIndex;
14362 break;
14363 }
14364 }
14365 }
14366
14367 if (sel_dbIndex >= 0) {
14368 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14369 // Re-qualify the quilt reference chart selection
14370 AdjustQuiltRefChart();
14371 }
14372
14373 // Now reset the scale to the target...
14374 SetVPScale(target_scale);
14375 }
14376
14377 SetQuiltChartHiLiteIndex(-1);
14378
14379 ReloadVP();
14380}
14381
14382bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14383 return std::find(m_tile_yesshow_index_array.begin(),
14384 m_tile_yesshow_index_array.end(),
14385 index) != m_tile_yesshow_index_array.end();
14386}
14387
14388bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14389 return std::find(m_tile_noshow_index_array.begin(),
14390 m_tile_noshow_index_array.end(),
14391 index) != m_tile_noshow_index_array.end();
14392}
14393
14394void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14395 if (std::find(m_tile_noshow_index_array.begin(),
14396 m_tile_noshow_index_array.end(),
14397 index) == m_tile_noshow_index_array.end()) {
14398 m_tile_noshow_index_array.push_back(index);
14399 }
14400}
14401
14402//-------------------------------------------------------------------------------------------------------
14403//
14404// Piano support
14405//
14406//-------------------------------------------------------------------------------------------------------
14407
14408void ChartCanvas::HandlePianoClick(
14409 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14410 if (g_options && g_options->IsShown())
14411 return; // Piano might be invalid due to chartset updates.
14412 if (!m_pCurrentStack) return;
14413 if (!ChartData) return;
14414
14415 // stop movement or on slow computer we may get something like :
14416 // zoom out with the wheel (timer is set)
14417 // quickly click and display a chart, which may zoom in
14418 // but the delayed timer fires first and it zooms out again!
14419 StopMovement();
14420
14421 // When switching by piano key click, we may appoint the new target chart to
14422 // be any chart in the composite array.
14423 // As an improvement to UX, find the chart that is "closest" to the current
14424 // vp,
14425 // and select that chart. This will cause a jump to the centroid of that
14426 // chart
14427
14428 double distance = 25000; // RTW
14429 int closest_index = -1;
14430 for (int chart_index : selected_dbIndex_array) {
14431 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14432 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14433 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14434
14435 // measure distance as Manhattan style
14436 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14437 if (test_distance < distance) {
14438 distance = test_distance;
14439 closest_index = chart_index;
14440 }
14441 }
14442
14443 int selected_dbIndex = selected_dbIndex_array[0];
14444 if (closest_index >= 0) selected_dbIndex = closest_index;
14445
14446 if (!GetQuiltMode()) {
14447 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14448 if (IsChartQuiltableRef(selected_dbIndex)) {
14449 ToggleCanvasQuiltMode();
14450 SelectQuiltRefdbChart(selected_dbIndex);
14451 m_bpersistent_quilt = false;
14452 } else {
14453 SelectChartFromStack(selected_index);
14454 }
14455 } else {
14456 SelectChartFromStack(selected_index);
14457 g_sticky_chart = selected_dbIndex;
14458 }
14459
14460 if (m_singleChart)
14461 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14462 } else {
14463 // Handle MBTiles overlays first
14464 // Left click simply toggles the noshow array index entry
14465 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14466 bool bfound = false;
14467 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14468 if (m_tile_noshow_index_array[i] ==
14469 selected_dbIndex) { // chart is in the noshow list
14470 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14471 i); // erase it
14472 bfound = true;
14473 break;
14474 }
14475 }
14476 if (!bfound) {
14477 m_tile_noshow_index_array.push_back(selected_dbIndex);
14478 }
14479
14480 // If not already present, add this tileset to the "yes_show" array.
14481 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14482 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14483 }
14484
14485 else {
14486 if (IsChartQuiltableRef(selected_dbIndex)) {
14487 // if( ChartData ) ChartData->PurgeCache();
14488
14489 // If the chart is a vector chart, and of very large scale,
14490 // then we had better set the new scale directly to avoid excessive
14491 // underzoom on, eg, Inland ENCs
14492 bool set_scale = false;
14493 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14494 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14495 set_scale = true;
14496 }
14497 }
14498
14499 if (!set_scale) {
14500 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14501 } else {
14502 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14503
14504 // Adjust scale so that the selected chart is underzoomed/overzoomed
14505 // by a controlled amount
14506 ChartBase *pc =
14507 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14508 if (pc) {
14509 double proposed_scale_onscreen =
14511
14512 if (g_bPreserveScaleOnX) {
14513 proposed_scale_onscreen =
14514 wxMin(proposed_scale_onscreen,
14515 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14516 GetCanvasWidth()));
14517 } else {
14518 proposed_scale_onscreen =
14519 wxMin(proposed_scale_onscreen,
14520 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14521 GetCanvasWidth()));
14522
14523 proposed_scale_onscreen =
14524 wxMax(proposed_scale_onscreen,
14525 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14527 }
14528
14529 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14530 }
14531 }
14532 } else {
14533 ToggleCanvasQuiltMode();
14534 SelectdbChart(selected_dbIndex);
14535 m_bpersistent_quilt = true;
14536 }
14537 }
14538 }
14539
14540 SetQuiltChartHiLiteIndex(-1);
14541 // update the state of the menu items (checkmarks etc)
14542 top_frame::Get()->UpdateGlobalMenuItems();
14543 HideChartInfoWindow();
14544 DoCanvasUpdate();
14545 ReloadVP(); // Pick up the new selections
14546}
14547
14548void ChartCanvas::HandlePianoRClick(
14549 int x, int y, int selected_index,
14550 const std::vector<int> &selected_dbIndex_array) {
14551 if (g_options && g_options->IsShown())
14552 return; // Piano might be invalid due to chartset updates.
14553 if (!GetpCurrentStack()) return;
14554
14555 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14556 UpdateCanvasControlBar();
14557
14558 SetQuiltChartHiLiteIndex(-1);
14559}
14560
14561void ChartCanvas::HandlePianoRollover(
14562 int selected_index, const std::vector<int> &selected_dbIndex_array,
14563 int n_charts, int scale) {
14564 if (g_options && g_options->IsShown())
14565 return; // Piano might be invalid due to chartset updates.
14566 if (!GetpCurrentStack()) return;
14567 if (!ChartData) return;
14568
14569 if (ChartData->IsBusy()) return;
14570
14571 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14572
14573 if (!GetQuiltMode()) {
14574 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14575 } else {
14576 // Select the correct vector
14577 std::vector<int> piano_chart_index_array;
14578 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14579 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14580 if ((GetpCurrentStack()->nEntry > 1) ||
14581 (piano_chart_index_array.size() >= 1)) {
14582 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14583
14584 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14585 ReloadVP(false); // no VP adjustment allowed
14586 } else if (GetpCurrentStack()->nEntry == 1) {
14587 const ChartTableEntry &cte =
14588 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14589 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14590 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14591 ReloadVP(false);
14592 } else if ((-1 == selected_index) &&
14593 (0 == selected_dbIndex_array.size())) {
14594 ShowChartInfoWindow(key_location.x, -1);
14595 }
14596 }
14597 } else {
14598 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14599
14600 if ((GetpCurrentStack()->nEntry > 1) ||
14601 (piano_chart_index_array.size() >= 1)) {
14602 if (n_charts > 1)
14603 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14604 selected_dbIndex_array);
14605 else if (n_charts == 1)
14606 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14607
14608 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14609 ReloadVP(false); // no VP adjustment allowed
14610 }
14611 }
14612 }
14613}
14614
14615void ChartCanvas::ClearPianoRollover() {
14616 ClearQuiltChartHiLiteIndexArray();
14617 ShowChartInfoWindow(0, -1);
14618 std::vector<int> vec;
14619 ShowCompositeInfoWindow(0, 0, 0, vec);
14620 ReloadVP(false);
14621}
14622
14623void ChartCanvas::UpdateCanvasControlBar() {
14624 if (m_pianoFrozen) return;
14625
14626 if (!GetpCurrentStack()) return;
14627 if (!ChartData) return;
14628 if (!g_bShowChartBar) return;
14629
14630 int sel_type = -1;
14631 int sel_family = -1;
14632
14633 std::vector<int> piano_chart_index_array;
14634 std::vector<int> empty_piano_chart_index_array;
14635
14636 wxString old_hash = m_Piano->GetStoredHash();
14637
14638 if (GetQuiltMode()) {
14639 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14640 GetQuiltFullScreendbIndexArray());
14641
14642 std::vector<int> piano_active_chart_index_array =
14643 GetQuiltCandidatedbIndexArray();
14644 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14645
14646 std::vector<int> piano_eclipsed_chart_index_array =
14647 GetQuiltEclipsedStackdbIndexArray();
14648 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14649
14650 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14651 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14652
14653 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14654 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14655 } else {
14656 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14657 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14658 // TODO refresh_Piano();
14659
14660 if (m_singleChart) {
14661 sel_type = m_singleChart->GetChartType();
14662 sel_family = m_singleChart->GetChartFamily();
14663 }
14664 }
14665
14666 // Set up the TMerc and Skew arrays
14667 std::vector<int> piano_skew_chart_index_array;
14668 std::vector<int> piano_tmerc_chart_index_array;
14669 std::vector<int> piano_poly_chart_index_array;
14670
14671 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14672 const ChartTableEntry &ctei =
14673 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14674 double skew_norm = ctei.GetChartSkew();
14675 if (skew_norm > 180.) skew_norm -= 360.;
14676
14677 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14678 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14679
14680 // Polyconic skewed charts should show as skewed
14681 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14682 if (fabs(skew_norm) > 1.)
14683 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14684 else
14685 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14686 } else if (fabs(skew_norm) > 1.)
14687 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14688 }
14689 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14690 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14691 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14692
14693 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14694 if (new_hash != old_hash) {
14695 m_Piano->FormatKeys();
14696 HideChartInfoWindow();
14697 m_Piano->ResetRollover();
14698 SetQuiltChartHiLiteIndex(-1);
14699 m_brepaint_piano = true;
14700 }
14701
14702 // Create a bitmask int that describes what Family/Type of charts are shown in
14703 // the bar, and notify the platform.
14704 int mask = 0;
14705 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14706 const ChartTableEntry &ctei =
14707 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14708 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14709 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14710 if (e == CHART_FAMILY_RASTER) mask |= 1;
14711 if (e == CHART_FAMILY_VECTOR) {
14712 if (t == CHART_TYPE_CM93COMP)
14713 mask |= 4;
14714 else
14715 mask |= 2;
14716 }
14717 }
14718
14719 wxString s_indicated;
14720 if (sel_type == CHART_TYPE_CM93COMP)
14721 s_indicated = "cm93";
14722 else {
14723 if (sel_family == CHART_FAMILY_RASTER)
14724 s_indicated = "raster";
14725 else if (sel_family == CHART_FAMILY_VECTOR)
14726 s_indicated = "vector";
14727 }
14728
14729 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14730}
14731
14732void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14733
14734void ChartCanvas::PianoPopupMenu(
14735 int x, int y, int selected_index,
14736 const std::vector<int> &selected_dbIndex_array) {
14737 if (!GetpCurrentStack()) return;
14738
14739 // No context menu if quilting is disabled
14740 if (!GetQuiltMode()) return;
14741
14742 m_piano_ctx_menu = new wxMenu();
14743
14744 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14745 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14746 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14747 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14748 } else {
14749 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14750 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14751 // wxEVT_COMMAND_MENU_SELECTED,
14752 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14753
14754 menu_selected_dbIndex = selected_dbIndex_array[0];
14755 menu_selected_index = selected_index;
14756
14757 // Search the no-show array
14758 bool b_is_in_noshow = false;
14759 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14760 if (m_quilt_noshow_index_array[i] ==
14761 menu_selected_dbIndex) // chart is in the noshow list
14762 {
14763 b_is_in_noshow = true;
14764 break;
14765 }
14766 }
14767
14768 if (b_is_in_noshow) {
14769 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14770 _("Show This Chart"));
14771 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14772 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14773 } else if (GetpCurrentStack()->nEntry > 1) {
14774 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14775 _("Hide This Chart"));
14776 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14777 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14778 }
14779 }
14780
14781 wxPoint pos = wxPoint(x, y - 30);
14782
14783 // Invoke the drop-down menu
14784 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14785 PopupMenu(m_piano_ctx_menu, pos);
14786
14787 delete m_piano_ctx_menu;
14788 m_piano_ctx_menu = NULL;
14789
14790 HideChartInfoWindow();
14791 m_Piano->ResetRollover();
14792
14793 SetQuiltChartHiLiteIndex(-1);
14794 ClearQuiltChartHiLiteIndexArray();
14795
14796 ReloadVP();
14797}
14798
14799void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14800 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14801 if (m_quilt_noshow_index_array[i] ==
14802 menu_selected_dbIndex) // chart is in the noshow list
14803 {
14804 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14805 break;
14806 }
14807 }
14808}
14809
14810void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14811 if (!GetpCurrentStack()) return;
14812 if (!ChartData) return;
14813
14814 RemoveChartFromQuilt(menu_selected_dbIndex);
14815
14816 // It could happen that the chart being disabled is the reference
14817 // chart....
14818 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14819 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14820
14821 int i = menu_selected_index + 1; // select next smaller scale chart
14822 bool b_success = false;
14823 while (i < GetpCurrentStack()->nEntry - 1) {
14824 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14825 if (type == ChartData->GetDBChartType(dbIndex)) {
14826 SelectQuiltRefChart(i);
14827 b_success = true;
14828 break;
14829 }
14830 i++;
14831 }
14832
14833 // If that did not work, try to select the next larger scale compatible
14834 // chart
14835 if (!b_success) {
14836 i = menu_selected_index - 1;
14837 while (i > 0) {
14838 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14839 if (type == ChartData->GetDBChartType(dbIndex)) {
14840 SelectQuiltRefChart(i);
14841 b_success = true;
14842 break;
14843 }
14844 i--;
14845 }
14846 }
14847 }
14848}
14849
14850void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14851 // Remove the item from the list (if it appears) to avoid multiple addition
14852 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14853 if (m_quilt_noshow_index_array[i] ==
14854 dbIndex) // chart is already in the noshow list
14855 {
14856 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14857 break;
14858 }
14859 }
14860
14861 m_quilt_noshow_index_array.push_back(dbIndex);
14862}
14863
14864bool ChartCanvas::UpdateS52State() {
14865 bool retval = false;
14866
14867 if (ps52plib) {
14868 ps52plib->SetShowS57Text(m_encShowText);
14869 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14870 ps52plib->m_bShowSoundg = m_encShowDepth;
14871 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14872 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14873
14874 // Lights
14875 if (!m_encShowLights) // On, going off
14876 ps52plib->AddObjNoshow("LIGHTS");
14877 else // Off, going on
14878 ps52plib->RemoveObjNoshow("LIGHTS");
14879 ps52plib->SetLightsOff(!m_encShowLights);
14880 ps52plib->m_bExtendLightSectors = true;
14881
14882 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14883 ps52plib->SetAnchorOn(m_encShowAnchor);
14884 ps52plib->SetQualityOfData(m_encShowDataQual);
14885 }
14886
14887 return retval;
14888}
14889
14890void ChartCanvas::SetShowENCDataQual(bool show) {
14891 m_encShowDataQual = show;
14892 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14893 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14894
14895 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14896}
14897
14898void ChartCanvas::SetShowENCText(bool show) {
14899 m_encShowText = show;
14900 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14901 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14902
14903 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14904}
14905
14906void ChartCanvas::SetENCDisplayCategory(int category) {
14907 m_encDisplayCategory = category;
14908 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14909}
14910
14911void ChartCanvas::SetShowENCDepth(bool show) {
14912 m_encShowDepth = show;
14913 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14914 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14915
14916 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14917}
14918
14919void ChartCanvas::SetShowENCLightDesc(bool show) {
14920 m_encShowLightDesc = show;
14921 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14922 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14923
14924 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14925}
14926
14927void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14928 m_encShowBuoyLabels = show;
14929 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14930}
14931
14932void ChartCanvas::SetShowENCLights(bool show) {
14933 m_encShowLights = show;
14934 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14935 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14936
14937 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14938}
14939
14940void ChartCanvas::SetShowENCAnchor(bool show) {
14941 m_encShowAnchor = show;
14942 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14943 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14944
14945 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14946}
14947
14948wxRect ChartCanvas::GetMUIBarRect() {
14949 wxRect rv;
14950 if (m_muiBar) {
14951 rv = m_muiBar->GetRect();
14952 }
14953
14954 return rv;
14955}
14956
14957void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14958 if (!GetAlertString().IsEmpty()) {
14959 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14960 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14961
14962 dc.SetFont(*pfont);
14963 dc.SetPen(*wxTRANSPARENT_PEN);
14964
14965 dc.SetBrush(wxColour(243, 229, 47));
14966 int w, h;
14967 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14968 h += 2;
14969 // int yp = vp.pix_height - 20 - h;
14970
14971 wxRect sbr = GetScaleBarRect();
14972 int xp = sbr.x + sbr.width + 10;
14973 int yp = (sbr.y + sbr.height) - h;
14974
14975 int wdraw = w + 10;
14976 dc.DrawRectangle(xp, yp, wdraw, h);
14977 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14978 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14979 }
14980}
14981
14982//--------------------------------------------------------------------------------------------------------
14983// Screen Brightness Control Support Routines
14984//
14985//--------------------------------------------------------------------------------------------------------
14986
14987#ifdef __UNIX__
14988#define BRIGHT_XCALIB
14989#define __OPCPN_USEICC__
14990#endif
14991
14992#ifdef __OPCPN_USEICC__
14993int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14994 double co_green, double co_blue);
14995
14996wxString temp_file_name;
14997#endif
14998
14999#if 0
15000class ocpnCurtain: public wxDialog
15001{
15002 DECLARE_CLASS( ocpnCurtain )
15003 DECLARE_EVENT_TABLE()
15004
15005public:
15006 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15007 ~ocpnCurtain( );
15008 bool ProcessEvent(wxEvent& event);
15009
15010};
15011
15012IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15013
15014BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15015END_EVENT_TABLE()
15016
15017ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15018{
15019 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15020}
15021
15022ocpnCurtain::~ocpnCurtain()
15023{
15024}
15025
15026bool ocpnCurtain::ProcessEvent(wxEvent& event)
15027{
15028 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15029 return GetParent()->GetEventHandler()->ProcessEvent(event);
15030}
15031#endif
15032
15033#ifdef _WIN32
15034#include <windows.h>
15035
15036HMODULE hGDI32DLL;
15037typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15038typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15039SetDeviceGammaRamp_ptr_type
15040 g_pSetDeviceGammaRamp; // the API entry points in the dll
15041GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15042
15043WORD *g_pSavedGammaMap;
15044
15045#endif
15046
15047int InitScreenBrightness() {
15048#ifdef _WIN32
15049#ifdef ocpnUSE_GL
15050 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15051 HDC hDC;
15052 BOOL bbr;
15053
15054 if (NULL == hGDI32DLL) {
15055 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15056
15057 if (NULL != hGDI32DLL) {
15058 // Get the entry points of the required functions
15059 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15060 hGDI32DLL, "SetDeviceGammaRamp");
15061 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15062 hGDI32DLL, "GetDeviceGammaRamp");
15063
15064 // If the functions are not found, unload the DLL and return false
15065 if ((NULL == g_pSetDeviceGammaRamp) ||
15066 (NULL == g_pGetDeviceGammaRamp)) {
15067 FreeLibrary(hGDI32DLL);
15068 hGDI32DLL = NULL;
15069 return 0;
15070 }
15071 }
15072 }
15073
15074 // Interface is ready, so....
15075 // Get some storage
15076 if (!g_pSavedGammaMap) {
15077 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15078
15079 hDC = GetDC(NULL); // Get the full screen DC
15080 bbr = g_pGetDeviceGammaRamp(
15081 hDC, g_pSavedGammaMap); // Get the existing ramp table
15082 ReleaseDC(NULL, hDC); // Release the DC
15083 }
15084
15085 // On Windows hosts, try to adjust the registry to allow full range
15086 // setting of Gamma table This is an undocumented Windows hack.....
15087 wxRegKey *pRegKey = new wxRegKey(
15088 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15089 "NT\\CurrentVersion\\ICM");
15090 if (!pRegKey->Exists()) pRegKey->Create();
15091 pRegKey->SetValue("GdiIcmGammaRange", 256);
15092
15093 g_brightness_init = true;
15094 return 1;
15095 }
15096#endif
15097
15098 {
15099 if (NULL == g_pcurtain) {
15100 if (top_frame::Get()->CanSetTransparent()) {
15101 // Build the curtain window
15102 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15103 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15104 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15105 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15106
15107 // g_pcurtain = new ocpnCurtain(gFrame,
15108 // wxPoint(0,0),::wxGetDisplaySize(),
15109 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15110 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15111
15112 g_pcurtain->Hide();
15113
15114 HWND hWnd = GetHwndOf(g_pcurtain);
15115 SetWindowLong(hWnd, GWL_EXSTYLE,
15116 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15117 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15118 g_pcurtain->SetTransparent(0);
15119
15120 g_pcurtain->Maximize();
15121 g_pcurtain->Show();
15122
15123 // All of this is obtuse, but necessary for Windows...
15124 g_pcurtain->Enable();
15125 g_pcurtain->Disable();
15126
15127 top_frame::Get()->Disable();
15128 top_frame::Get()->Enable();
15129 // SetFocus();
15130 }
15131 }
15132 g_brightness_init = true;
15133
15134 return 1;
15135 }
15136#else
15137 // Look for "xcalib" application
15138 wxString cmd("xcalib -version");
15139
15140 wxArrayString output;
15141 long r = wxExecute(cmd, output);
15142 if (0 != r)
15143 wxLogMessage(
15144 " External application \"xcalib\" not found. Screen brightness "
15145 "not changed.");
15146
15147 g_brightness_init = true;
15148 return 0;
15149#endif
15150}
15151
15152int RestoreScreenBrightness() {
15153#ifdef _WIN32
15154
15155 if (g_pSavedGammaMap) {
15156 HDC hDC = GetDC(NULL); // Get the full screen DC
15157 g_pSetDeviceGammaRamp(hDC,
15158 g_pSavedGammaMap); // Restore the saved ramp table
15159 ReleaseDC(NULL, hDC); // Release the DC
15160
15161 free(g_pSavedGammaMap);
15162 g_pSavedGammaMap = NULL;
15163 }
15164
15165 if (g_pcurtain) {
15166 g_pcurtain->Close();
15167 g_pcurtain->Destroy();
15168 g_pcurtain = NULL;
15169 }
15170
15171 g_brightness_init = false;
15172 return 1;
15173
15174#endif
15175
15176#ifdef BRIGHT_XCALIB
15177 if (g_brightness_init) {
15178 wxString cmd;
15179 cmd = "xcalib -clear";
15180 wxExecute(cmd, wxEXEC_ASYNC);
15181 g_brightness_init = false;
15182 }
15183
15184 return 1;
15185#endif
15186
15187 return 0;
15188}
15189
15190// Set brightness. [0..100]
15191int SetScreenBrightness(int brightness) {
15192#ifdef _WIN32
15193
15194 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15195 // some (most modern?) versions of gdi32.dll Load the required library dll,
15196 // if not already in place
15197#ifdef ocpnUSE_GL
15198 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15199 if (g_pcurtain) {
15200 g_pcurtain->Close();
15201 g_pcurtain->Destroy();
15202 g_pcurtain = NULL;
15203 }
15204
15205 InitScreenBrightness();
15206
15207 if (NULL == hGDI32DLL) {
15208 // Unicode stuff.....
15209 wchar_t wdll_name[80];
15210 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15211 LPCWSTR cstr = wdll_name;
15212
15213 hGDI32DLL = LoadLibrary(cstr);
15214
15215 if (NULL != hGDI32DLL) {
15216 // Get the entry points of the required functions
15217 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15218 hGDI32DLL, "SetDeviceGammaRamp");
15219 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15220 hGDI32DLL, "GetDeviceGammaRamp");
15221
15222 // If the functions are not found, unload the DLL and return false
15223 if ((NULL == g_pSetDeviceGammaRamp) ||
15224 (NULL == g_pGetDeviceGammaRamp)) {
15225 FreeLibrary(hGDI32DLL);
15226 hGDI32DLL = NULL;
15227 return 0;
15228 }
15229 }
15230 }
15231
15232 HDC hDC = GetDC(NULL); // Get the full screen DC
15233
15234 /*
15235 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15236 if (cmcap != CM_GAMMA_RAMP)
15237 {
15238 wxLogMessage(" Video hardware does not support brightness control by
15239 gamma ramp adjustment."); return false;
15240 }
15241 */
15242
15243 int increment = brightness * 256 / 100;
15244
15245 // Build the Gamma Ramp table
15246 WORD GammaTable[3][256];
15247
15248 int table_val = 0;
15249 for (int i = 0; i < 256; i++) {
15250 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15251 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15252 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15253
15254 table_val += increment;
15255
15256 if (table_val > 65535) table_val = 65535;
15257 }
15258
15259 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15260 ReleaseDC(NULL, hDC); // Release the DC
15261
15262 return 1;
15263 }
15264#endif
15265
15266 {
15267 if (g_pSavedGammaMap) {
15268 HDC hDC = GetDC(NULL); // Get the full screen DC
15269 g_pSetDeviceGammaRamp(hDC,
15270 g_pSavedGammaMap); // Restore the saved ramp table
15271 ReleaseDC(NULL, hDC); // Release the DC
15272 }
15273
15274 if (brightness < 100) {
15275 if (NULL == g_pcurtain) InitScreenBrightness();
15276
15277 if (g_pcurtain) {
15278 int sbrite = wxMax(1, brightness);
15279 sbrite = wxMin(100, sbrite);
15280
15281 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15282 }
15283 } else {
15284 if (g_pcurtain) {
15285 g_pcurtain->Close();
15286 g_pcurtain->Destroy();
15287 g_pcurtain = NULL;
15288 }
15289 }
15290
15291 return 1;
15292 }
15293
15294#endif
15295
15296#ifdef BRIGHT_XCALIB
15297
15298 if (!g_brightness_init) {
15299 last_brightness = 100;
15300 g_brightness_init = true;
15301 temp_file_name = wxFileName::CreateTempFileName("");
15302 InitScreenBrightness();
15303 }
15304
15305#ifdef __OPCPN_USEICC__
15306 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15307 // desired, and then activate this temporary profile using xcalib <filename>
15308 if (!CreateSimpleICCProfileFile(
15309 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15310 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15311 wxString cmd("xcalib ");
15312 cmd += temp_file_name;
15313
15314 wxExecute(cmd, wxEXEC_ASYNC);
15315 }
15316
15317#else
15318 // Or, use "xcalib -co" to set overall contrast value
15319 // This is not as nice, since the -co parameter wants to be a fraction of
15320 // the current contrast, and values greater than 100 are not allowed. As a
15321 // result, increases of contrast must do a "-clear" step first, which
15322 // produces objectionable flashing.
15323 if (brightness > last_brightness) {
15324 wxString cmd;
15325 cmd = "xcalib -clear";
15326 wxExecute(cmd, wxEXEC_ASYNC);
15327
15328 ::wxMilliSleep(10);
15329
15330 int brite_adj = wxMax(1, brightness);
15331 cmd.Printf("xcalib -co %2d -a", brite_adj);
15332 wxExecute(cmd, wxEXEC_ASYNC);
15333 } else {
15334 int brite_adj = wxMax(1, brightness);
15335 int factor = (brite_adj * 100) / last_brightness;
15336 factor = wxMax(1, factor);
15337 wxString cmd;
15338 cmd.Printf("xcalib -co %2d -a", factor);
15339 wxExecute(cmd, wxEXEC_ASYNC);
15340 }
15341
15342#endif
15343
15344 last_brightness = brightness;
15345
15346#endif
15347
15348 return 0;
15349}
15350
15351#ifdef __OPCPN_USEICC__
15352
15353#define MLUT_TAG 0x6d4c5554L
15354#define VCGT_TAG 0x76636774L
15355
15356int GetIntEndian(unsigned char *s) {
15357 int ret;
15358 unsigned char *p;
15359 int i;
15360
15361 p = (unsigned char *)&ret;
15362
15363 if (1)
15364 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15365 else
15366 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15367
15368 return ret;
15369}
15370
15371unsigned short GetShortEndian(unsigned char *s) {
15372 unsigned short ret;
15373 unsigned char *p;
15374 int i;
15375
15376 p = (unsigned char *)&ret;
15377
15378 if (1)
15379 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15380 else
15381 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15382
15383 return ret;
15384}
15385
15386// Create a very simple Gamma correction file readable by xcalib
15387int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15388 double co_green, double co_blue) {
15389 FILE *fp;
15390
15391 if (file_name) {
15392 fp = fopen(file_name, "wb");
15393 if (!fp) return -1; /* file can not be created */
15394 } else
15395 return -1; /* filename char pointer not valid */
15396
15397 // Write header
15398 char header[128];
15399 for (int i = 0; i < 128; i++) header[i] = 0;
15400
15401 fwrite(header, 128, 1, fp);
15402
15403 // Num tags
15404 int numTags0 = 1;
15405 int numTags = GetIntEndian((unsigned char *)&numTags0);
15406 fwrite(&numTags, 1, 4, fp);
15407
15408 int tagName0 = VCGT_TAG;
15409 int tagName = GetIntEndian((unsigned char *)&tagName0);
15410 fwrite(&tagName, 1, 4, fp);
15411
15412 int tagOffset0 = 128 + 4 * sizeof(int);
15413 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15414 fwrite(&tagOffset, 1, 4, fp);
15415
15416 int tagSize0 = 1;
15417 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15418 fwrite(&tagSize, 1, 4, fp);
15419
15420 fwrite(&tagName, 1, 4, fp); // another copy of tag
15421
15422 fwrite(&tagName, 1, 4, fp); // dummy
15423
15424 // Table type
15425
15426 /* VideoCardGammaTable (The simplest type) */
15427 int gammatype0 = 0;
15428 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15429 fwrite(&gammatype, 1, 4, fp);
15430
15431 int numChannels0 = 3;
15432 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15433 fwrite(&numChannels, 1, 2, fp);
15434
15435 int numEntries0 = 256;
15436 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15437 fwrite(&numEntries, 1, 2, fp);
15438
15439 int entrySize0 = 1;
15440 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15441 fwrite(&entrySize, 1, 2, fp);
15442
15443 unsigned char ramp[256];
15444
15445 // Red ramp
15446 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15447 fwrite(ramp, 256, 1, fp);
15448
15449 // Green ramp
15450 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15451 fwrite(ramp, 256, 1, fp);
15452
15453 // Blue ramp
15454 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15455 fwrite(ramp, 256, 1, fp);
15456
15457 fclose(fp);
15458
15459 return 0;
15460}
15461#endif // __OPCPN_USEICC__
Select * pSelectAIS
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
AIS target definitions.
Class AISTargetQueryDialog.
std::unique_ptr< HostApi > GetHostApi()
HostApi factory,.
Definition api_121.cpp:868
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:61
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1315
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1314
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1315
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1314
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:173
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13824
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:4582
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11868
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4578
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3674
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13783
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:4528
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:805
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2359
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8034
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7843
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5110
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:4659
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5390
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:4603
bool IsTideDialogOpen() const
Definition chcanv.cpp:13822
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:4665
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13779
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4523
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5409
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10277
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.