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// For compilers that support precompilation, includes "wx.h".
27#include <wx/wxprec.h>
28
29#ifndef WX_PRECOMP
30#include <wx/wx.h>
31#endif // precompiled headers
32#include <wx/image.h>
33#include <wx/graphics.h>
34#include <wx/clipbrd.h>
35#include <wx/aui/aui.h>
36
37#include "config.h"
38
39#include "model/ais_decoder.h"
42#include "model/cmdline.h"
43#include "model/conn_params.h"
44#include "model/geodesic.h"
45#include "model/gui.h"
46#include "model/gui_vars.h"
47#include "model/idents.h"
48#include "model/multiplexer.h"
50#include "model/nav_object_database.h"
51#include "model/navobj_db.h"
52#include "model/navutil_base.h"
53#include "model/own_ship.h"
54#include "model/plugin_comm.h"
55#include "model/route.h"
56#include "model/routeman.h"
57#include "model/select.h"
58#include "model/select_item.h"
59#include "model/track.h"
60
61#include "ais.h"
64#include "canvas_config.h"
65#include "canvas_menu.h"
66#include "canvas_options.h"
67#include "chartdb.h"
68#include "chartimg.h"
69#include "chcanv.h"
70#include "ch_info_win.h"
71#include "cm93.h" // for chart outline draw
72#include "compass.h"
73#include "concanv.h"
74#include "detail_slider.h"
75#include "hotkeys_dlg.h"
76#include "font_mgr.h"
77#include "gl_texture_descr.h"
78#include "go_to_position_dlg.h"
79#include "gshhs.h"
80#include "ienc_toolbar.h"
81#include "kml.h"
82#include "line_clip.h"
83#include "mark_info.h"
84#include "mbtiles.h"
85#include "mui_bar.h"
86#include "navutil.h"
87#include "ocpn_aui_manager.h"
88#include "ocpndc.h"
89#include "ocpn_frame.h"
90#include "ocpn_pixel.h"
91#include "ocpn_region.h"
92#include "options.h"
93#include "piano.h"
94#include "pluginmanager.h"
95#include "quilt.h"
96#include "route_gui.h"
97#include "routemanagerdialog.h"
98#include "route_point_gui.h"
99#include "route_prop_dlg_impl.h"
100#include "s52plib.h"
101#include "s52utils.h"
102#include "s57_query_dlg.h"
103#include "s57chart.h" // for ArrayOfS57Obj
104#include "shapefile_basemap.h"
105#include "styles.h"
106#include "SystemCmdSound.h"
107#include "tcmgr.h"
108#include "tc_win.h"
109#include "thumbwin.h"
110#include "tide_time.h"
111#include "timers.h"
112#include "toolbar.h"
113#include "track_gui.h"
114#include "track_prop_dlg.h"
115#include "undo.h"
116
117#include "s57_ocpn_utils.h"
118
119#ifdef __ANDROID__
120#include "androidUTIL.h"
121#endif
122
123#ifdef ocpnUSE_GL
124#include "gl_chart_canvas.h"
127#endif
128
129#ifdef __VISUALC__
130#include <wx/msw/msvcrt.h>
131#endif
132
133#ifndef __WXMSW__
134#include <signal.h>
135#include <setjmp.h>
136#endif
137
138#ifdef __WXMSW__
139#define printf printf2
140
141int __cdecl printf2(const char *format, ...) {
142 char str[1024];
143
144 va_list argptr;
145 va_start(argptr, format);
146 int ret = vsnprintf(str, sizeof(str), format, argptr);
147 va_end(argptr);
148 OutputDebugStringA(str);
149 return ret;
150}
151#endif
152
153#if defined(__MSVC__) && (_MSC_VER < 1700)
154#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
155#endif
156
157// Define to enable the invocation of a temporary menubar by pressing the Alt
158// key. Not implemented for Windows XP, as it interferes with Alt-Tab
159// processing.
160#define OCPN_ALT_MENUBAR 1
161
162// Profiling support
163// #include "/usr/include/valgrind/callgrind.h"
164
165// ----------------------------------------------------------------------------
166// Useful Prototypes
167// ----------------------------------------------------------------------------
168extern ColorScheme global_color_scheme; // library dependence
169extern wxColor GetDimColor(wxColor c); // library dependence
170
171static bool g_bSmoothRecenter = true;
172static bool bDrawCurrentValues;
182static int mouse_x;
192static int mouse_y;
193static bool mouse_leftisdown;
194static bool g_brouteCreating;
195static int r_gamma_mult;
196static int g_gamma_mult;
197static int b_gamma_mult;
198static int gamma_state;
199static bool g_brightness_init;
200static int last_brightness;
201static wxGLContext *g_pGLcontext; // shared common context
202
203// "Curtain" mode parameters
204static wxDialog *g_pcurtain;
205
206static wxString g_lastS52PLIBPluginMessage;
207
208#define MIN_BRIGHT 10
209#define MAX_BRIGHT 100
210
211//------------------------------------------------------------------------------
212// ChartCanvas Implementation
213//------------------------------------------------------------------------------
214BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
215EVT_PAINT(ChartCanvas::OnPaint)
216EVT_ACTIVATE(ChartCanvas::OnActivate)
217EVT_SIZE(ChartCanvas::OnSize)
218#ifndef HAVE_WX_GESTURE_EVENTS
219EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
220#endif
221EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
222EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
223EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
224EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
225EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
226EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
227EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
228EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
229EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
230EVT_KEY_UP(ChartCanvas::OnKeyUp)
231EVT_CHAR(ChartCanvas::OnKeyChar)
232EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
233EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
234EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
235EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
236EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
237EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
238EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
239EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
240EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
241
242END_EVENT_TABLE()
243
244// Define a constructor for my canvas
245ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
246 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
247 m_nmea_log(nmea_log) {
248 parent_frame = (MyFrame *)frame; // save a pointer to parent
249 m_canvasIndex = canvasIndex;
250
251 pscratch_bm = NULL;
252
253 SetBackgroundColour(wxColour(0, 0, 0));
254 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
255 // color scheme change
256
257 m_groupIndex = 0;
258 m_bDrawingRoute = false;
259 m_bRouteEditing = false;
260 m_bMarkEditing = false;
261 m_bRoutePoinDragging = false;
262 m_bIsInRadius = false;
263 m_bMayToggleMenuBar = true;
264
265 m_bFollow = false;
266 m_bShowNavobjects = true;
267 m_bTCupdate = false;
268 m_bAppendingRoute = false; // was true in MSW, why??
269 pThumbDIBShow = NULL;
270 m_bShowCurrent = false;
271 m_bShowTide = false;
272 bShowingCurrent = false;
273 pCwin = NULL;
274 warp_flag = false;
275 m_bzooming = false;
276 m_b_paint_enable = true;
277 m_routeState = 0;
278
279 pss_overlay_bmp = NULL;
280 pss_overlay_mask = NULL;
281 m_bChartDragging = false;
282 m_bMeasure_Active = false;
283 m_bMeasure_DistCircle = false;
284 m_pMeasureRoute = NULL;
285 m_pTrackRolloverWin = NULL;
286 m_pRouteRolloverWin = NULL;
287 m_pAISRolloverWin = NULL;
288 m_bedge_pan = false;
289 m_disable_edge_pan = false;
290 m_dragoffsetSet = false;
291 m_bautofind = false;
292 m_bFirstAuto = true;
293 m_groupIndex = 0;
294 m_singleChart = NULL;
295 m_upMode = NORTH_UP_MODE;
296 m_bShowAIS = true;
297 m_bShowAISScaled = false;
298 m_timed_move_vp_active = false;
299 m_inPinch = false;
300 m_disable_adjust_on_zoom = false;
301
302 m_vLat = 0.;
303 m_vLon = 0.;
304
305 m_pCIWin = NULL;
306
307 m_pSelectedRoute = NULL;
308 m_pSelectedTrack = NULL;
309 m_pRoutePointEditTarget = NULL;
310 m_pFoundPoint = NULL;
311 m_pMouseRoute = NULL;
312 m_prev_pMousePoint = NULL;
313 m_pEditRouteArray = NULL;
314 m_pFoundRoutePoint = NULL;
315 m_FinishRouteOnKillFocus = true;
316
317 m_pRolloverRouteSeg = NULL;
318 m_pRolloverTrackSeg = NULL;
319 m_bsectors_shown = false;
320
321 m_bbrightdir = false;
322 r_gamma_mult = 1;
323 g_gamma_mult = 1;
324 b_gamma_mult = 1;
325
326 m_pos_image_user_day = NULL;
327 m_pos_image_user_dusk = NULL;
328 m_pos_image_user_night = NULL;
329 m_pos_image_user_grey_day = NULL;
330 m_pos_image_user_grey_dusk = NULL;
331 m_pos_image_user_grey_night = NULL;
332
333 m_zoom_factor = 1;
334 m_rotation_speed = 0;
335 m_mustmove = 0;
336
337 m_OSoffsetx = 0.;
338 m_OSoffsety = 0.;
339
340 m_pos_image_user_yellow_day = NULL;
341 m_pos_image_user_yellow_dusk = NULL;
342 m_pos_image_user_yellow_night = NULL;
343
344 SetOwnShipState(SHIP_INVALID);
345
346 undo = new Undo(this);
347
348 VPoint.Invalidate();
349
350 m_glcc = NULL;
351
352 m_focus_indicator_pix = 1;
353
354 m_pCurrentStack = NULL;
355 m_bpersistent_quilt = false;
356 m_piano_ctx_menu = NULL;
357 m_Compass = NULL;
358 m_NotificationsList = NULL;
359 m_notification_button = NULL;
360
361 g_ChartNotRenderScaleFactor = 2.0;
362 m_bShowScaleInStatusBar = true;
363
364 m_muiBar = NULL;
365 m_bShowScaleInStatusBar = false;
366 m_show_focus_bar = true;
367
368 m_bShowOutlines = false;
369 m_bDisplayGrid = false;
370 m_bShowDepthUnits = true;
371 m_encDisplayCategory = (int)STANDARD;
372
373 m_encShowLights = true;
374 m_encShowAnchor = true;
375 m_encShowDataQual = false;
376 m_bShowGPS = true;
377 m_pQuilt = new Quilt(this);
378 SetQuiltMode(true);
379 SetAlertString("");
380 m_sector_glat = 0;
381 m_sector_glon = 0;
382 g_PrintingInProgress = false;
383
384#ifdef HAVE_WX_GESTURE_EVENTS
385 m_oldVPSScale = -1.0;
386 m_popupWanted = false;
387 m_leftdown = false;
388#endif /* HAVE_WX_GESTURE_EVENTS */
389 m_inLongPress = false;
390 SetupGlCanvas();
391
392 singleClickEventIsValid = false;
393
394 // Build the cursors
395
396 pCursorLeft = NULL;
397 pCursorRight = NULL;
398 pCursorUp = NULL;
399 pCursorDown = NULL;
400 pCursorArrow = NULL;
401 pCursorPencil = NULL;
402 pCursorCross = NULL;
403
404 RebuildCursors();
405
406 SetCursor(*pCursorArrow);
407
408 pPanTimer = new wxTimer(this, m_MouseDragging);
409 pPanTimer->Stop();
410
411 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
412 pMovementTimer->Stop();
413
414 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
415 pMovementStopTimer->Stop();
416
417 pRotDefTimer = new wxTimer(this, ROT_TIMER);
418 pRotDefTimer->Stop();
419
420 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
421 m_DoubleClickTimer->Stop();
422
423 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
424 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
425 m_chart_drag_inertia_active = false;
426
427 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
428 m_animationActive = false;
429 m_menuTimer.SetOwner(this, MENU_TIMER);
430
431 m_panx = m_pany = 0;
432 m_panspeed = 0;
433 m_panx_target_final = m_pany_target_final = 0;
434 m_panx_target_now = m_pany_target_now = 0;
435
436 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
437 pCurTrackTimer->Stop();
438 m_curtrack_timer_msec = 10;
439
440 m_wheelzoom_stop_oneshot = 0;
441 m_last_wheel_dir = 0;
442
443 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
444
445 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
446
447 m_rollover_popup_timer_msec = 20;
448
449 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
450
451 m_b_rot_hidef = true;
452
453 proute_bm = NULL;
454 m_prot_bm = NULL;
455
456 m_upMode = NORTH_UP_MODE;
457 m_bLookAhead = false;
458
459 // Set some benign initial values
460
461 m_cs = GLOBAL_COLOR_SCHEME_DAY;
462 VPoint.clat = 0;
463 VPoint.clon = 0;
464 VPoint.view_scale_ppm = 1;
465 VPoint.Invalidate();
466 m_nMeasureState = 0;
467
468 m_canvas_scale_factor = 1.;
469
470 m_canvas_width = 1000;
471
472 m_overzoomTextWidth = 0;
473 m_overzoomTextHeight = 0;
474
475 // Create the default world chart
476 pWorldBackgroundChart = new GSHHSChart;
477 gShapeBasemap.Reset();
478
479 // Create the default depth unit emboss maps
480 m_pEM_Feet = NULL;
481 m_pEM_Meters = NULL;
482 m_pEM_Fathoms = NULL;
483
484 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
485
486 m_pEM_OverZoom = NULL;
487 SetOverzoomFont();
488 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
489
490 // Build icons for tide/current points
491 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
492 m_bmTideDay =
493 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
494
495 // Dusk
496 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
497
498 // Night
499 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
500
501 // Build Dusk/Night ownship icons
502 double factor_dusk = 0.5;
503 double factor_night = 0.25;
504
505 // Red
506 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
507
508 int rimg_width = m_os_image_red_day.GetWidth();
509 int rimg_height = m_os_image_red_day.GetHeight();
510
511 m_os_image_red_dusk = m_os_image_red_day.Copy();
512 m_os_image_red_night = m_os_image_red_day.Copy();
513
514 for (int iy = 0; iy < rimg_height; iy++) {
515 for (int ix = 0; ix < rimg_width; ix++) {
516 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
517 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
518 m_os_image_red_day.GetGreen(ix, iy),
519 m_os_image_red_day.GetBlue(ix, iy));
520 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
521 hsv.value = hsv.value * factor_dusk;
522 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
523 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
524
525 hsv = wxImage::RGBtoHSV(rgb);
526 hsv.value = hsv.value * factor_night;
527 nrgb = wxImage::HSVtoRGB(hsv);
528 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
529 }
530 }
531 }
532
533 // Grey
534 m_os_image_grey_day =
535 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
536
537 int gimg_width = m_os_image_grey_day.GetWidth();
538 int gimg_height = m_os_image_grey_day.GetHeight();
539
540 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
541 m_os_image_grey_night = m_os_image_grey_day.Copy();
542
543 for (int iy = 0; iy < gimg_height; iy++) {
544 for (int ix = 0; ix < gimg_width; ix++) {
545 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
546 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
547 m_os_image_grey_day.GetGreen(ix, iy),
548 m_os_image_grey_day.GetBlue(ix, iy));
549 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
550 hsv.value = hsv.value * factor_dusk;
551 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
552 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
553
554 hsv = wxImage::RGBtoHSV(rgb);
555 hsv.value = hsv.value * factor_night;
556 nrgb = wxImage::HSVtoRGB(hsv);
557 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
558 }
559 }
560 }
561
562 // Yellow
563 m_os_image_yellow_day = m_os_image_red_day.Copy();
564
565 gimg_width = m_os_image_yellow_day.GetWidth();
566 gimg_height = m_os_image_yellow_day.GetHeight();
567
568 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
569 m_os_image_yellow_night = m_os_image_red_day.Copy();
570
571 for (int iy = 0; iy < gimg_height; iy++) {
572 for (int ix = 0; ix < gimg_width; ix++) {
573 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
574 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
575 m_os_image_yellow_day.GetGreen(ix, iy),
576 m_os_image_yellow_day.GetBlue(ix, iy));
577 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
578 hsv.hue += 60. / 360.; // shift to yellow
579 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
580 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
581
582 hsv = wxImage::RGBtoHSV(rgb);
583 hsv.value = hsv.value * factor_dusk;
584 hsv.hue += 60. / 360.; // shift to yellow
585 nrgb = wxImage::HSVtoRGB(hsv);
586 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
587
588 hsv = wxImage::RGBtoHSV(rgb);
589 hsv.hue += 60. / 360.; // shift to yellow
590 hsv.value = hsv.value * factor_night;
591 nrgb = wxImage::HSVtoRGB(hsv);
592 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
593 }
594 }
595 }
596
597 // Set initial pointers to ownship images
598 m_pos_image_red = &m_os_image_red_day;
599 m_pos_image_yellow = &m_os_image_yellow_day;
600 m_pos_image_grey = &m_os_image_grey_day;
601
602 SetUserOwnship();
603
604 m_pBrightPopup = NULL;
605
606#ifdef ocpnUSE_GL
607 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
608#endif
609
610 SetupGridFont();
611
612 m_Piano = new Piano(this);
613
614 m_bShowCompassWin = true;
615 m_Compass = new ocpnCompass(this);
616 m_Compass->SetScaleFactor(g_compass_scalefactor);
617 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
618
619 m_notification_button = new NotificationButton(this);
620 m_notification_button->SetScaleFactor(g_compass_scalefactor);
621 m_notification_button->Show(true);
622
623 m_pianoFrozen = false;
624
625 SetMinSize(wxSize(200, 200));
626
627 m_displayScale = 1.0;
628#if defined(__WXOSX__) || defined(__WXGTK3__)
629 // Support scaled HDPI displays.
630 m_displayScale = GetContentScaleFactor();
631#endif
632 VPoint.SetPixelScale(m_displayScale);
633
634#ifdef HAVE_WX_GESTURE_EVENTS
635 // if (!m_glcc)
636 {
637 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
638 wxLogError("Failed to enable touch events");
639 }
640
641 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
642
643 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
644 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
645
646 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
647 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
648
649 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
650 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
651
652 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
653 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
654 }
655#endif
656
657 // Listen for notification events
658 auto &noteman = NotificationManager::GetInstance();
659
660 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
661 evt_notificationlist_change_listener.Listen(
662 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
663 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
664 if (m_NotificationsList && m_NotificationsList->IsShown()) {
665 m_NotificationsList->ReloadNotificationList();
666 }
667 Refresh();
668 });
669}
670
671ChartCanvas::~ChartCanvas() {
672 delete pThumbDIBShow;
673
674 // Delete Cursors
675 delete pCursorLeft;
676 delete pCursorRight;
677 delete pCursorUp;
678 delete pCursorDown;
679 delete pCursorArrow;
680 delete pCursorPencil;
681 delete pCursorCross;
682
683 delete pPanTimer;
684 delete pMovementTimer;
685 delete pMovementStopTimer;
686 delete pCurTrackTimer;
687 delete pRotDefTimer;
688 delete m_DoubleClickTimer;
689
690 delete m_pTrackRolloverWin;
691 delete m_pRouteRolloverWin;
692 delete m_pAISRolloverWin;
693 delete m_pBrightPopup;
694
695 delete m_pCIWin;
696
697 delete pscratch_bm;
698
699 m_dc_route.SelectObject(wxNullBitmap);
700 delete proute_bm;
701
702 delete pWorldBackgroundChart;
703 delete pss_overlay_bmp;
704
705 delete m_pEM_Feet;
706 delete m_pEM_Meters;
707 delete m_pEM_Fathoms;
708
709 delete m_pEM_OverZoom;
710 // delete m_pEM_CM93Offset;
711
712 delete m_prot_bm;
713
714 delete m_pos_image_user_day;
715 delete m_pos_image_user_dusk;
716 delete m_pos_image_user_night;
717 delete m_pos_image_user_grey_day;
718 delete m_pos_image_user_grey_dusk;
719 delete m_pos_image_user_grey_night;
720 delete m_pos_image_user_yellow_day;
721 delete m_pos_image_user_yellow_dusk;
722 delete m_pos_image_user_yellow_night;
723
724 delete undo;
725#ifdef ocpnUSE_GL
726 if (!g_bdisable_opengl) {
727 delete m_glcc;
728
729#if wxCHECK_VERSION(2, 9, 0)
730 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
731#endif
732 }
733#endif
734
735 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
736 // wx tries to deliver events to this canvas during destroy.
737 MUIBar *muiBar = m_muiBar;
738 m_muiBar = 0;
739 delete muiBar;
740 delete m_pQuilt;
741 delete m_pCurrentStack;
742 delete m_Compass;
743 delete m_Piano;
744}
745
746void ChartCanvas::SetupGridFont() {
747 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
748 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
749 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
750 m_pgridFont = FontMgr::Get().FindOrCreateFont(
751 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
752 FALSE, wxString("Arial"));
753}
754
755void ChartCanvas::RebuildCursors() {
756 delete pCursorLeft;
757 delete pCursorRight;
758 delete pCursorUp;
759 delete pCursorDown;
760 delete pCursorArrow;
761 delete pCursorPencil;
762 delete pCursorCross;
763
764 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
765 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
766
767 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
768
769 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
770 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
771 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
772 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
773 wxImage ICursorPencil =
774 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
775 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
776
777#if !defined(__WXMSW__) && !defined(__WXQT__)
778 ICursorLeft.ConvertAlphaToMask(128);
779 ICursorRight.ConvertAlphaToMask(128);
780 ICursorUp.ConvertAlphaToMask(128);
781 ICursorDown.ConvertAlphaToMask(128);
782 ICursorPencil.ConvertAlphaToMask(10);
783 ICursorCross.ConvertAlphaToMask(10);
784#endif
785
786 if (ICursorLeft.Ok()) {
787 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
788 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
789 pCursorLeft = new wxCursor(ICursorLeft);
790 } else
791 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
792
793 if (ICursorRight.Ok()) {
794 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
795 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
796 pCursorRight = new wxCursor(ICursorRight);
797 } else
798 pCursorRight = new wxCursor(wxCURSOR_ARROW);
799
800 if (ICursorUp.Ok()) {
801 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
802 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
803 pCursorUp = new wxCursor(ICursorUp);
804 } else
805 pCursorUp = new wxCursor(wxCURSOR_ARROW);
806
807 if (ICursorDown.Ok()) {
808 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
809 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
810 pCursorDown = new wxCursor(ICursorDown);
811 } else
812 pCursorDown = new wxCursor(wxCURSOR_ARROW);
813
814 if (ICursorPencil.Ok()) {
815 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
816 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
817 pCursorPencil = new wxCursor(ICursorPencil);
818 } else
819 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
820
821 if (ICursorCross.Ok()) {
822 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
823 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
824 pCursorCross = new wxCursor(ICursorCross);
825 } else
826 pCursorCross = new wxCursor(wxCURSOR_ARROW);
827
828 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
829 pPlugIn_Cursor = NULL;
830}
831
832void ChartCanvas::CanvasApplyLocale() {
833 CreateDepthUnitEmbossMaps(m_cs);
834 CreateOZEmbossMapData(m_cs);
835}
836
837void ChartCanvas::SetupGlCanvas() {
838#ifndef __ANDROID__
839#ifdef ocpnUSE_GL
840 if (!g_bdisable_opengl) {
841 if (g_bopengl) {
842 wxLogMessage("Creating glChartCanvas");
843 m_glcc = new glChartCanvas(this);
844
845 // We use one context for all GL windows, so that textures etc will be
846 // automatically shared
847 if (IsPrimaryCanvas()) {
848 // qDebug() << "Creating Primary Context";
849
850 // wxGLContextAttrs ctxAttr;
851 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
852 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
853 // NULL, &ctxAttr);
854 wxGLContext *pctx = new wxGLContext(m_glcc);
855 m_glcc->SetContext(pctx);
856 g_pGLcontext = pctx; // Save a copy of the common context
857 } else {
858#ifdef __WXOSX__
859 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
860#else
861 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
862 // saved common context
863#endif
864 }
865 }
866 }
867#endif
868#endif
869
870#ifdef __ANDROID__ // ocpnUSE_GL
871 if (!g_bdisable_opengl) {
872 if (g_bopengl) {
873 // qDebug() << "SetupGlCanvas";
874 wxLogMessage("Creating glChartCanvas");
875
876 // We use one context for all GL windows, so that textures etc will be
877 // automatically shared
878 if (IsPrimaryCanvas()) {
879 qDebug() << "Creating Primary glChartCanvas";
880
881 // wxGLContextAttrs ctxAttr;
882 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
883 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
884 // NULL, &ctxAttr);
885 m_glcc = new glChartCanvas(this);
886
887 wxGLContext *pctx = new wxGLContext(m_glcc);
888 m_glcc->SetContext(pctx);
889 g_pGLcontext = pctx; // Save a copy of the common context
890 m_glcc->m_pParentCanvas = this;
891 // m_glcc->Reparent(this);
892 } else {
893 qDebug() << "Creating Secondary glChartCanvas";
894 // QGLContext *pctx =
895 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
896 // << "pctx: " << pctx;
897
898 m_glcc = new glChartCanvas(
899 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
900 // m_glcc = new glChartCanvas(this, pctx); //Shared
901 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
902 wxGLContext *pwxctx = new wxGLContext(m_glcc);
903 m_glcc->SetContext(pwxctx);
904 m_glcc->m_pParentCanvas = this;
905 // m_glcc->Reparent(this);
906 }
907 }
908 }
909#endif
910}
911
912void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
913 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
914
915 // On Android, we get a KillFocus on just about every keystroke.
916 // Why?
917#ifdef __ANDROID__
918 return;
919#endif
920
921 // Special logic:
922 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
923 // canvas focus. Why??? Who knows... So, we provide for this case by
924 // starting a timer if required to actually Finish() a route on a legitimate
925 // focus change, but not if the focus is quickly regained ( <20 msec.) on
926 // this canvas.
927#ifdef __WXOSX__
928 if (m_routeState && m_FinishRouteOnKillFocus)
929 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
930#else
931 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
932#endif
933}
934
935void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
936 m_routeFinishTimer.Stop();
937
938 // Try to keep the global top-line menubar selections up to date with the
939 // current "focus" canvas
940 gFrame->UpdateGlobalMenuItems(this);
941
942 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
943}
944
945void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
946 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
947}
948
949#ifdef HAVE_WX_GESTURE_EVENTS
950void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
951#ifdef __ANDROID__
952 /* we defer the popup menu call upon the leftup event
953 else the menu disappears immediately,
954 (see
955 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
956 */
957 m_popupWanted = true;
958#else
959 // m_inLongPress = true;
960 // Send a synthetic mouse left-up event to sync the mouse pan logic.
961 m_menuPos = event.GetPosition();
962 wxMouseEvent ev(wxEVT_LEFT_UP);
963 ev.m_x = m_menuPos.x;
964 ev.m_y = m_menuPos.y;
965 wxPostEvent(this, ev);
966
967 // Send a "RIGHT CLICK" event, for plugins
968 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
969 ev_right_click.m_x = m_menuPos.x;
970 ev_right_click.m_y = m_menuPos.y;
971 MouseEvent(ev_right_click);
972
973 // m_menuTimer.StartOnce(20); // Delay of 20 millisecond
974#endif
975}
976
977void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
978 // not implemented yet
979}
980
981void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
982
983void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
984
985void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
986 wxPoint pos = event.GetPosition();
987
988 m_leftdown = false;
989
990 if (!m_popupWanted) {
991 wxMouseEvent ev(wxEVT_LEFT_UP);
992 ev.m_x = pos.x;
993 ev.m_y = pos.y;
994 MouseEvent(ev);
995 return;
996 }
997
998 m_popupWanted = false;
999
1000 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1001 ev.m_x = pos.x;
1002 ev.m_y = pos.y;
1003
1004 MouseEvent(ev);
1005}
1006
1007void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1008 m_leftdown = true;
1009
1010 wxPoint pos = event.GetPosition();
1011 MouseEvent(event);
1012}
1013
1014void ChartCanvas::OnMotion(wxMouseEvent &event) {
1015 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1016 dragging, upon simple click, and without the OnLeftDown event before Thus,
1017 this consists in skiping it, and setting the leftdown bit according to a
1018 status that we trust */
1019 event.m_leftDown = m_leftdown;
1020 MouseEvent(event);
1021}
1022
1023void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1024 /* there are spurious end zoom events upon right-click */
1025 if (event.IsGestureEnd()) return;
1026
1027 double factor = event.GetZoomFactor();
1028
1029 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1030 m_oldVPSScale = GetVPScale();
1031 }
1032
1033 double current_vps = GetVPScale();
1034 double wanted_factor = m_oldVPSScale / current_vps * factor;
1035
1036 ZoomCanvas(wanted_factor, true, false);
1037
1038 // Allow combined zoom/pan operation
1039 if (event.IsGestureStart()) {
1040 m_zoomStartPoint = event.GetPosition();
1041 } else {
1042 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1043 PanCanvas(-delta.x, -delta.y);
1044 m_zoomStartPoint = event.GetPosition();
1045 }
1046}
1047
1048void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1049
1050void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1051 DoRotateCanvas(0.0);
1052}
1053#endif /* HAVE_WX_GESTURE_EVENTS */
1054void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1055 m_FinishRouteOnKillFocus = false;
1056 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1057 m_FinishRouteOnKillFocus = true;
1058}
1059
1060void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1061 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1062 m_vLat = pcc->iLat;
1063 m_vLon = pcc->iLon;
1064
1065 m_restore_dbindex = pcc->DBindex;
1066 m_bFollow = pcc->bFollow;
1067 if (pcc->GroupID < 0) pcc->GroupID = 0;
1068
1069 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1070 m_groupIndex = 0;
1071 else
1072 m_groupIndex = pcc->GroupID;
1073
1074 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1075
1076 ShowTides(pcc->bShowTides);
1077 ShowCurrents(pcc->bShowCurrents);
1078
1079 SetShowDepthUnits(pcc->bShowDepthUnits);
1080 SetShowGrid(pcc->bShowGrid);
1081 SetShowOutlines(pcc->bShowOutlines);
1082
1083 SetShowAIS(pcc->bShowAIS);
1084 SetAttenAIS(pcc->bAttenAIS);
1085
1086 // ENC options
1087 SetShowENCText(pcc->bShowENCText);
1088 m_encDisplayCategory = pcc->nENCDisplayCategory;
1089 m_encShowDepth = pcc->bShowENCDepths;
1090 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1091 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1092 m_encShowLights = pcc->bShowENCLights;
1093 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1094 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1095 m_encShowDataQual = pcc->bShowENCDataQuality;
1096
1097 bool courseUp = pcc->bCourseUp;
1098 bool headUp = pcc->bHeadUp;
1099 m_upMode = NORTH_UP_MODE;
1100 if (courseUp)
1101 m_upMode = COURSE_UP_MODE;
1102 else if (headUp)
1103 m_upMode = HEAD_UP_MODE;
1104
1105 m_bLookAhead = pcc->bLookahead;
1106
1107 m_singleChart = NULL;
1108}
1109
1110void ChartCanvas::ApplyGlobalSettings() {
1111 // GPS compas window
1112 if (m_Compass) {
1113 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1114 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1115 }
1116 m_notification_button->UpdateStatus();
1117}
1118
1119void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1120 bool groupOK = CheckGroup(m_groupIndex);
1121
1122 if (!groupOK) {
1123 SetGroupIndex(m_groupIndex, true);
1124 }
1125}
1126
1127void ChartCanvas::SetShowGPS(bool bshow) {
1128 if (m_bShowGPS != bshow) {
1129 delete m_Compass;
1130 m_Compass = new ocpnCompass(this, bshow);
1131 m_Compass->SetScaleFactor(g_compass_scalefactor);
1132 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1133 }
1134 m_bShowGPS = bshow;
1135}
1136
1137void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1138 m_bShowCompassWin = bshow;
1139 if (m_Compass) {
1140 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1141 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1142 }
1143}
1144
1145int ChartCanvas::GetPianoHeight() {
1146 int height = 0;
1147 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1148
1149 return height;
1150}
1151
1152void ChartCanvas::ConfigureChartBar() {
1153 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1154
1155 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1156 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1157
1158 if (GetQuiltMode()) {
1159 m_Piano->SetRoundedRectangles(true);
1160 }
1161 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1162 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1163 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1164}
1165
1166void ChartCanvas::ShowTides(bool bShow) {
1167 gFrame->LoadHarmonics();
1168
1169 if (ptcmgr->IsReady()) {
1170 SetbShowTide(bShow);
1171
1172 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1173 } else {
1174 wxLogMessage("Chart1::Event...TCMgr Not Available");
1175 SetbShowTide(false);
1176 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1177 }
1178
1179 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1180 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1181
1182 // TODO
1183 // if( GetbShowTide() ) {
1184 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1185 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1186 // update
1187 // } else
1188 // FrameTCTimer.Stop();
1189}
1190
1191void ChartCanvas::ShowCurrents(bool bShow) {
1192 gFrame->LoadHarmonics();
1193
1194 if (ptcmgr->IsReady()) {
1195 SetbShowCurrent(bShow);
1196 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1197 } else {
1198 wxLogMessage("Chart1::Event...TCMgr Not Available");
1199 SetbShowCurrent(false);
1200 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1201 }
1202
1203 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1204 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1205
1206 // TODO
1207 // if( GetbShowCurrent() ) {
1208 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1209 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1210 // update
1211 // } else
1212 // FrameTCTimer.Stop();
1213}
1214
1215// TODO
1216static ChartDummy *pDummyChart;
1217
1220
1221void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1222
1223void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1224 SetAlertString("");
1225
1226 int new_index = index;
1227 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1228
1229 bool bgroup_override = false;
1230 int old_group_index = new_index;
1231
1232 if (!CheckGroup(new_index)) {
1233 new_index = 0;
1234 bgroup_override = true;
1235 }
1236
1237 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1238 new_index = index;
1239
1240 // Get the currently displayed chart native scale, and the current ViewPort
1241 int current_chart_native_scale = GetCanvasChartNativeScale();
1242 ViewPort vp = GetVP();
1243
1244 m_groupIndex = new_index;
1245
1246 // Are there ENCs in this group
1247 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1248
1249 // Update the MUIBar for ENC availability
1250 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1251
1252 // Allow the chart database to pre-calculate the MBTile inclusion test
1253 // boolean...
1254 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1255
1256 // Invalidate the "sticky" chart on group change, since it might not be in
1257 // the new group
1258 g_sticky_chart = -1;
1259
1260 // We need a chartstack and quilt to figure out which chart to open in the
1261 // new group
1262 UpdateCanvasOnGroupChange();
1263
1264 int dbi_now = -1;
1265 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1266
1267 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1268
1269 // If a new reference chart is indicated, set a good scale for it.
1270 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1271 double best_scale = GetBestStartScale(dbi_hint, vp);
1272 SetVPScale(best_scale);
1273 }
1274
1275 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1276
1277 // Refresh the canvas, selecting the "best" chart,
1278 // applying the prior ViewPort exactly
1279 canvasChartsRefresh(dbi_hint);
1280
1281 UpdateCanvasControlBar();
1282
1283 if (!autoSwitch && bgroup_override) {
1284 // show a short timed message box
1285 wxString msg(_("Group \""));
1286
1287 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1288 msg += pGroup->m_group_name;
1289
1290 msg += _("\" is empty.");
1291
1292 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1293
1294 return;
1295 }
1296
1297 // Message box is deferred so that canvas refresh occurs properly before
1298 // dialog
1299 if (bgroup_override) {
1300 wxString msg(_("Group \""));
1301
1302 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1303 msg += pGroup->m_group_name;
1304
1305 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1306
1307 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1308 }
1309}
1310
1311bool ChartCanvas::CheckGroup(int igroup) {
1312 if (!ChartData) return true; // Not known yet...
1313
1314 if (igroup == 0) return true; // "all charts" is always OK
1315
1316 if (igroup < 0) // negative group is an error
1317 return false;
1318
1319 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1320
1321 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1322 // and auto-shift to group 0
1323 return false;
1324
1325 for (const auto &elem : pGroup->m_element_array) {
1326 for (unsigned int ic = 0;
1327 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1328 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1329 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1330
1331 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1332 }
1333 }
1334
1335 // If necessary, check for GSHHS
1336 for (const auto &elem : pGroup->m_element_array) {
1337 const wxString &element_root = elem.m_element_name;
1338 wxString test_string = "GSHH";
1339 if (element_root.Upper().Contains(test_string)) return true;
1340 }
1341
1342 return false;
1343}
1344
1345void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1346 if (!ChartData) return;
1347
1348 AbstractPlatform::ShowBusySpinner();
1349
1350 double old_scale = GetVPScale();
1351 InvalidateQuilt();
1352 SetQuiltRefChart(-1);
1353
1354 m_singleChart = NULL;
1355
1356 // delete m_pCurrentStack;
1357 // m_pCurrentStack = NULL;
1358
1359 // Build a new ChartStack
1360 if (!m_pCurrentStack) {
1361 m_pCurrentStack = new ChartStack;
1362 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1363 }
1364
1365 if (-1 != dbi_hint) {
1366 if (GetQuiltMode()) {
1367 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1368 SetQuiltRefChart(dbi_hint);
1369 } else {
1370 // Open the saved chart
1371 ChartBase *pTentative_Chart;
1372 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1373
1374 if (pTentative_Chart) {
1375 /* m_singleChart is always NULL here, (set above) should this go before
1376 * that? */
1377 if (m_singleChart) m_singleChart->Deactivate();
1378
1379 m_singleChart = pTentative_Chart;
1380 m_singleChart->Activate();
1381
1382 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1383 GetpCurrentStack(), m_singleChart->GetFullPath());
1384 }
1385 }
1386
1387 // refresh_Piano();
1388 } else {
1389 // Select reference chart from the stack, as though clicked by user
1390 // Make it the smallest scale chart on the stack
1391 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1392 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1393 SetQuiltRefChart(selected_index);
1394 }
1395
1396 // Validate the correct single chart, or set the quilt mode as appropriate
1397 SetupCanvasQuiltMode();
1398 if (!GetQuiltMode() && m_singleChart == 0) {
1399 // use a dummy like in DoChartUpdate
1400 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1401 m_singleChart = pDummyChart;
1402 SetVPScale(old_scale);
1403 }
1404
1405 ReloadVP();
1406
1407 UpdateCanvasControlBar();
1408 UpdateGPSCompassStatusBox(true);
1409
1410 SetCursor(wxCURSOR_ARROW);
1411
1412 AbstractPlatform::HideBusySpinner();
1413}
1414
1415bool ChartCanvas::DoCanvasUpdate() {
1416 double tLat, tLon; // Chart Stack location
1417 double vpLat, vpLon; // ViewPort location
1418 bool blong_jump = false;
1419 meters_to_shift = 0;
1420 dir_to_shift = 0;
1421
1422 bool bNewChart = false;
1423 bool bNewView = false;
1424 bool bCanvasChartAutoOpen = true; // debugging
1425
1426 bool bNewPiano = false;
1427 bool bOpenSpecified;
1428 ChartStack LastStack;
1429 ChartBase *pLast_Ch;
1430
1431 ChartStack WorkStack;
1432
1433 if (bDBUpdateInProgress) return false;
1434 if (!ChartData) return false;
1435
1436 if (ChartData->IsBusy()) return false;
1437 if (m_chart_drag_inertia_active) return false;
1438
1439 // Startup case:
1440 // Quilting is enabled, but the last chart seen was not quiltable
1441 // In this case, drop to single chart mode, set persistence flag,
1442 // And open the specified chart
1443 // TODO implement this
1444 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1445 // if( GetQuiltMode() ) {
1446 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1447 // gFrame->ToggleQuiltMode();
1448 // m_bpersistent_quilt = true;
1449 // m_singleChart = NULL;
1450 // }
1451 // }
1452 // }
1453
1454 // If in auto-follow mode, use the current glat,glon to build chart
1455 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1456 // other means
1457
1458 if (m_bFollow) {
1459 tLat = gLat;
1460 tLon = gLon;
1461
1462 // Set the ViewPort center based on the OWNSHIP offset
1463 double dx = m_OSoffsetx;
1464 double dy = m_OSoffsety;
1465 double d_east = dx / GetVP().view_scale_ppm;
1466 double d_north = dy / GetVP().view_scale_ppm;
1467
1468 if (GetUpMode() == NORTH_UP_MODE) {
1469 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1470 } else {
1471 double offset_angle = atan2(d_north, d_east);
1472 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1473 double chart_angle = GetVPRotation();
1474 double target_angle = chart_angle + offset_angle;
1475 double d_east_mod = offset_distance * cos(target_angle);
1476 double d_north_mod = offset_distance * sin(target_angle);
1477 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1478 }
1479
1480 // on lookahead mode, adjust the vp center point
1481 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1482 double cog_to_use = gCog;
1483 if (g_btenhertz &&
1484 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1485 cog_to_use = gCog_gt;
1486 blong_jump = true;
1487 }
1488 if (!g_btenhertz) cog_to_use = g_COGAvg;
1489
1490 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1491
1492 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1493 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1494
1495 double pixel_delta_tent =
1496 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1497
1498 double pixel_delta = 0;
1499
1500 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1501 // avoid jumping of the vp center point during slow maneuvering, or at
1502 // anchor....
1503 if (!std::isnan(gSog)) {
1504 if (gSog < 2.0)
1505 pixel_delta = 0.;
1506 else
1507 pixel_delta = pixel_delta_tent;
1508 }
1509
1510 meters_to_shift = 0;
1511 dir_to_shift = 0;
1512 if (!std::isnan(gCog)) {
1513 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1514 dir_to_shift = cog_to_use;
1515 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1516 &vpLon);
1517 } else {
1518 vpLat = gLat;
1519 vpLon = gLon;
1520 }
1521 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1522 m_OSoffsetx = 0; // center ownship on loss of GPS
1523 m_OSoffsety = 0;
1524 vpLat = gLat;
1525 vpLon = gLon;
1526 }
1527
1528 } else {
1529 tLat = m_vLat;
1530 tLon = m_vLon;
1531 vpLat = m_vLat;
1532 vpLon = m_vLon;
1533 }
1534
1535 if (GetQuiltMode()) {
1536 int current_db_index = -1;
1537 if (m_pCurrentStack)
1538 current_db_index =
1539 m_pCurrentStack
1540 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1541 // chart dbIndex
1542 else
1543 m_pCurrentStack = new ChartStack;
1544
1545 // This logic added to enable opening a chart when there is no
1546 // previous chart indication, either from inital startup, or from adding
1547 // new chart directory
1548 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1549 m_pCurrentStack) {
1550 if (m_pCurrentStack->nEntry) {
1551 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1552 1); // smallest scale
1553 SelectQuiltRefdbChart(new_dbIndex, true);
1554 m_bautofind = false;
1555 }
1556 }
1557
1558 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1559 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1560
1561 if (m_bFirstAuto) {
1562 // Allow the chart database to pre-calculate the MBTile inclusion test
1563 // boolean...
1564 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1565
1566 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1567 // physical pixels. On standard DPI displays where logical = physical
1568 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1569 // logical pixels, this ratio would be 0.5.
1570 double proposed_scale_onscreen =
1571 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1572
1573 int initial_db_index = m_restore_dbindex;
1574 if (initial_db_index < 0) {
1575 if (m_pCurrentStack->nEntry) {
1576 initial_db_index =
1577 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1578 } else
1579 m_bautofind = true; // initial_db_index = 0;
1580 }
1581
1582 if (m_pCurrentStack->nEntry) {
1583 int initial_type = ChartData->GetDBChartType(initial_db_index);
1584
1585 // Check to see if the target new chart is quiltable as a reference
1586 // chart
1587
1588 if (!IsChartQuiltableRef(initial_db_index)) {
1589 // If it is not quiltable, then walk the stack up looking for a
1590 // satisfactory chart i.e. one that is quiltable and of the same type
1591 // XXX if there's none?
1592 int stack_index = 0;
1593
1594 if (stack_index >= 0) {
1595 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1596 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1597 if (IsChartQuiltableRef(test_db_index) &&
1598 (initial_type ==
1599 ChartData->GetDBChartType(initial_db_index))) {
1600 initial_db_index = test_db_index;
1601 break;
1602 }
1603 stack_index++;
1604 }
1605 }
1606 }
1607
1608 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1609 if (pc) {
1610 SetQuiltRefChart(initial_db_index);
1611 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1612 }
1613 }
1614 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1615 // just GetVPScale(), so I'm not sure why it's necessary to define the
1616 // proposed_scale_onscreen variable.
1617 bNewView |= SetViewPoint(vpLat, vpLon,
1618 GetCanvasScaleFactor() / proposed_scale_onscreen,
1619 0, GetVPRotation());
1620 }
1621 // Measure rough jump distance if in bfollow mode
1622 // No good reason to do smooth pan for
1623 // jump distance more than one screen width at scale.
1624 bool super_jump = false;
1625 if (m_bFollow) {
1626 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1627 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1628 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1629 }
1630#if 0
1631 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1632 int nstep = 5;
1633 if (blong_jump) nstep = 20;
1634 StartTimedMovementVP(vpLat, vpLon, nstep);
1635 } else
1636#endif
1637 {
1638 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1639 }
1640
1641 goto update_finish;
1642 }
1643
1644 // Single Chart Mode from here....
1645 pLast_Ch = m_singleChart;
1646 ChartTypeEnum new_open_type;
1647 ChartFamilyEnum new_open_family;
1648 if (pLast_Ch) {
1649 new_open_type = pLast_Ch->GetChartType();
1650 new_open_family = pLast_Ch->GetChartFamily();
1651 } else {
1652 new_open_type = CHART_TYPE_KAP;
1653 new_open_family = CHART_FAMILY_RASTER;
1654 }
1655
1656 bOpenSpecified = m_bFirstAuto;
1657
1658 // Make sure the target stack is valid
1659 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1660
1661 // Build a chart stack based on tLat, tLon
1662 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1663 m_groupIndex)) { // Bogus Lat, Lon?
1664 if (NULL == pDummyChart) {
1665 pDummyChart = new ChartDummy;
1666 bNewChart = true;
1667 }
1668
1669 if (m_singleChart)
1670 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1671
1672 m_singleChart = pDummyChart;
1673
1674 // If the current viewpoint is invalid, set the default scale to
1675 // something reasonable.
1676 double set_scale = GetVPScale();
1677 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1678
1679 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1680
1681 // If the chart stack has just changed, there is new status
1682 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1683 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1684 bNewPiano = true;
1685 bNewChart = true;
1686 }
1687 }
1688
1689 // Copy the new (by definition empty) stack into the target stack
1690 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1691
1692 goto update_finish;
1693 }
1694
1695 // Check to see if Chart Stack has changed
1696 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1697 // New chart stack, so...
1698 bNewPiano = true;
1699
1700 // Save a copy of the current stack
1701 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1702
1703 // Copy the new stack into the target stack
1704 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1705
1706 // Is Current Chart in new stack?
1707
1708 int tEntry = -1;
1709 if (NULL != m_singleChart) // this handles startup case
1710 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1711 m_singleChart->GetFullPath());
1712
1713 if (tEntry != -1) { // m_singleChart is in the new stack
1714 m_pCurrentStack->CurrentStackEntry = tEntry;
1715 bNewChart = false;
1716 }
1717
1718 else // m_singleChart is NOT in new stack
1719 { // So, need to open a new chart
1720 // Find the largest scale raster chart that opens OK
1721
1722 ChartBase *pProposed = NULL;
1723
1724 if (bCanvasChartAutoOpen) {
1725 bool search_direction =
1726 false; // default is to search from lowest to highest
1727 int start_index = 0;
1728
1729 // A special case: If panning at high scale, open largest scale
1730 // chart first
1731 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1732 (LastStack.nEntry == 0)) {
1733 search_direction = true;
1734 start_index = m_pCurrentStack->nEntry - 1;
1735 }
1736
1737 // Another special case, open specified index on program start
1738 if (bOpenSpecified) {
1739 search_direction = false;
1740 start_index = 0;
1741 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1742 start_index = 0;
1743
1744 new_open_type = CHART_TYPE_DONTCARE;
1745 }
1746
1747 pProposed = ChartData->OpenStackChartConditional(
1748 m_pCurrentStack, start_index, search_direction, new_open_type,
1749 new_open_family);
1750
1751 // Try to open other types/families of chart in some priority
1752 if (NULL == pProposed)
1753 pProposed = ChartData->OpenStackChartConditional(
1754 m_pCurrentStack, start_index, search_direction,
1755 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1756
1757 if (NULL == pProposed)
1758 pProposed = ChartData->OpenStackChartConditional(
1759 m_pCurrentStack, start_index, search_direction,
1760 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1761
1762 bNewChart = true;
1763
1764 } // bCanvasChartAutoOpen
1765
1766 else
1767 pProposed = NULL;
1768
1769 // If no go, then
1770 // Open a Dummy Chart
1771 if (NULL == pProposed) {
1772 if (NULL == pDummyChart) {
1773 pDummyChart = new ChartDummy;
1774 bNewChart = true;
1775 }
1776
1777 if (pLast_Ch)
1778 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1779
1780 pProposed = pDummyChart;
1781 }
1782
1783 // Arriving here, pProposed points to an opened chart, or NULL.
1784 if (m_singleChart) m_singleChart->Deactivate();
1785 m_singleChart = pProposed;
1786
1787 if (m_singleChart) {
1788 m_singleChart->Activate();
1789 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1790 m_pCurrentStack, m_singleChart->GetFullPath());
1791 }
1792 } // need new chart
1793
1794 // Arriving here, m_singleChart is opened and OK, or NULL
1795 if (NULL != m_singleChart) {
1796 // Setup the view using the current scale
1797 double set_scale = GetVPScale();
1798
1799 // If the current viewpoint is invalid, set the default scale to
1800 // something reasonable.
1801 if (!GetVP().IsValid())
1802 set_scale = 1. / 20000.;
1803 else { // otherwise, match scale if elected.
1804 double proposed_scale_onscreen;
1805
1806 if (m_bFollow) { // autoset the scale only if in autofollow
1807 double new_scale_ppm =
1808 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1809 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1810 } else
1811 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1812
1813 // This logic will bring a new chart onscreen at roughly twice the true
1814 // paper scale equivalent. Note that first chart opened on application
1815 // startup (bOpenSpecified = true) will open at the config saved scale
1816 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1817 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1818 double equivalent_vp_scale =
1819 GetCanvasScaleFactor() / proposed_scale_onscreen;
1820 double new_scale_ppm =
1821 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1822 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1823 }
1824
1825 if (m_bFollow) { // bounds-check the scale only if in autofollow
1826 proposed_scale_onscreen =
1827 wxMin(proposed_scale_onscreen,
1828 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1829 GetCanvasWidth()));
1830 proposed_scale_onscreen =
1831 wxMax(proposed_scale_onscreen,
1832 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1834 }
1835
1836 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1837 }
1838
1839 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1840 m_singleChart->GetChartSkew() * PI / 180.,
1841 GetVPRotation());
1842 }
1843 } // new stack
1844
1845 else // No change in Chart Stack
1846 {
1847 if ((m_bFollow) && m_singleChart)
1848 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1849 m_singleChart->GetChartSkew() * PI / 180.,
1850 GetVPRotation());
1851 }
1852
1853update_finish:
1854
1855 // TODO
1856 // if( bNewPiano ) UpdateControlBar();
1857
1858 m_bFirstAuto = false; // Auto open on program start
1859
1860 // If we need a Refresh(), do it here...
1861 // But don't duplicate a Refresh() done by SetViewPoint()
1862 if (bNewChart && !bNewView) Refresh(false);
1863
1864#ifdef ocpnUSE_GL
1865 // If a new chart, need to invalidate gl viewport for refresh
1866 // so the fbo gets flushed
1867 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1868#endif
1869
1870 return bNewChart | bNewView;
1871}
1872
1873void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1874 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1875
1876 SetQuiltRefChart(db_index);
1877 if (ChartData) {
1878 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1879 if (pc) {
1880 if (b_autoscale) {
1881 double best_scale_ppm = GetBestVPScale(pc);
1882 SetVPScale(best_scale_ppm);
1883 }
1884 } else
1885 SetQuiltRefChart(-1);
1886 } else
1887 SetQuiltRefChart(-1);
1888}
1889
1890void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1891 std::vector<int> piano_chart_index_array =
1892 GetQuiltExtendedStackdbIndexArray();
1893 int current_db_index = piano_chart_index_array[selected_index];
1894
1895 SelectQuiltRefdbChart(current_db_index);
1896}
1897
1898double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1899 if (pchart) {
1900 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1901
1902 if ((g_bPreserveScaleOnX) ||
1903 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1904 double new_scale_ppm = GetVPScale();
1905 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1906 } else {
1907 // This logic will bring the new chart onscreen at roughly twice the true
1908 // paper scale equivalent.
1909 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
1910 double equivalent_vp_scale =
1911 GetCanvasScaleFactor() / proposed_scale_onscreen;
1912 double new_scale_ppm =
1913 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1914 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1915 }
1916
1917 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
1918 // set. Otherwise, we get severe performance problems on all platforms
1919
1920 double max_underzoom_multiplier = 2.0;
1921 if (GetVP().b_quilt) {
1922 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
1923 pchart->GetChartType(),
1924 pchart->GetChartFamily());
1925 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
1926 }
1927
1928 proposed_scale_onscreen = wxMin(
1929 proposed_scale_onscreen,
1930 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
1931 max_underzoom_multiplier);
1932
1933 // And, do not allow excessive overzoom either
1934 proposed_scale_onscreen =
1935 wxMax(proposed_scale_onscreen,
1936 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
1937
1938 return GetCanvasScaleFactor() / proposed_scale_onscreen;
1939 } else
1940 return 1.0;
1941}
1942
1943void ChartCanvas::SetupCanvasQuiltMode() {
1944 if (GetQuiltMode()) // going to quilt mode
1945 {
1946 ChartData->LockCache();
1947
1948 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
1949
1950 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1951
1952 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1953 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1954 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1955 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1956
1957 m_Piano->SetRoundedRectangles(true);
1958
1959 // Select the proper Ref chart
1960 int target_new_dbindex = -1;
1961 if (m_pCurrentStack) {
1962 target_new_dbindex =
1963 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
1964
1965 if (-1 != target_new_dbindex) {
1966 if (!IsChartQuiltableRef(target_new_dbindex)) {
1967 int proj = ChartData->GetDBChartProj(target_new_dbindex);
1968 int type = ChartData->GetDBChartType(target_new_dbindex);
1969
1970 // walk the stack up looking for a satisfactory chart
1971 int stack_index = m_pCurrentStack->CurrentStackEntry;
1972
1973 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
1974 (stack_index >= 0)) {
1975 int proj_tent = ChartData->GetDBChartProj(
1976 m_pCurrentStack->GetDBIndex(stack_index));
1977 int type_tent = ChartData->GetDBChartType(
1978 m_pCurrentStack->GetDBIndex(stack_index));
1979
1980 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
1981 if ((proj == proj_tent) && (type_tent == type)) {
1982 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
1983 break;
1984 }
1985 }
1986 stack_index++;
1987 }
1988 }
1989 }
1990 }
1991
1992 if (IsChartQuiltableRef(target_new_dbindex))
1993 SelectQuiltRefdbChart(target_new_dbindex,
1994 false); // Try not to allow a scale change
1995 else
1996 SelectQuiltRefdbChart(-1, false);
1997
1998 m_singleChart = NULL; // Bye....
1999
2000 // Re-qualify the quilt reference chart selection
2001 AdjustQuiltRefChart();
2002
2003 // Restore projection type saved on last quilt mode toggle
2004 // TODO
2005 // if(g_sticky_projection != -1)
2006 // GetVP().SetProjectionType(g_sticky_projection);
2007 // else
2008 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2009 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2010
2011 } else // going to SC Mode
2012 {
2013 std::vector<int> empty_array;
2014 m_Piano->SetActiveKeyArray(empty_array);
2015 m_Piano->SetNoshowIndexArray(empty_array);
2016 m_Piano->SetEclipsedIndexArray(empty_array);
2017
2018 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2019 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2020 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2021 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2022 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2023
2024 m_Piano->SetRoundedRectangles(false);
2025 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2026 }
2027
2028 // When shifting from quilt to single chart mode, select the "best" single
2029 // chart to show
2030 if (!GetQuiltMode()) {
2031 if (ChartData && ChartData->IsValid()) {
2032 UnlockQuilt();
2033
2034 double tLat, tLon;
2035 if (m_bFollow == true) {
2036 tLat = gLat;
2037 tLon = gLon;
2038 } else {
2039 tLat = m_vLat;
2040 tLon = m_vLon;
2041 }
2042
2043 if (!m_singleChart) {
2044 // Build a temporary chart stack based on tLat, tLon
2045 ChartStack TempStack;
2046 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2047 m_groupIndex);
2048
2049 // Iterate over the quilt charts actually shown, looking for the
2050 // largest scale chart that will be in the new chartstack.... This
2051 // will (almost?) always be the reference chart....
2052
2053 ChartBase *Candidate_Chart = NULL;
2054 int cur_max_scale = (int)1e8;
2055
2056 ChartBase *pChart = GetFirstQuiltChart();
2057 while (pChart) {
2058 // Is this pChart in new stack?
2059 int tEntry =
2060 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2061 if (tEntry != -1) {
2062 if (pChart->GetNativeScale() < cur_max_scale) {
2063 Candidate_Chart = pChart;
2064 cur_max_scale = pChart->GetNativeScale();
2065 }
2066 }
2067 pChart = GetNextQuiltChart();
2068 }
2069
2070 m_singleChart = Candidate_Chart;
2071
2072 // If the quilt is empty, there is no "best" chart.
2073 // So, open the smallest scale chart in the current stack
2074 if (NULL == m_singleChart) {
2075 m_singleChart = ChartData->OpenStackChartConditional(
2076 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2077 CHART_FAMILY_DONTCARE);
2078 }
2079 }
2080
2081 // Invalidate all the charts in the quilt,
2082 // as any cached data may be region based and not have fullscreen coverage
2083 InvalidateAllQuiltPatchs();
2084
2085 if (m_singleChart) {
2086 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2087 std::vector<int> one_array;
2088 one_array.push_back(dbi);
2089 m_Piano->SetActiveKeyArray(one_array);
2090 }
2091
2092 if (m_singleChart) {
2093 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2094 }
2095 }
2096 // Invalidate the current stack so that it will be rebuilt on next tick
2097 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2098 }
2099}
2100
2101bool ChartCanvas::IsTempMenuBarEnabled() {
2102#ifdef __WXMSW__
2103 int major;
2104 wxGetOsVersion(&major);
2105 return (major >
2106 5); // For Windows, function is only available on Vista and above
2107#else
2108 return true;
2109#endif
2110}
2111
2112double ChartCanvas::GetCanvasRangeMeters() {
2113 int width, height;
2114 GetSize(&width, &height);
2115 int minDimension = wxMin(width, height);
2116
2117 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2118 range *= cos(GetVP().clat * PI / 180.);
2119 return range;
2120}
2121
2122void ChartCanvas::SetCanvasRangeMeters(double range) {
2123 int width, height;
2124 GetSize(&width, &height);
2125 int minDimension = wxMin(width, height);
2126
2127 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2128 SetVPScale(scale_ppm / 2);
2129}
2130
2131bool ChartCanvas::SetUserOwnship() {
2132 // Look for user defined ownship image
2133 // This may be found in the shared data location along with other user
2134 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2135 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2136 double factor_dusk = 0.5;
2137 double factor_night = 0.25;
2138
2139 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2140 m_pos_image_user_day = new wxImage;
2141 *m_pos_image_user_day = pbmp->ConvertToImage();
2142 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2143
2144 int gimg_width = m_pos_image_user_day->GetWidth();
2145 int gimg_height = m_pos_image_user_day->GetHeight();
2146
2147 // Make dusk and night images
2148 m_pos_image_user_dusk = new wxImage;
2149 m_pos_image_user_night = new wxImage;
2150
2151 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2152 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2153
2154 for (int iy = 0; iy < gimg_height; iy++) {
2155 for (int ix = 0; ix < gimg_width; ix++) {
2156 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2157 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2158 m_pos_image_user_day->GetGreen(ix, iy),
2159 m_pos_image_user_day->GetBlue(ix, iy));
2160 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2161 hsv.value = hsv.value * factor_dusk;
2162 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2163 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2164 nrgb.blue);
2165
2166 hsv = wxImage::RGBtoHSV(rgb);
2167 hsv.value = hsv.value * factor_night;
2168 nrgb = wxImage::HSVtoRGB(hsv);
2169 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2170 nrgb.blue);
2171 }
2172 }
2173 }
2174
2175 // Make some alternate greyed out day/dusk/night images
2176 m_pos_image_user_grey_day = new wxImage;
2177 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2178
2179 m_pos_image_user_grey_dusk = new wxImage;
2180 m_pos_image_user_grey_night = new wxImage;
2181
2182 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2183 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2184
2185 for (int iy = 0; iy < gimg_height; iy++) {
2186 for (int ix = 0; ix < gimg_width; ix++) {
2187 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2188 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2189 m_pos_image_user_grey_day->GetGreen(ix, iy),
2190 m_pos_image_user_grey_day->GetBlue(ix, iy));
2191 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2192 hsv.value = hsv.value * factor_dusk;
2193 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2194 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2195 nrgb.blue);
2196
2197 hsv = wxImage::RGBtoHSV(rgb);
2198 hsv.value = hsv.value * factor_night;
2199 nrgb = wxImage::HSVtoRGB(hsv);
2200 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2201 nrgb.blue);
2202 }
2203 }
2204 }
2205
2206 // Make a yellow image for rendering under low accuracy chart conditions
2207 m_pos_image_user_yellow_day = new wxImage;
2208 m_pos_image_user_yellow_dusk = new wxImage;
2209 m_pos_image_user_yellow_night = new wxImage;
2210
2211 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2212 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2213 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2214
2215 for (int iy = 0; iy < gimg_height; iy++) {
2216 for (int ix = 0; ix < gimg_width; ix++) {
2217 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2218 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2219 m_pos_image_user_grey_day->GetGreen(ix, iy),
2220 m_pos_image_user_grey_day->GetBlue(ix, iy));
2221
2222 // Simply remove all "blue" from the greyscaled image...
2223 // so, what is not black becomes yellow.
2224 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2225 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2226 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2227
2228 hsv = wxImage::RGBtoHSV(rgb);
2229 hsv.value = hsv.value * factor_dusk;
2230 nrgb = wxImage::HSVtoRGB(hsv);
2231 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2232
2233 hsv = wxImage::RGBtoHSV(rgb);
2234 hsv.value = hsv.value * factor_night;
2235 nrgb = wxImage::HSVtoRGB(hsv);
2236 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2237 0);
2238 }
2239 }
2240 }
2241
2242 return true;
2243 } else
2244 return false;
2245}
2246
2248 m_display_size_mm = size;
2249
2250 // int sx, sy;
2251 // wxDisplaySize( &sx, &sy );
2252
2253 // Calculate logical pixels per mm for later reference.
2254 wxSize sd = g_Platform->getDisplaySize();
2255 double horizontal = sd.x;
2256 // Set DPI (Win) scale factor
2257 g_scaler = g_Platform->GetDisplayDIPMult(this);
2258
2259 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2260 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2261
2262 if (ps52plib) {
2263 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2264 ps52plib->SetPPMM(m_pix_per_mm);
2265 }
2266
2267 wxString msg;
2268 msg.Printf(
2269 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2270 "%d:%d ",
2271 m_display_size_mm, sd.x, sd.y);
2272 wxLogMessage(msg);
2273
2274 int ssx, ssy;
2275 ssx = g_monitor_info[g_current_monitor].width;
2276 ssy = g_monitor_info[g_current_monitor].height;
2277 msg.Printf("monitor size: %d %d", ssx, ssy);
2278 wxLogMessage(msg);
2279
2280 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2281}
2282#if 0
2283void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2284{
2285 wxString msg(event.m_string.c_str(), wxConvUTF8);
2286 // if cpus are removed between runs
2287 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2288 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2289 }
2290
2291 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2292 {
2293 compress_msg_array.RemoveAt(event.thread);
2294 compress_msg_array.Insert( msg, event.thread);
2295 }
2296 else
2297 compress_msg_array.Add(msg);
2298
2299
2300 wxString combined_msg;
2301 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2302 combined_msg += compress_msg_array[i];
2303 combined_msg += "\n";
2304 }
2305
2306 bool skip = false;
2307 pprog->Update(pprog_count, combined_msg, &skip );
2308 pprog->SetSize(pprog_size);
2309 if(skip)
2310 b_skipout = skip;
2311}
2312#endif
2313void ChartCanvas::InvalidateGL() {
2314 if (!m_glcc) return;
2315#ifdef ocpnUSE_GL
2316 if (g_bopengl) m_glcc->Invalidate();
2317#endif
2318 if (m_Compass) m_Compass->UpdateStatus(true);
2319}
2320
2321int ChartCanvas::GetCanvasChartNativeScale() {
2322 int ret = 1;
2323 if (!VPoint.b_quilt) {
2324 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2325 } else
2326 ret = (int)m_pQuilt->GetRefNativeScale();
2327
2328 return ret;
2329}
2330
2331ChartBase *ChartCanvas::GetChartAtCursor() {
2332 ChartBase *target_chart;
2333 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2334 target_chart = m_singleChart;
2335 else if (VPoint.b_quilt)
2336 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2337 else
2338 target_chart = NULL;
2339 return target_chart;
2340}
2341
2342ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2343 ChartBase *target_chart;
2344 if (VPoint.b_quilt)
2345 target_chart =
2346 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2347 else
2348 target_chart = NULL;
2349 return target_chart;
2350}
2351
2352int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2353 int new_dbIndex = -1;
2354 if (!VPoint.b_quilt) {
2355 if (m_pCurrentStack) {
2356 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2357 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2358 if (sc >= scale) {
2359 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2360 break;
2361 }
2362 }
2363 }
2364 } else {
2365 // Using the current quilt, select a useable reference chart
2366 // Said chart will be in the extended (possibly full-screen) stack,
2367 // And will have a scale equal to or just greater than the stipulated
2368 // value
2369 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2370 if (im > 0) {
2371 for (unsigned int is = 0; is < im; is++) {
2372 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2373 m_pQuilt->GetExtendedStackIndexArray()[is]);
2374 if ((m.Scale_ge(
2375 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2376 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2377 break;
2378 }
2379 }
2380 }
2381 }
2382
2383 return new_dbIndex;
2384}
2385
2386void ChartCanvas::EnablePaint(bool b_enable) {
2387 m_b_paint_enable = b_enable;
2388#ifdef ocpnUSE_GL
2389 if (m_glcc) m_glcc->EnablePaint(b_enable);
2390#endif
2391}
2392
2393bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2394
2395void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2396
2397std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2398 return m_pQuilt->GetQuiltIndexArray();
2399 ;
2400}
2401
2402void ChartCanvas::SetQuiltMode(bool b_quilt) {
2403 VPoint.b_quilt = b_quilt;
2404 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2405}
2406
2407bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2408
2409int ChartCanvas::GetQuiltReferenceChartIndex() {
2410 return m_pQuilt->GetRefChartdbIndex();
2411}
2412
2413void ChartCanvas::InvalidateAllQuiltPatchs() {
2414 m_pQuilt->InvalidateAllQuiltPatchs();
2415}
2416
2417ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2418 return m_pQuilt->GetLargestScaleChart();
2419}
2420
2421ChartBase *ChartCanvas::GetFirstQuiltChart() {
2422 return m_pQuilt->GetFirstChart();
2423}
2424
2425ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2426
2427int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2428
2429void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2430 m_pQuilt->SetHiliteIndex(dbIndex);
2431}
2432
2433void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2434 m_pQuilt->SetHiliteIndexArray(hilite_array);
2435}
2436
2437void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2438 m_pQuilt->ClearHiliteIndexArray();
2439}
2440
2441std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2442 bool flag2) {
2443 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2444}
2445
2446int ChartCanvas::GetQuiltRefChartdbIndex() {
2447 return m_pQuilt->GetRefChartdbIndex();
2448}
2449
2450std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2451 return m_pQuilt->GetExtendedStackIndexArray();
2452}
2453
2454std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2455 return m_pQuilt->GetFullscreenIndexArray();
2456}
2457
2458std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2459 return m_pQuilt->GetEclipsedStackIndexArray();
2460}
2461
2462void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2463
2464double ChartCanvas::GetQuiltMaxErrorFactor() {
2465 return m_pQuilt->GetMaxErrorFactor();
2466}
2467
2468bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2469 return m_pQuilt->IsChartQuiltableRef(db_index);
2470}
2471
2472bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2473 double chartMaxScale =
2474 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2475 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2476}
2477
2478void ChartCanvas::StartMeasureRoute() {
2479 if (!m_routeState) { // no measure tool if currently creating route
2480 if (m_bMeasure_Active) {
2481 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2482 m_pMeasureRoute = NULL;
2483 }
2484
2485 m_bMeasure_Active = true;
2486 m_nMeasureState = 1;
2487 m_bDrawingRoute = false;
2488
2489 SetCursor(*pCursorPencil);
2490 Refresh();
2491 }
2492}
2493
2494void ChartCanvas::CancelMeasureRoute() {
2495 m_bMeasure_Active = false;
2496 m_nMeasureState = 0;
2497 m_bDrawingRoute = false;
2498
2499 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2500 m_pMeasureRoute = NULL;
2501
2502 SetCursor(*pCursorArrow);
2503}
2504
2505ViewPort &ChartCanvas::GetVP() { return VPoint; }
2506
2507void ChartCanvas::SetVP(ViewPort &vp) {
2508 VPoint = vp;
2509 VPoint.SetPixelScale(m_displayScale);
2510}
2511
2512// void ChartCanvas::SetFocus()
2513// {
2514// printf("set %d\n", m_canvasIndex);
2515// //wxWindow:SetFocus();
2516// }
2517
2518void ChartCanvas::TriggerDeferredFocus() {
2519 // #if defined(__WXGTK__) || defined(__WXOSX__)
2520
2521 m_deferredFocusTimer.Start(20, true);
2522
2523#if defined(__WXGTK__) || defined(__WXOSX__)
2524 gFrame->Raise();
2525#endif
2526
2527 // gFrame->Raise();
2528 // #else
2529 // SetFocus();
2530 // Refresh(true);
2531 // #endif
2532}
2533
2534void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2535 SetFocus();
2536 Refresh(true);
2537}
2538
2539void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2540 if (SendKeyEventToPlugins(event))
2541 return; // PlugIn did something, and does not want the canvas to do
2542 // anything else
2543
2544 int key_char = event.GetKeyCode();
2545 switch (key_char) {
2546 case '?':
2547 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2548 break;
2549 case '+':
2550 ZoomCanvas(g_plus_minus_zoom_factor, false);
2551 break;
2552 case '-':
2553 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2554 break;
2555 default:
2556 break;
2557 }
2558 if (g_benable_rotate) {
2559 switch (key_char) {
2560 case ']':
2561 RotateCanvas(1);
2562 Refresh();
2563 break;
2564
2565 case '[':
2566 RotateCanvas(-1);
2567 Refresh();
2568 break;
2569
2570 case '\\':
2571 DoRotateCanvas(0);
2572 break;
2573 }
2574 }
2575
2576 event.Skip();
2577}
2578
2579void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2580 if (SendKeyEventToPlugins(event))
2581 return; // PlugIn did something, and does not want the canvas to do
2582 // anything else
2583
2584 bool b_handled = false;
2585
2586 m_modkeys = event.GetModifiers();
2587
2588 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2589
2590#ifdef OCPN_ALT_MENUBAR
2591#ifndef __WXOSX__
2592 // If the permanent menubar is disabled, we show it temporarily when Alt is
2593 // pressed or when Alt + a letter is presssed (for the top-menu-level
2594 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2595 // some special cases.
2596 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2597 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2598 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2599 if (!g_bTempShowMenuBar) {
2600 g_bTempShowMenuBar = true;
2601 parent_frame->ApplyGlobalSettings(false);
2602 }
2603 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2604 event.Skip();
2605 return;
2606 }
2607 // If another key is pressed while Alt is down, do NOT toggle the menus when
2608 // Alt is released
2609 if (event.GetKeyCode() != WXK_ALT) {
2610 m_bMayToggleMenuBar = false;
2611 }
2612 }
2613#endif
2614#endif
2615
2616 // HOTKEYS
2617 switch (event.GetKeyCode()) {
2618 case WXK_TAB:
2619 // parent_frame->SwitchKBFocus( this );
2620 break;
2621
2622 case WXK_MENU:
2623 int x, y;
2624 event.GetPosition(&x, &y);
2625 m_FinishRouteOnKillFocus = false;
2626 CallPopupMenu(x, y);
2627 m_FinishRouteOnKillFocus = true;
2628 break;
2629
2630 case WXK_ALT:
2631 m_modkeys |= wxMOD_ALT;
2632 break;
2633
2634 case WXK_CONTROL:
2635 m_modkeys |= wxMOD_CONTROL;
2636 break;
2637
2638#ifdef __WXOSX__
2639 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2640 case WXK_RAW_CONTROL:
2641 m_modkeys |= wxMOD_RAW_CONTROL;
2642 break;
2643#endif
2644
2645 case WXK_LEFT:
2646 if (m_modkeys == wxMOD_CONTROL)
2647 parent_frame->DoStackDown(this);
2648 else if (g_bsmoothpanzoom) {
2649 StartTimedMovement();
2650 m_panx = -1;
2651 } else {
2652 PanCanvas(-panspeed, 0);
2653 }
2654 b_handled = true;
2655 break;
2656
2657 case WXK_UP:
2658 if (g_bsmoothpanzoom) {
2659 StartTimedMovement();
2660 m_pany = -1;
2661 } else
2662 PanCanvas(0, -panspeed);
2663 b_handled = true;
2664 break;
2665
2666 case WXK_RIGHT:
2667 if (m_modkeys == wxMOD_CONTROL)
2668 parent_frame->DoStackUp(this);
2669 else if (g_bsmoothpanzoom) {
2670 StartTimedMovement();
2671 m_panx = 1;
2672 } else
2673 PanCanvas(panspeed, 0);
2674 b_handled = true;
2675
2676 break;
2677
2678 case WXK_DOWN:
2679 if (g_bsmoothpanzoom) {
2680 StartTimedMovement();
2681 m_pany = 1;
2682 } else
2683 PanCanvas(0, panspeed);
2684 b_handled = true;
2685 break;
2686
2687 case WXK_F2:
2688 TogglebFollow();
2689 break;
2690
2691 case WXK_F3: {
2692 SetShowENCText(!GetShowENCText());
2693 Refresh(true);
2694 InvalidateGL();
2695 break;
2696 }
2697 case WXK_F4:
2698 if (!m_bMeasure_Active) {
2699 if (event.ShiftDown())
2700 m_bMeasure_DistCircle = true;
2701 else
2702 m_bMeasure_DistCircle = false;
2703
2704 StartMeasureRoute();
2705 } else {
2706 CancelMeasureRoute();
2707
2708 SetCursor(*pCursorArrow);
2709
2710 // SurfaceToolbar();
2711 InvalidateGL();
2712 Refresh(false);
2713 }
2714
2715 break;
2716
2717 case WXK_F5:
2718 parent_frame->ToggleColorScheme();
2719 gFrame->Raise();
2720 TriggerDeferredFocus();
2721 break;
2722
2723 case WXK_F6: {
2724 int mod = m_modkeys & wxMOD_SHIFT;
2725 if (mod != m_brightmod) {
2726 m_brightmod = mod;
2727 m_bbrightdir = !m_bbrightdir;
2728 }
2729
2730 if (!m_bbrightdir) {
2731 g_nbrightness -= 10;
2732 if (g_nbrightness <= MIN_BRIGHT) {
2733 g_nbrightness = MIN_BRIGHT;
2734 m_bbrightdir = true;
2735 }
2736 } else {
2737 g_nbrightness += 10;
2738 if (g_nbrightness >= MAX_BRIGHT) {
2739 g_nbrightness = MAX_BRIGHT;
2740 m_bbrightdir = false;
2741 }
2742 }
2743
2744 SetScreenBrightness(g_nbrightness);
2745 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2746
2747 SetFocus(); // just in case the external program steals it....
2748 gFrame->Raise(); // And reactivate the application main
2749
2750 break;
2751 }
2752
2753 case WXK_F7:
2754 parent_frame->DoStackDown(this);
2755 break;
2756
2757 case WXK_F8:
2758 parent_frame->DoStackUp(this);
2759 break;
2760
2761#ifndef __WXOSX__
2762 case WXK_F9: {
2763 ToggleCanvasQuiltMode();
2764 break;
2765 }
2766#endif
2767
2768 case WXK_F11:
2769 parent_frame->ToggleFullScreen();
2770 b_handled = true;
2771 break;
2772
2773 case WXK_F12: {
2774 if (m_modkeys == wxMOD_ALT) {
2775 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2776 } else {
2777 ToggleChartOutlines();
2778 }
2779 break;
2780 }
2781
2782 case WXK_PAUSE: // Drop MOB
2783 parent_frame->ActivateMOB();
2784 break;
2785
2786 // NUMERIC PAD
2787 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2788 case WXK_PAGEUP: {
2789 ZoomCanvas(g_plus_minus_zoom_factor, false);
2790 break;
2791 }
2792 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2793 case WXK_PAGEDOWN: {
2794 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2795 break;
2796 }
2797 case WXK_DELETE:
2798 case WXK_BACK:
2799 if (m_bMeasure_Active) {
2800 if (m_nMeasureState > 2) {
2801 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2802 m_pMeasureRoute->m_lastMousePointIndex =
2803 m_pMeasureRoute->GetnPoints();
2804 m_nMeasureState--;
2805 gFrame->RefreshAllCanvas();
2806 } else {
2807 CancelMeasureRoute();
2808 StartMeasureRoute();
2809 }
2810 }
2811 break;
2812 default:
2813 break;
2814 }
2815
2816 if (event.GetKeyCode() < 128) // ascii
2817 {
2818 int key_char = event.GetKeyCode();
2819
2820 // Handle both QWERTY and AZERTY keyboard separately for a few control
2821 // codes
2822 if (!g_b_assume_azerty) {
2823#ifdef __WXMAC__
2824 if (g_benable_rotate) {
2825 switch (key_char) {
2826 // On other platforms these are handled in OnKeyChar, which
2827 // (apparently) works better in some locales. On OS X it is better
2828 // to handle them here, since pressing Alt (which should change the
2829 // rotation speed) changes the key char and so prevents the keys
2830 // from working.
2831 case ']':
2832 RotateCanvas(1);
2833 b_handled = true;
2834 break;
2835
2836 case '[':
2837 RotateCanvas(-1);
2838 b_handled = true;
2839 break;
2840
2841 case '\\':
2842 DoRotateCanvas(0);
2843 b_handled = true;
2844 break;
2845 }
2846 }
2847#endif
2848 } else { // AZERTY
2849 switch (key_char) {
2850 case 43:
2851 ZoomCanvas(g_plus_minus_zoom_factor, false);
2852 break;
2853
2854 case 54: // '-' alpha/num pad
2855 // case 56: // '_' alpha/num pad
2856 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2857 break;
2858 }
2859 }
2860
2861#ifdef __WXOSX__
2862 // Ctrl+Cmd+F toggles fullscreen on macOS
2863 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2864 m_modkeys & wxMOD_RAW_CONTROL) {
2865 parent_frame->ToggleFullScreen();
2866 return;
2867 }
2868#endif
2869
2870 if (event.ControlDown()) key_char -= 64;
2871
2872 if (key_char >= '0' && key_char <= '9')
2873 SetGroupIndex(key_char - '0');
2874 else
2875
2876 switch (key_char) {
2877 case 'A':
2878 SetShowENCAnchor(!GetShowENCAnchor());
2879 ReloadVP();
2880
2881 break;
2882
2883 case 'C':
2884 parent_frame->ToggleColorScheme();
2885 break;
2886
2887 case 'D': {
2888 int x, y;
2889 event.GetPosition(&x, &y);
2890 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
2891 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
2892 // First find out what kind of chart is being used
2893 if (!pPopupDetailSlider) {
2894 if (VPoint.b_quilt) {
2895 if (m_pQuilt) {
2896 if (m_pQuilt->GetChartAtPix(
2897 VPoint,
2898 wxPoint(
2899 x, y))) // = null if no chart loaded for this point
2900 {
2901 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2902 ->GetChartType();
2903 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
2904 ->GetChartFamily();
2905 }
2906 }
2907 } else {
2908 if (m_singleChart) {
2909 ChartType = m_singleChart->GetChartType();
2910 ChartFam = m_singleChart->GetChartFamily();
2911 }
2912 }
2913 // If a charttype is found show the popupslider
2914 if ((ChartType != CHART_TYPE_UNKNOWN) ||
2915 (ChartFam != CHART_FAMILY_UNKNOWN)) {
2917 this, -1, ChartType, ChartFam,
2918 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
2919 wxDefaultSize, wxSIMPLE_BORDER, "");
2921 }
2922 } else //( !pPopupDetailSlider ) close popupslider
2923 {
2925 pPopupDetailSlider = NULL;
2926 }
2927 break;
2928 }
2929
2930 case 'E':
2931 m_nmea_log->Show();
2932 m_nmea_log->Raise();
2933 break;
2934
2935 case 'L':
2936 SetShowENCLights(!GetShowENCLights());
2937 ReloadVP();
2938
2939 break;
2940
2941 case 'M':
2942 if (event.ShiftDown())
2943 m_bMeasure_DistCircle = true;
2944 else
2945 m_bMeasure_DistCircle = false;
2946
2947 StartMeasureRoute();
2948 break;
2949
2950 case 'N':
2951 if (g_bInlandEcdis && ps52plib) {
2952 SetENCDisplayCategory((_DisCat)STANDARD);
2953 }
2954 break;
2955
2956 case 'O':
2957 ToggleChartOutlines();
2958 break;
2959
2960 case 'Q':
2961 ToggleCanvasQuiltMode();
2962 break;
2963
2964 case 'P':
2965 parent_frame->ToggleTestPause();
2966 break;
2967 case 'R':
2968 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
2969 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
2970 g_iNavAidRadarRingsNumberVisible = 1;
2971 else if (!g_bNavAidRadarRingsShown &&
2972 g_iNavAidRadarRingsNumberVisible == 1)
2973 g_iNavAidRadarRingsNumberVisible = 0;
2974 break;
2975 case 'S':
2976 SetShowENCDepth(!m_encShowDepth);
2977 ReloadVP();
2978 break;
2979
2980 case 'T':
2981 SetShowENCText(!GetShowENCText());
2982 ReloadVP();
2983 break;
2984
2985 case 'U':
2986 SetShowENCDataQual(!GetShowENCDataQual());
2987 ReloadVP();
2988 break;
2989
2990 case 'V':
2991 m_bShowNavobjects = !m_bShowNavobjects;
2992 Refresh(true);
2993 break;
2994
2995 case 'W': // W Toggle CPA alarm
2996 ToggleCPAWarn();
2997
2998 break;
2999
3000 case 1: // Ctrl A
3001 TogglebFollow();
3002
3003 break;
3004
3005 case 2: // Ctrl B
3006 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3007 break;
3008
3009 case 13: // Ctrl M // Drop Marker at cursor
3010 {
3011 if (event.ControlDown()) gFrame->DropMarker(false);
3012 break;
3013 }
3014
3015 case 14: // Ctrl N - Activate next waypoint in a route
3016 {
3017 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3018 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3019 if ((indexActive + 1) <= r->GetnPoints()) {
3021 InvalidateGL();
3022 Refresh(false);
3023 }
3024 }
3025 break;
3026 }
3027
3028 case 15: // Ctrl O - Drop Marker at boat's position
3029 {
3030 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3031 break;
3032 }
3033
3034 case 32: // Special needs use space bar
3035 {
3036 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3037 break;
3038 }
3039
3040 case -32: // Ctrl Space // Drop MOB
3041 {
3042 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3043
3044 break;
3045 }
3046
3047 case -20: // Ctrl ,
3048 {
3049 parent_frame->DoSettings();
3050 break;
3051 }
3052 case 17: // Ctrl Q
3053 parent_frame->Close();
3054 return;
3055
3056 case 18: // Ctrl R
3057 StartRoute();
3058 return;
3059
3060 case 20: // Ctrl T
3061 if (NULL == pGoToPositionDialog) // There is one global instance of
3062 // the Go To Position Dialog
3064 pGoToPositionDialog->SetCanvas(this);
3065 pGoToPositionDialog->Show();
3066 break;
3067
3068 case 25: // Ctrl Y
3069 if (undo->AnythingToRedo()) {
3070 undo->RedoNextAction();
3071 InvalidateGL();
3072 Refresh(false);
3073 }
3074 break;
3075
3076 case 26:
3077 if (event.ShiftDown()) { // Shift-Ctrl-Z
3078 if (undo->AnythingToRedo()) {
3079 undo->RedoNextAction();
3080 InvalidateGL();
3081 Refresh(false);
3082 }
3083 } else { // Ctrl Z
3084 if (undo->AnythingToUndo()) {
3085 undo->UndoLastAction();
3086 InvalidateGL();
3087 Refresh(false);
3088 }
3089 }
3090 break;
3091
3092 case 27:
3093 // Generic break
3094 if (m_bMeasure_Active) {
3095 CancelMeasureRoute();
3096
3097 SetCursor(*pCursorArrow);
3098
3099 // SurfaceToolbar();
3100 gFrame->RefreshAllCanvas();
3101 }
3102
3103 if (m_routeState) // creating route?
3104 {
3105 FinishRoute();
3106 // SurfaceToolbar();
3107 InvalidateGL();
3108 Refresh(false);
3109 }
3110
3111 break;
3112
3113 case 7: // Ctrl G
3114 switch (gamma_state) {
3115 case (0):
3116 r_gamma_mult = 0;
3117 g_gamma_mult = 1;
3118 b_gamma_mult = 0;
3119 gamma_state = 1;
3120 break;
3121 case (1):
3122 r_gamma_mult = 1;
3123 g_gamma_mult = 0;
3124 b_gamma_mult = 0;
3125 gamma_state = 2;
3126 break;
3127 case (2):
3128 r_gamma_mult = 1;
3129 g_gamma_mult = 1;
3130 b_gamma_mult = 1;
3131 gamma_state = 0;
3132 break;
3133 }
3134 SetScreenBrightness(g_nbrightness);
3135
3136 break;
3137
3138 case 9: // Ctrl I
3139 if (event.ControlDown()) {
3140 m_bShowCompassWin = !m_bShowCompassWin;
3141 SetShowGPSCompassWindow(m_bShowCompassWin);
3142 Refresh(false);
3143 }
3144 break;
3145
3146 default:
3147 break;
3148
3149 } // switch
3150 }
3151
3152 // Allow OnKeyChar to catch the key events too.
3153 if (!b_handled) {
3154 event.Skip();
3155 }
3156}
3157
3158void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3159 if (SendKeyEventToPlugins(event))
3160 return; // PlugIn did something, and does not want the canvas to do
3161 // anything else
3162
3163 switch (event.GetKeyCode()) {
3164 case WXK_TAB:
3165 parent_frame->SwitchKBFocus(this);
3166 break;
3167
3168 case WXK_LEFT:
3169 case WXK_RIGHT:
3170 m_panx = 0;
3171 if (!m_pany) m_panspeed = 0;
3172 break;
3173
3174 case WXK_UP:
3175 case WXK_DOWN:
3176 m_pany = 0;
3177 if (!m_panx) m_panspeed = 0;
3178 break;
3179
3180 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3181 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3182 case WXK_PAGEUP:
3183 case WXK_PAGEDOWN:
3184 if (m_mustmove) DoMovement(m_mustmove);
3185
3186 m_zoom_factor = 1;
3187 break;
3188
3189 case WXK_ALT:
3190 m_modkeys &= ~wxMOD_ALT;
3191#ifdef OCPN_ALT_MENUBAR
3192#ifndef __WXOSX__
3193 // If the permanent menu bar is disabled, and we are not in the middle of
3194 // another key combo, then show the menu bar temporarily when Alt is
3195 // released (or hide it if already visible).
3196 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3197 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3198 parent_frame->ApplyGlobalSettings(false);
3199 }
3200 m_bMayToggleMenuBar = true;
3201#endif
3202#endif
3203 break;
3204
3205 case WXK_CONTROL:
3206 m_modkeys &= ~wxMOD_CONTROL;
3207 break;
3208 }
3209
3210 if (event.GetKeyCode() < 128) // ascii
3211 {
3212 int key_char = event.GetKeyCode();
3213
3214 // Handle both QWERTY and AZERTY keyboard separately for a few control
3215 // codes
3216 if (!g_b_assume_azerty) {
3217 switch (key_char) {
3218 case '+':
3219 case '=':
3220 case '-':
3221 case '_':
3222 case 54:
3223 case 56: // '_' alpha/num pad
3224 DoMovement(m_mustmove);
3225
3226 // m_zoom_factor = 1;
3227 break;
3228 case '[':
3229 case ']':
3230 DoMovement(m_mustmove);
3231 m_rotation_speed = 0;
3232 break;
3233 }
3234 } else {
3235 switch (key_char) {
3236 case 43:
3237 case 54: // '-' alpha/num pad
3238 case 56: // '_' alpha/num pad
3239 DoMovement(m_mustmove);
3240
3241 m_zoom_factor = 1;
3242 break;
3243 }
3244 }
3245 }
3246 event.Skip();
3247}
3248
3249void ChartCanvas::ToggleChartOutlines() {
3250 m_bShowOutlines = !m_bShowOutlines;
3251
3252 Refresh(false);
3253
3254#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3255 // needs a full refresh
3256 if (g_bopengl) InvalidateGL();
3257#endif
3258}
3259
3260void ChartCanvas::ToggleLookahead() {
3261 m_bLookAhead = !m_bLookAhead;
3262 m_OSoffsetx = 0; // center ownship
3263 m_OSoffsety = 0;
3264}
3265
3266void ChartCanvas::SetUpMode(int mode) {
3267 m_upMode = mode;
3268
3269 if (mode != NORTH_UP_MODE) {
3270 // Stuff the COGAvg table in case COGUp is selected
3271 double stuff = 0;
3272 if (!std::isnan(gCog)) stuff = gCog;
3273
3274 if (g_COGAvgSec > 0) {
3275 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3276 }
3277 g_COGAvg = stuff;
3278 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3279 } else {
3280 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3281 SetVPRotation(GetVPSkew());
3282 else
3283 SetVPRotation(0); /* reset to north up */
3284 }
3285
3286 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3287 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3288
3289 UpdateGPSCompassStatusBox(true);
3290 gFrame->DoChartUpdate();
3291}
3292
3293bool ChartCanvas::DoCanvasCOGSet() {
3294 if (GetUpMode() == NORTH_UP_MODE) return false;
3295 double cog_use = g_COGAvg;
3296 if (g_btenhertz) cog_use = gCog;
3297
3298 double rotation = 0;
3299 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3300 rotation = -gHdt * PI / 180.;
3301 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3302 rotation = -cog_use * PI / 180.;
3303
3304 SetVPRotation(rotation);
3305 return true;
3306}
3307
3308double easeOutCubic(double t) {
3309 // Starts quickly and slows down toward the end
3310 return 1.0 - pow(1.0 - t, 3.0);
3311}
3312
3313void ChartCanvas::StartChartDragInertia() {
3314 m_bChartDragging = false;
3315
3316 // Set some parameters
3317 m_chart_drag_inertia_time = 750; // msec
3318 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3319 m_last_elapsed = 0;
3320
3321 // Calculate ending drag velocity
3322 size_t n_vel = 10;
3323 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3324 int xacc = 0;
3325 int yacc = 0;
3326 double tacc = 0;
3327 size_t length = m_drag_vec_t.size();
3328 for (size_t i = 0; i < n_vel; i++) {
3329 xacc += m_drag_vec_x.at(length - 1 - i);
3330 yacc += m_drag_vec_y.at(length - 1 - i);
3331 tacc += m_drag_vec_t.at(length - 1 - i);
3332 }
3333
3334 if (tacc == 0) return;
3335
3336 m_chart_drag_velocity_x = xacc / tacc;
3337 m_chart_drag_velocity_y = yacc / tacc;
3338
3339 m_chart_drag_inertia_active = true;
3340 // First callback as fast as possible.
3341 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3342}
3343
3344void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3345 if (!m_chart_drag_inertia_active) return;
3346 // Calculate time fraction from 0..1
3347 wxLongLong now = wxGetLocalTimeMillis();
3348 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3349 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3350 if (t > 1.0) t = 1.0;
3351 double e = 1.0 - easeOutCubic(t); // 0..1
3352
3353 double dx =
3354 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3355 double dy =
3356 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3357
3358 m_last_elapsed = elapsed;
3359
3360 // Ensure that target destination lies on whole-pixel boundary
3361 // This allows the render engine to use a faster FBO copy method for drawing
3362 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3363 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3364 double inertia_lat, inertia_lon;
3365 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3366 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3367 // Check if ownship has moved off-screen
3368 if (!IsOwnshipOnScreen()) {
3369 m_bFollow = false; // update the follow flag
3370 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3371 UpdateFollowButtonState();
3372 m_OSoffsetx = 0;
3373 m_OSoffsety = 0;
3374 } else {
3375 m_OSoffsetx += dx;
3376 m_OSoffsety -= dy;
3377 }
3378
3379 Refresh(false);
3380
3381 // Stop condition
3382 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3383 m_chart_drag_inertia_timer.Stop();
3384
3385 // Disable chart pan movement logic
3386 m_target_lat = GetVP().clat;
3387 m_target_lon = GetVP().clon;
3388 m_pan_drag.x = m_pan_drag.y = 0;
3389 m_panx = m_pany = 0;
3390 m_chart_drag_inertia_active = false;
3391 DoCanvasUpdate();
3392
3393 } else {
3394 int target_redraw_interval = 40; // msec
3395 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3396 }
3397}
3398
3399void ChartCanvas::StopMovement() {
3400 m_panx = m_pany = 0;
3401 m_panspeed = 0;
3402 m_zoom_factor = 1;
3403 m_rotation_speed = 0;
3404 m_mustmove = 0;
3405#if 0
3406#if !defined(__WXGTK__) && !defined(__WXQT__)
3407 SetFocus();
3408 gFrame->Raise();
3409#endif
3410#endif
3411}
3412
3413/* instead of integrating in timer callbacks
3414 (which do not always get called fast enough)
3415 we can perform the integration of movement
3416 at each render frame based on the time change */
3417bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3418 // Start/restart the stop movement timer
3419 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3420
3421 if (!pMovementTimer->IsRunning()) {
3422 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3423 }
3424
3425 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3426 // already moving, gets called again because of key-repeat event
3427 return false;
3428 }
3429
3430 m_last_movement_time = wxDateTime::UNow();
3431
3432 return true;
3433}
3434void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3435 int nstep) {
3436 // Save the target
3437 m_target_lat = target_lat;
3438 m_target_lon = target_lon;
3439
3440 // Save the start point
3441 m_start_lat = GetVP().clat;
3442 m_start_lon = GetVP().clon;
3443
3444 m_VPMovementTimer.Start(1, true); // oneshot
3445 m_timed_move_vp_active = true;
3446 m_stvpc = 0;
3447 m_timedVP_step = nstep;
3448}
3449
3450void ChartCanvas::DoTimedMovementVP() {
3451 if (!m_timed_move_vp_active) return; // not active
3452 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3453 StopMovement();
3454 return;
3455 }
3456 // Stop condition
3457 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3458 double d2 =
3459 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3460 d2 = pow(d2, 0.5);
3461
3462 if (d2 < one_pix) {
3463 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3464 StopMovementVP();
3465 return;
3466 }
3467
3468 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3469 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3470 // StopMovementVP();
3471 // return;
3472 // }
3473
3474 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3475 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3476
3477 m_run_lat = new_lat;
3478 m_run_lon = new_lon;
3479
3480 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3481}
3482
3483void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3484
3485void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3486
3487void ChartCanvas::StartTimedMovementTarget() {}
3488
3489void ChartCanvas::DoTimedMovementTarget() {}
3490
3491void ChartCanvas::StopMovementTarget() {}
3492int ntm;
3493
3494void ChartCanvas::DoTimedMovement() {
3495 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3496 !m_rotation_speed)
3497 return; /* not moving */
3498
3499 wxDateTime now = wxDateTime::UNow();
3500 long dt = 0;
3501 if (m_last_movement_time.IsValid())
3502 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3503
3504 m_last_movement_time = now;
3505
3506 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3507 dt = 500;
3508
3509 DoMovement(dt);
3510}
3511
3513 /* if we get here quickly assume 1ms so that some movement occurs */
3514 if (dt == 0) dt = 1;
3515
3516 m_mustmove -= dt;
3517 if (m_mustmove < 0) m_mustmove = 0;
3518
3519 if (!m_inPinch) { // this stops compound zoom/pan
3520 if (m_pan_drag.x || m_pan_drag.y) {
3521 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3522 m_pan_drag.x = m_pan_drag.y = 0;
3523 }
3524
3525 if (m_panx || m_pany) {
3526 const double slowpan = .1, maxpan = 2;
3527 if (m_modkeys == wxMOD_ALT)
3528 m_panspeed = slowpan;
3529 else {
3530 m_panspeed += (double)dt / 500; /* apply acceleration */
3531 m_panspeed = wxMin(maxpan, m_panspeed);
3532 }
3533 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3534 }
3535 }
3536 if (m_zoom_factor != 1) {
3537 double alpha = 400, beta = 1.5;
3538 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3539
3540 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3541
3542 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3543
3544 // Try to hit the zoom target exactly.
3545 // if(m_wheelzoom_stop_oneshot > 0)
3546 {
3547 if (zoom_factor > 1) {
3548 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3549 zoom_factor = VPoint.chart_scale / m_zoom_target;
3550 }
3551
3552 else if (zoom_factor < 1) {
3553 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3554 zoom_factor = VPoint.chart_scale / m_zoom_target;
3555 }
3556 }
3557
3558 if (fabs(zoom_factor - 1) > 1e-4) {
3559 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3560 } else {
3561 StopMovement();
3562 }
3563
3564 if (m_wheelzoom_stop_oneshot > 0) {
3565 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3566 m_wheelzoom_stop_oneshot = 0;
3567 StopMovement();
3568 }
3569
3570 // Don't overshoot the zoom target.
3571 if (zoom_factor > 1) {
3572 if (VPoint.chart_scale <= m_zoom_target) {
3573 m_wheelzoom_stop_oneshot = 0;
3574 StopMovement();
3575 }
3576 } else if (zoom_factor < 1) {
3577 if (VPoint.chart_scale >= m_zoom_target) {
3578 m_wheelzoom_stop_oneshot = 0;
3579 StopMovement();
3580 }
3581 }
3582 }
3583 }
3584
3585 if (m_rotation_speed) { /* in degrees per second */
3586 double speed = m_rotation_speed;
3587 if (m_modkeys == wxMOD_ALT) speed /= 10;
3588 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3589 }
3590}
3591
3592void ChartCanvas::SetColorScheme(ColorScheme cs) {
3593 SetAlertString("");
3594
3595 // Setup ownship image pointers
3596 switch (cs) {
3597 case GLOBAL_COLOR_SCHEME_DAY:
3598 m_pos_image_red = &m_os_image_red_day;
3599 m_pos_image_grey = &m_os_image_grey_day;
3600 m_pos_image_yellow = &m_os_image_yellow_day;
3601 m_pos_image_user = m_pos_image_user_day;
3602 m_pos_image_user_grey = m_pos_image_user_grey_day;
3603 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3604 m_cTideBitmap = m_bmTideDay;
3605 m_cCurrentBitmap = m_bmCurrentDay;
3606
3607 break;
3608 case GLOBAL_COLOR_SCHEME_DUSK:
3609 m_pos_image_red = &m_os_image_red_dusk;
3610 m_pos_image_grey = &m_os_image_grey_dusk;
3611 m_pos_image_yellow = &m_os_image_yellow_dusk;
3612 m_pos_image_user = m_pos_image_user_dusk;
3613 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3614 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3615 m_cTideBitmap = m_bmTideDusk;
3616 m_cCurrentBitmap = m_bmCurrentDusk;
3617 break;
3618 case GLOBAL_COLOR_SCHEME_NIGHT:
3619 m_pos_image_red = &m_os_image_red_night;
3620 m_pos_image_grey = &m_os_image_grey_night;
3621 m_pos_image_yellow = &m_os_image_yellow_night;
3622 m_pos_image_user = m_pos_image_user_night;
3623 m_pos_image_user_grey = m_pos_image_user_grey_night;
3624 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3625 m_cTideBitmap = m_bmTideNight;
3626 m_cCurrentBitmap = m_bmCurrentNight;
3627 break;
3628 default:
3629 m_pos_image_red = &m_os_image_red_day;
3630 m_pos_image_grey = &m_os_image_grey_day;
3631 m_pos_image_yellow = &m_os_image_yellow_day;
3632 m_pos_image_user = m_pos_image_user_day;
3633 m_pos_image_user_grey = m_pos_image_user_grey_day;
3634 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3635 m_cTideBitmap = m_bmTideDay;
3636 m_cCurrentBitmap = m_bmCurrentDay;
3637 break;
3638 }
3639
3640 CreateDepthUnitEmbossMaps(cs);
3641 CreateOZEmbossMapData(cs);
3642
3643 // Set up fog effect base color
3644 m_fog_color = wxColor(
3645 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3646 float dim = 1.0;
3647 switch (cs) {
3648 case GLOBAL_COLOR_SCHEME_DUSK:
3649 dim = 0.5;
3650 break;
3651 case GLOBAL_COLOR_SCHEME_NIGHT:
3652 dim = 0.25;
3653 break;
3654 default:
3655 break;
3656 }
3657 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3658 m_fog_color.Blue() * dim);
3659
3660 // Really dark
3661#if 0
3662 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3663 SetBackgroundColour( wxColour(0,0,0) );
3664
3665 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3666 }
3667 else{
3668 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3669#ifndef __WXMAC__
3670 SetBackgroundColour( wxNullColour );
3671#endif
3672 }
3673#endif
3674
3675 // UpdateToolbarColorScheme(cs);
3676
3677 m_Piano->SetColorScheme(cs);
3678
3679 m_Compass->SetColorScheme(cs);
3680
3681 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3682
3683 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3684
3685 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3686 if (m_notification_button) {
3687 m_notification_button->SetColorScheme(cs);
3688 }
3689
3690#ifdef ocpnUSE_GL
3691 if (g_bopengl && m_glcc) {
3692 m_glcc->SetColorScheme(cs);
3693 g_glTextureManager->ClearAllRasterTextures();
3694 // m_glcc->FlushFBO();
3695 }
3696#endif
3697 SetbTCUpdate(true); // force re-render of tide/current locators
3698 m_brepaint_piano = true;
3699
3700 ReloadVP();
3701
3702 m_cs = cs;
3703}
3704
3705wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3706 wxImage img = Bitmap.ConvertToImage();
3707 int sx = img.GetWidth();
3708 int sy = img.GetHeight();
3709
3710 wxImage new_img(img);
3711
3712 for (int i = 0; i < sx; i++) {
3713 for (int j = 0; j < sy; j++) {
3714 if (!img.IsTransparent(i, j)) {
3715 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3716 (unsigned char)(img.GetGreen(i, j) * factor),
3717 (unsigned char)(img.GetBlue(i, j) * factor));
3718 }
3719 }
3720 }
3721
3722 wxBitmap ret = wxBitmap(new_img);
3723
3724 return ret;
3725}
3726
3727void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3728 int max) {
3729 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3730 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3731
3732 if (!m_pBrightPopup) {
3733 // Calculate size
3734 int x, y;
3735 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3736
3737 m_pBrightPopup = new TimedPopupWin(this, 3);
3738
3739 m_pBrightPopup->SetSize(x, y);
3740 m_pBrightPopup->Move(120, 120);
3741 }
3742
3743 int bmpsx = m_pBrightPopup->GetSize().x;
3744 int bmpsy = m_pBrightPopup->GetSize().y;
3745
3746 wxBitmap bmp(bmpsx, bmpsx);
3747 wxMemoryDC mdc(bmp);
3748
3749 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3750 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3751 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3752 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3753 mdc.Clear();
3754
3755 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3756
3757 mdc.SetFont(*pfont);
3758 wxString val;
3759
3760 if (brightness == max)
3761 val = "MAX";
3762 else if (brightness == min)
3763 val = "MIN";
3764 else
3765 val.Printf("%3d", brightness);
3766
3767 mdc.DrawText(val, 0, 0);
3768
3769 mdc.SelectObject(wxNullBitmap);
3770
3771 m_pBrightPopup->SetBitmap(bmp);
3772 m_pBrightPopup->Show();
3773 m_pBrightPopup->Refresh();
3774}
3775
3776void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3777 m_b_rot_hidef = true;
3778 ReloadVP();
3779}
3780
3781void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3782 if (!g_bRollover) return;
3783
3784 bool b_need_refresh = false;
3785
3786 wxSize win_size = GetSize() * m_displayScale;
3787 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3788
3789 // Handle the AIS Rollover Window first
3790 bool showAISRollover = false;
3791 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3792 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3793 SelectItem *pFind = pSelectAIS->FindSelection(
3794 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3795 if (pFind) {
3796 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3797 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3798
3799 if (ptarget) {
3800 showAISRollover = true;
3801
3802 if (NULL == m_pAISRolloverWin) {
3803 m_pAISRolloverWin = new RolloverWin(this);
3804 m_pAISRolloverWin->IsActive(false);
3805 b_need_refresh = true;
3806 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3807 m_AISRollover_MMSI != FoundAIS_MMSI) {
3808 // Sometimes the mouse moves fast enough to get over a new AIS
3809 // target before the one-shot has fired to remove the old target.
3810 // Result: wrong target data is shown.
3811 // Detect this case,close the existing rollover ASAP, and restart
3812 // the timer.
3813 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3814 m_pAISRolloverWin->IsActive(false);
3815 m_AISRollover_MMSI = 0;
3816 Refresh();
3817 return;
3818 }
3819
3820 m_AISRollover_MMSI = FoundAIS_MMSI;
3821
3822 if (!m_pAISRolloverWin->IsActive()) {
3823 wxString s = ptarget->GetRolloverString();
3824 m_pAISRolloverWin->SetString(s);
3825
3826 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3827 AIS_ROLLOVER, win_size);
3828 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3829 m_pAISRolloverWin->IsActive(true);
3830 b_need_refresh = true;
3831 }
3832 }
3833 } else {
3834 m_AISRollover_MMSI = 0;
3835 showAISRollover = false;
3836 }
3837 }
3838
3839 // Maybe turn the rollover off
3840 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3841 m_pAISRolloverWin->IsActive(false);
3842 m_AISRollover_MMSI = 0;
3843 b_need_refresh = true;
3844 }
3845
3846 // Now the Route info rollover
3847 // Show the route segment info
3848 bool showRouteRollover = false;
3849
3850 if (NULL == m_pRolloverRouteSeg) {
3851 // Get a list of all selectable sgements, and search for the first
3852 // visible segment as the rollover target.
3853
3854 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3855 SelectableItemList SelList = pSelect->FindSelectionList(
3856 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3857 auto node = SelList.begin();
3858 while (node != SelList.end()) {
3859 SelectItem *pFindSel = *node;
3860
3861 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3862
3863 if (pr && pr->IsVisible()) {
3864 m_pRolloverRouteSeg = pFindSel;
3865 showRouteRollover = true;
3866
3867 if (NULL == m_pRouteRolloverWin) {
3868 m_pRouteRolloverWin = new RolloverWin(this, 10);
3869 m_pRouteRolloverWin->IsActive(false);
3870 }
3871
3872 if (!m_pRouteRolloverWin->IsActive()) {
3873 wxString s;
3874 RoutePoint *segShow_point_a =
3875 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3876 RoutePoint *segShow_point_b =
3877 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3878
3879 double brg, dist;
3880 DistanceBearingMercator(
3881 segShow_point_b->m_lat, segShow_point_b->m_lon,
3882 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
3883
3884 if (!pr->m_bIsInLayer)
3885 s.Append(_("Route") + ": ");
3886 else
3887 s.Append(_("Layer Route: "));
3888
3889 if (pr->m_RouteNameString.IsEmpty())
3890 s.Append(_("(unnamed)"));
3891 else
3892 s.Append(pr->m_RouteNameString);
3893
3894 s << "\n"
3895 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
3896 << "\n"
3897 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
3898 << segShow_point_b->GetName() << "\n";
3899
3900 if (g_bShowTrue)
3901 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
3902 (int)floor(brg + 0.5), 0x00B0);
3903 if (g_bShowMag) {
3904 double latAverage =
3905 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
3906 double lonAverage =
3907 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
3908 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
3909
3910 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
3911 (int)floor(varBrg + 0.5), 0x00B0);
3912 }
3913
3914 s << FormatDistanceAdaptive(dist);
3915
3916 // Compute and display cumulative distance from route start point to
3917 // current leg end point and RNG,TTG,ETA from ship to current leg end
3918 // point for active route
3919 double shiptoEndLeg = 0.;
3920 bool validActive = false;
3921 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
3922 validActive = true;
3923
3924 if (segShow_point_a != *pr->pRoutePointList->begin()) {
3925 auto node = pr->pRoutePointList->begin();
3926 ++node;
3927 RoutePoint *prp;
3928 float dist_to_endleg = 0;
3929 wxString t;
3930
3931 while (node != pr->pRoutePointList->end()) {
3932 prp = *node;
3933 if (validActive)
3934 shiptoEndLeg += prp->m_seg_len;
3935 else if (prp->m_bIsActive)
3936 validActive = true;
3937 dist_to_endleg += prp->m_seg_len;
3938 if (prp->IsSame(segShow_point_a)) break;
3939 ++node;
3940 }
3941 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
3942 }
3943 // write from ship to end selected leg point data if the route is
3944 // active
3945 if (validActive) {
3946 s << "\n"
3947 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
3948 shiptoEndLeg +=
3950 ->GetCurrentRngToActivePoint(); // add distance from ship
3951 // to active point
3952 shiptoEndLeg +=
3953 segShow_point_b
3954 ->m_seg_len; // add the lenght of the selected leg
3955 s << FormatDistanceAdaptive(shiptoEndLeg);
3956 // ensure sog/cog are valid and vmg is positive to keep data
3957 // coherent
3958 double vmg = 0.;
3959 if (!std::isnan(gCog) && !std::isnan(gSog))
3960 vmg = gSog *
3961 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
3962 PI / 180.);
3963 if (vmg > 0.) {
3964 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
3965 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
3966 s << " - "
3967 << wxString(ttg_sec > SECONDS_PER_DAY
3968 ? ttg_span.Format(_("%Dd %H:%M"))
3969 : ttg_span.Format(_("%H:%M")));
3970 wxDateTime dtnow, eta;
3971 eta = dtnow.SetToCurrent().Add(ttg_span);
3972 s << " - " << eta.Format("%b").Mid(0, 4)
3973 << eta.Format(" %d %H:%M");
3974 } else
3975 s << " ---- ----";
3976 }
3977 m_pRouteRolloverWin->SetString(s);
3978
3979 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3980 LEG_ROLLOVER, win_size);
3981 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
3982 m_pRouteRolloverWin->IsActive(true);
3983 b_need_refresh = true;
3984 showRouteRollover = true;
3985 break;
3986 }
3987 } else {
3988 ++node;
3989 }
3990 }
3991 } else {
3992 // Is the cursor still in select radius, and not timed out?
3993 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3994 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
3995 m_pRolloverRouteSeg))
3996 showRouteRollover = false;
3997 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
3998 showRouteRollover = false;
3999 else
4000 showRouteRollover = true;
4001 }
4002
4003 // If currently creating a route, do not show this rollover window
4004 if (m_routeState) showRouteRollover = false;
4005
4006 // Similar for AIS target rollover window
4007 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4008 showRouteRollover = false;
4009
4010 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4011 !showRouteRollover) {
4012 m_pRouteRolloverWin->IsActive(false);
4013 m_pRolloverRouteSeg = NULL;
4014 m_pRouteRolloverWin->Destroy();
4015 m_pRouteRolloverWin = NULL;
4016 b_need_refresh = true;
4017 } else if (m_pRouteRolloverWin && showRouteRollover) {
4018 m_pRouteRolloverWin->IsActive(true);
4019 b_need_refresh = true;
4020 }
4021
4022 // Now the Track info rollover
4023 // Show the track segment info
4024 bool showTrackRollover = false;
4025
4026 if (NULL == m_pRolloverTrackSeg) {
4027 // Get a list of all selectable sgements, and search for the first
4028 // visible segment as the rollover target.
4029
4030 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4031 SelectableItemList SelList = pSelect->FindSelectionList(
4032 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4033
4034 auto node = SelList.begin();
4035 while (node != SelList.end()) {
4036 SelectItem *pFindSel = *node;
4037
4038 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4039
4040 if (pt && pt->IsVisible()) {
4041 m_pRolloverTrackSeg = pFindSel;
4042 showTrackRollover = true;
4043
4044 if (NULL == m_pTrackRolloverWin) {
4045 m_pTrackRolloverWin = new RolloverWin(this, 10);
4046 m_pTrackRolloverWin->IsActive(false);
4047 }
4048
4049 if (!m_pTrackRolloverWin->IsActive()) {
4050 wxString s;
4051 TrackPoint *segShow_point_a =
4052 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4053 TrackPoint *segShow_point_b =
4054 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4055
4056 double brg, dist;
4057 DistanceBearingMercator(
4058 segShow_point_b->m_lat, segShow_point_b->m_lon,
4059 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4060
4061 if (!pt->m_bIsInLayer)
4062 s.Append(_("Track") + ": ");
4063 else
4064 s.Append(_("Layer Track: "));
4065
4066 if (pt->GetName().IsEmpty())
4067 s.Append(_("(unnamed)"));
4068 else
4069 s.Append(pt->GetName());
4070 double tlenght = pt->Length();
4071 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4072 if (pt->GetLastPoint()->GetTimeString() &&
4073 pt->GetPoint(0)->GetTimeString()) {
4074 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4075 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4076 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4077 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4078 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4079 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4080 << getUsrSpeedUnit();
4081 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4082 : ttime.Format(" %H:%M"));
4083 }
4084 }
4085
4086 if (g_bShowTrackPointTime &&
4087 strlen(segShow_point_b->GetTimeString())) {
4088 wxString stamp = segShow_point_b->GetTimeString();
4089 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4090 if (timestamp.IsValid()) {
4091 // Format track rollover timestamp to OCPN global TZ setting
4094 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4095 }
4096 s << "\n" << _("Segment Created: ") << stamp;
4097 }
4098
4099 s << "\n";
4100 if (g_bShowTrue)
4101 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4102 0x00B0);
4103
4104 if (g_bShowMag) {
4105 double latAverage =
4106 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4107 double lonAverage =
4108 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4109 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4110
4111 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4112 0x00B0);
4113 }
4114
4115 s << FormatDistanceAdaptive(dist);
4116
4117 if (segShow_point_a->GetTimeString() &&
4118 segShow_point_b->GetTimeString()) {
4119 wxDateTime apoint = segShow_point_a->GetCreateTime();
4120 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4121 if (apoint.IsValid() && bpoint.IsValid()) {
4122 double segmentSpeed = toUsrSpeed(
4123 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4124 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4125 << getUsrSpeedUnit();
4126 }
4127 }
4128
4129 m_pTrackRolloverWin->SetString(s);
4130
4131 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4132 LEG_ROLLOVER, win_size);
4133 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4134 m_pTrackRolloverWin->IsActive(true);
4135 b_need_refresh = true;
4136 showTrackRollover = true;
4137 break;
4138 }
4139 } else {
4140 ++node;
4141 }
4142 }
4143 } else {
4144 // Is the cursor still in select radius, and not timed out?
4145 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4146 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4147 m_pRolloverTrackSeg))
4148 showTrackRollover = false;
4149 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4150 showTrackRollover = false;
4151 else
4152 showTrackRollover = true;
4153 }
4154
4155 // Similar for AIS target rollover window
4156 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4157 showTrackRollover = false;
4158
4159 // Similar for route rollover window
4160 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4161 showTrackRollover = false;
4162
4163 // TODO We onlt show tracks on primary canvas....
4164 // if(!IsPrimaryCanvas())
4165 // showTrackRollover = false;
4166
4167 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4168 !showTrackRollover) {
4169 m_pTrackRolloverWin->IsActive(false);
4170 m_pRolloverTrackSeg = NULL;
4171 m_pTrackRolloverWin->Destroy();
4172 m_pTrackRolloverWin = NULL;
4173 b_need_refresh = true;
4174 } else if (m_pTrackRolloverWin && showTrackRollover) {
4175 m_pTrackRolloverWin->IsActive(true);
4176 b_need_refresh = true;
4177 }
4178
4179 if (b_need_refresh) Refresh();
4180}
4181
4182void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4183 if ((GetShowENCLights() || m_bsectors_shown) &&
4184 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4185 extendedSectorLegs)) {
4186 if (!m_bsectors_shown) {
4187 ReloadVP(false);
4188 m_bsectors_shown = true;
4189 }
4190 } else {
4191 if (m_bsectors_shown) {
4192 ReloadVP(false);
4193 m_bsectors_shown = false;
4194 }
4195 }
4196
4197// This is here because GTK status window update is expensive..
4198// cairo using pango rebuilds the font every time so is very
4199// inefficient
4200// Anyway, only update the status bar when this timer expires
4201#if defined(__WXGTK__) || defined(__WXQT__)
4202 {
4203 // Check the absolute range of the cursor position
4204 // There could be a window wherein the chart geoereferencing is not
4205 // valid....
4206 double cursor_lat, cursor_lon;
4207 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4208
4209 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4210 while (cursor_lon < -180.) cursor_lon += 360.;
4211
4212 while (cursor_lon > 180.) cursor_lon -= 360.;
4213
4214 SetCursorStatus(cursor_lat, cursor_lon);
4215 }
4216 }
4217#endif
4218}
4219
4220void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4221 if (!parent_frame->m_pStatusBar) return;
4222
4223 wxString s1;
4224 s1 += " ";
4225 s1 += toSDMM(1, cursor_lat);
4226 s1 += " ";
4227 s1 += toSDMM(2, cursor_lon);
4228
4229 if (STAT_FIELD_CURSOR_LL >= 0)
4230 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4231
4232 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4233
4234 double brg, dist;
4235 wxString sm;
4236 wxString st;
4237 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4238 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4239 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4240
4241 wxString s = st + sm;
4242 s << FormatDistanceAdaptive(dist);
4243
4244 // CUSTOMIZATION - LIVE ETA OPTION
4245 // -------------------------------------------------------
4246 // Calculate an "live" ETA based on route starting from the current
4247 // position of the boat and goes to the cursor of the mouse.
4248 // In any case, an standard ETA will be calculated with a default speed
4249 // of the boat to give an estimation of the route (in particular if GPS
4250 // is off).
4251
4252 // Display only if option "live ETA" is selected in Settings > Display >
4253 // General.
4254 if (g_bShowLiveETA) {
4255 float realTimeETA;
4256 float boatSpeed;
4257 float boatSpeedDefault = g_defaultBoatSpeed;
4258
4259 // Calculate Estimate Time to Arrival (ETA) in minutes
4260 // Check before is value not closed to zero (it will make an very big
4261 // number...)
4262 if (!std::isnan(gSog)) {
4263 boatSpeed = gSog;
4264 if (boatSpeed < 0.5) {
4265 realTimeETA = 0;
4266 } else {
4267 realTimeETA = dist / boatSpeed * 60;
4268 }
4269 } else {
4270 realTimeETA = 0;
4271 }
4272
4273 // Add space after distance display
4274 s << " ";
4275 // Display ETA
4276 s << minutesToHoursDays(realTimeETA);
4277
4278 // In any case, display also an ETA with default speed at 6knts
4279
4280 s << " [@";
4281 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4282 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4283 s << " ";
4284 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4285 s << "]";
4286 }
4287 // END OF - LIVE ETA OPTION
4288
4289 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4290}
4291
4292// CUSTOMIZATION - FORMAT MINUTES
4293// -------------------------------------------------------
4294// New function to format minutes into a more readable format:
4295// * Hours + minutes, or
4296// * Days + hours.
4297wxString minutesToHoursDays(float timeInMinutes) {
4298 wxString s;
4299
4300 if (timeInMinutes == 0) {
4301 s << "--min";
4302 }
4303
4304 // Less than 60min, keep time in minutes
4305 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4306 s << wxString::Format("%d", (int)timeInMinutes);
4307 s << "min";
4308 }
4309
4310 // Between 1h and less than 24h, display time in hours, minutes
4311 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4312 int hours;
4313 int min;
4314 hours = (int)timeInMinutes / 60;
4315 min = (int)timeInMinutes % 60;
4316
4317 if (min == 0) {
4318 s << wxString::Format("%d", hours);
4319 s << "h";
4320 } else {
4321 s << wxString::Format("%d", hours);
4322 s << "h";
4323 s << wxString::Format("%d", min);
4324 s << "min";
4325 }
4326
4327 }
4328
4329 // More than 24h, display time in days, hours
4330 else if (timeInMinutes > 24 * 60) {
4331 int days;
4332 int hours;
4333 days = (int)(timeInMinutes / 60) / 24;
4334 hours = (int)(timeInMinutes / 60) % 24;
4335
4336 if (hours == 0) {
4337 s << wxString::Format("%d", days);
4338 s << "d";
4339 } else {
4340 s << wxString::Format("%d", days);
4341 s << "d";
4342 s << wxString::Format("%d", hours);
4343 s << "h";
4344 }
4345 }
4346
4347 return s;
4348}
4349
4350// END OF CUSTOMIZATION - FORMAT MINUTES
4351// Thanks open source code ;-)
4352// -------------------------------------------------------
4353
4354void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4355 double clat, clon;
4356 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4357 *lat = clat;
4358 *lon = clon;
4359}
4360
4361void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4362 wxPoint2DDouble *r) {
4363 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4364}
4365
4367 double rlon, wxPoint2DDouble *r) {
4368 // If the Current Chart is a raster chart, and the
4369 // requested lat/long is within the boundaries of the chart,
4370 // and the VP is not rotated,
4371 // then use the embedded BSB chart georeferencing algorithm
4372 // for greater accuracy
4373 // Additionally, use chart embedded georef if the projection is TMERC
4374 // i.e. NOT MERCATOR and NOT POLYCONIC
4375
4376 // If for some reason the chart rejects the request by returning an error,
4377 // then fall back to Viewport Projection estimate from canvas parameters
4378 if (!g_bopengl && m_singleChart &&
4379 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4380 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4381 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4382 (m_singleChart->GetChartProjectionType() !=
4383 PROJECTION_TRANSVERSE_MERCATOR) &&
4384 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4385 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4386 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4387 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4388 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4389 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4390 // Cur_BSB_Ch->GetCOVRTablenPoints
4391 // ( 0 ), rlon,
4392 // rlat );
4393 // bInside = true;
4394 // if ( bInside )
4395 if (Cur_BSB_Ch) {
4396 // This is a Raster chart....
4397 // If the VP is changing, the raster chart parameters may not yet be
4398 // setup So do that before accessing the chart's embedded
4399 // georeferencing
4400 Cur_BSB_Ch->SetVPRasterParms(vp);
4401 double rpixxd, rpixyd;
4402 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4403 r->m_x = rpixxd;
4404 r->m_y = rpixyd;
4405 return;
4406 }
4407 }
4408 }
4409
4410 // if needed, use the VPoint scaling estimator,
4411 *r = vp.GetDoublePixFromLL(rlat, rlon);
4412}
4413
4414// This routine might be deleted and all of the rendering improved
4415// to have floating point accuracy
4416bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4417 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4418}
4419
4420bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4421 wxPoint *r) {
4422 wxPoint2DDouble p;
4423 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4424
4425 // some projections give nan values when invisible values (other side of
4426 // world) are requested we should stop using integer coordinates or return
4427 // false here (and test it everywhere)
4428 if (std::isnan(p.m_x)) {
4429 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4430 return false;
4431 }
4432
4433 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4434 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4435 else
4436 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4437
4438 return true;
4439}
4440
4441void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4442 double &lon) {
4443 // If the Current Chart is a raster chart, and the
4444 // requested x,y is within the boundaries of the chart,
4445 // and the VP is not rotated,
4446 // then use the embedded BSB chart georeferencing algorithm
4447 // for greater accuracy
4448 // Additionally, use chart embedded georef if the projection is TMERC
4449 // i.e. NOT MERCATOR and NOT POLYCONIC
4450
4451 // If for some reason the chart rejects the request by returning an error,
4452 // then fall back to Viewport Projection estimate from canvas parameters
4453 bool bUseVP = true;
4454
4455 if (!g_bopengl && m_singleChart &&
4456 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4457 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4458 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4459 (m_singleChart->GetChartProjectionType() !=
4460 PROJECTION_TRANSVERSE_MERCATOR) &&
4461 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4462 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4463 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4464 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4465
4466 // TODO maybe need iterative process to validate bInside
4467 // first pass is mercator, then check chart boundaries
4468
4469 if (Cur_BSB_Ch) {
4470 // This is a Raster chart....
4471 // If the VP is changing, the raster chart parameters may not yet be
4472 // setup So do that before accessing the chart's embedded
4473 // georeferencing
4474 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4475
4476 double slat, slon;
4477 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4478 lat = slat;
4479
4480 if (slon < -180.)
4481 slon += 360.;
4482 else if (slon > 180.)
4483 slon -= 360.;
4484
4485 lon = slon;
4486 bUseVP = false;
4487 }
4488 }
4489 }
4490
4491 // if needed, use the VPoint scaling estimator
4492 if (bUseVP) {
4493 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4494 }
4495}
4496
4498 StopMovement();
4499 DoZoomCanvas(factor, false);
4500 extendedSectorLegs.clear();
4501}
4502
4503void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4504 bool stoptimer) {
4505 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4506
4507 if (g_bsmoothpanzoom) {
4508 if (StartTimedMovement(stoptimer)) {
4509 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4510 m_zoom_factor = factor;
4511 }
4512
4513 m_zoom_target = VPoint.chart_scale / factor;
4514 } else {
4515 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4516
4517 DoZoomCanvas(factor, can_zoom_to_cursor);
4518 }
4519
4520 extendedSectorLegs.clear();
4521}
4522
4523void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4524 // possible on startup
4525 if (!ChartData) return;
4526 if (!m_pCurrentStack) return;
4527
4528 /* TODO: queue the quilted loading code to a background thread
4529 so yield is never called from here, and also rendering is not delayed */
4530
4531 // Cannot allow Yield() re-entrancy here
4532 if (m_bzooming) return;
4533 m_bzooming = true;
4534
4535 double old_ppm = GetVP().view_scale_ppm;
4536
4537 // Capture current cursor position for zoom to cursor
4538 double zlat = m_cursor_lat;
4539 double zlon = m_cursor_lon;
4540
4541 double proposed_scale_onscreen =
4542 GetVP().chart_scale /
4543 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4544 bool b_do_zoom = false;
4545
4546 if (factor > 1) {
4547 b_do_zoom = true;
4548
4549 // double zoom_factor = factor;
4550
4551 ChartBase *pc = NULL;
4552
4553 if (!VPoint.b_quilt) {
4554 pc = m_singleChart;
4555 } else {
4556 if (!m_disable_adjust_on_zoom) {
4557 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4558 if (new_db_index >= 0)
4559 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4560 else { // for whatever reason, no reference chart is known
4561 // Choose the smallest scale chart on the current stack
4562 // and then adjust for scale range
4563 int current_ref_stack_index = -1;
4564 if (m_pCurrentStack->nEntry) {
4565 int trial_index =
4566 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4567 m_pQuilt->SetReferenceChart(trial_index);
4568 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4569 if (new_db_index >= 0)
4570 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4571 }
4572 }
4573
4574 if (m_pCurrentStack)
4575 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4576 new_db_index); // highlite the correct bar entry
4577 }
4578 }
4579
4580 if (pc) {
4581 // double target_scale_ppm = GetVPScale() * zoom_factor;
4582 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4583 // target_scale_ppm;
4584
4585 // Query the chart to determine the appropriate zoom range
4586 double min_allowed_scale =
4587 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4588
4589 if (proposed_scale_onscreen < min_allowed_scale) {
4590 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4591 m_zoom_factor = 1; /* stop zooming */
4592 b_do_zoom = false;
4593 } else
4594 proposed_scale_onscreen = min_allowed_scale;
4595 }
4596
4597 } else {
4598 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4599 }
4600
4601 } else if (factor < 1) {
4602 b_do_zoom = true;
4603
4604 ChartBase *pc = NULL;
4605
4606 bool b_smallest = false;
4607
4608 if (!VPoint.b_quilt) { // not quilted
4609 pc = m_singleChart;
4610
4611 if (pc) {
4612 // If m_singleChart is not on the screen, unbound the zoomout
4613 LLBBox viewbox = VPoint.GetBBox();
4614 // BoundingBox chart_box;
4615 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4616 double max_allowed_scale;
4617
4618 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4619
4620 // We can allow essentially unbounded zoomout in single chart mode
4621 // if( ChartData->GetDBBoundingBox( current_index,
4622 // &chart_box ) &&
4623 // !viewbox.IntersectOut( chart_box ) )
4624 // // Clamp the minimum scale zoom-out to the value
4625 // specified by the chart max_allowed_scale =
4626 // wxMin(max_allowed_scale, 4.0 *
4627 // pc->GetNormalScaleMax(
4628 // GetCanvasScaleFactor(),
4629 // GetCanvasWidth() ) );
4630 if (proposed_scale_onscreen > max_allowed_scale) {
4631 m_zoom_factor = 1; /* stop zooming */
4632 proposed_scale_onscreen = max_allowed_scale;
4633 }
4634 }
4635
4636 } else {
4637 if (!m_disable_adjust_on_zoom) {
4638 int new_db_index =
4639 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4640 if (new_db_index >= 0)
4641 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4642
4643 if (m_pCurrentStack)
4644 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4645 new_db_index); // highlite the correct bar entry
4646
4647 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4648
4649 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4650 proposed_scale_onscreen =
4651 wxMin(proposed_scale_onscreen,
4652 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4653 }
4654
4655 // set a minimum scale
4656 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4657 m_absolute_min_scale_ppm)
4658 proposed_scale_onscreen =
4659 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4660 }
4661 }
4662 double new_scale =
4663 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4664
4665 if (b_do_zoom) {
4666 // Disable ZTC if lookahead is ON, and currently b_follow is active
4667 bool b_allow_ztc = true;
4668 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4669 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4670 if (m_bLookAhead) {
4671 double brg, distance;
4672 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4673 &distance);
4674 dir_to_shift = brg;
4675 meters_to_shift = distance * 1852;
4676 }
4677 // Arrange to combine the zoom and pan into one operation for smoother
4678 // appearance
4679 SetVPScale(new_scale, false); // adjust, but deferred refresh
4680 wxPoint r;
4681 GetCanvasPointPix(zlat, zlon, &r);
4682 // this will emit the Refresh()
4683 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4684 } else {
4685 SetVPScale(new_scale);
4686 if (m_bFollow) DoCanvasUpdate();
4687 }
4688 }
4689
4690 m_bzooming = false;
4691}
4692
4693void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4694 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4695 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4696}
4697
4698int rot;
4699void ChartCanvas::RotateCanvas(double dir) {
4700 // SetUpMode(NORTH_UP_MODE);
4701
4702 if (g_bsmoothpanzoom) {
4703 if (StartTimedMovement()) {
4704 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4705 m_rotation_speed = dir * 60;
4706 }
4707 } else {
4708 double speed = dir * 10;
4709 if (m_modkeys == wxMOD_ALT) speed /= 20;
4710 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4711 }
4712}
4713
4714void ChartCanvas::DoRotateCanvas(double rotation) {
4715 while (rotation < 0) rotation += 2 * PI;
4716 while (rotation > 2 * PI) rotation -= 2 * PI;
4717
4718 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4719
4720 SetVPRotation(rotation);
4721 parent_frame->UpdateRotationState(VPoint.rotation);
4722}
4723
4724void ChartCanvas::DoTiltCanvas(double tilt) {
4725 while (tilt < 0) tilt = 0;
4726 while (tilt > .95) tilt = .95;
4727
4728 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4729
4730 VPoint.tilt = tilt;
4731 Refresh(false);
4732}
4733
4734void ChartCanvas::TogglebFollow() {
4735 if (!m_bFollow)
4736 SetbFollow();
4737 else
4738 ClearbFollow();
4739}
4740
4741void ChartCanvas::ClearbFollow() {
4742 m_bFollow = false; // update the follow flag
4743
4744 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4745
4746 UpdateFollowButtonState();
4747
4748 DoCanvasUpdate();
4749 ReloadVP();
4750 parent_frame->SetChartUpdatePeriod();
4751}
4752
4753void ChartCanvas::SetbFollow() {
4754 // Is the OWNSHIP on-screen?
4755 // If not, then reset the OWNSHIP offset to 0 (center screen)
4756 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4757 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4758 m_OSoffsetx = 0;
4759 m_OSoffsety = 0;
4760 }
4761
4762 // Apply the present b_follow offset values to ship position
4763 wxPoint2DDouble p;
4765 p.m_x += m_OSoffsetx;
4766 p.m_y -= m_OSoffsety;
4767
4768 // compute the target center screen lat/lon
4769 double dlat, dlon;
4770 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4771
4772 JumpToPosition(dlat, dlon, GetVPScale());
4773 m_bFollow = true;
4774
4775 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4776 UpdateFollowButtonState();
4777
4778 if (!g_bSmoothRecenter) {
4779 DoCanvasUpdate();
4780 ReloadVP();
4781 }
4782 parent_frame->SetChartUpdatePeriod();
4783}
4784
4785void ChartCanvas::UpdateFollowButtonState() {
4786 if (m_muiBar) {
4787 if (!m_bFollow)
4788 m_muiBar->SetFollowButtonState(0);
4789 else {
4790 if (m_bLookAhead)
4791 m_muiBar->SetFollowButtonState(2);
4792 else
4793 m_muiBar->SetFollowButtonState(1);
4794 }
4795 }
4796
4797#ifdef __ANDROID__
4798 if (!m_bFollow)
4799 androidSetFollowTool(0);
4800 else {
4801 if (m_bLookAhead)
4802 androidSetFollowTool(2);
4803 else
4804 androidSetFollowTool(1);
4805 }
4806#endif
4807
4808 // Look for plugin using API-121 or later
4809 // If found, make the follow state callback.
4810 if (g_pi_manager) {
4811 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4812 if (pic->m_enabled && pic->m_init_state) {
4813 switch (pic->m_api_version) {
4814 case 121: {
4815 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4816 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4817 break;
4818 }
4819 default:
4820 break;
4821 }
4822 }
4823 }
4824 }
4825}
4826
4827void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4828 if (g_bSmoothRecenter && !m_routeState) {
4829 if (StartSmoothJump(lat, lon, scale_ppm))
4830 return;
4831 else {
4832 // move closer to the target destination, and try again
4833 double gcDist, gcBearingEnd;
4834 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4835 &gcBearingEnd);
4836 gcBearingEnd += 180;
4837 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4838 GetCanvasWidth() / GetVPScale(); // meters
4839 double lon_offset =
4840 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4841 double new_lat = lat + (lat_offset / (1852 * 60));
4842 double new_lon = lon + (lon_offset / (1852 * 60));
4843 SetViewPoint(new_lat, new_lon);
4844 ReloadVP();
4845 StartSmoothJump(lat, lon, scale_ppm);
4846 return;
4847 }
4848 }
4849
4850 if (lon > 180.0) lon -= 360.0;
4851 m_vLat = lat;
4852 m_vLon = lon;
4853 StopMovement();
4854 m_bFollow = false;
4855
4856 if (!GetQuiltMode()) {
4857 double skew = 0;
4858 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4859 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4860 } else {
4861 if (scale_ppm != GetVPScale()) {
4862 // XXX should be done in SetViewPoint
4863 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4864 AdjustQuiltRefChart();
4865 }
4866 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4867 }
4868
4869 ReloadVP();
4870
4871 UpdateFollowButtonState();
4872
4873 // TODO
4874 // if( g_pi_manager ) {
4875 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4876 // }
4877}
4878
4879bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4880 // Check distance to jump, in pixels at current chart scale
4881 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
4882 // width.
4883 double gcDist;
4884 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
4885 double distance_pixels = gcDist * GetVPScale();
4886 if (distance_pixels > 0.5 * GetCanvasWidth()) {
4887 // Jump is too far, try again
4888 return false;
4889 }
4890
4891 // Save where we're coming from
4892 m_startLat = m_vLat;
4893 m_startLon = m_vLon;
4894 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
4895
4896 // Save where we want to end up
4897 m_endLat = lat;
4898 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
4899 m_endScale = scale_ppm;
4900
4901 // Setup timing
4902 m_animationDuration = 600; // ms
4903 m_animationStart = wxGetLocalTimeMillis();
4904
4905 // Stop any previous movement, ensure no conflicts
4906 StopMovement();
4907 m_bFollow = false;
4908
4909 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
4910 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
4911 m_animationActive = true;
4912
4913 return true;
4914}
4915
4916void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
4917 // Calculate time fraction from 0..1
4918 wxLongLong now = wxGetLocalTimeMillis();
4919 double elapsed = (now - m_animationStart).ToDouble();
4920 double t = elapsed / m_animationDuration.ToDouble();
4921 if (t > 1.0) t = 1.0;
4922
4923 // Ease function for smoother movement
4924 double e = easeOutCubic(t);
4925
4926 // Interpolate lat/lon/scale
4927 double curLat = m_startLat + (m_endLat - m_startLat) * e;
4928 double curLon = m_startLon + (m_endLon - m_startLon) * e;
4929 double curScale = m_startScale + (m_endScale - m_startScale) * e;
4930
4931 // Update viewpoint
4932 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
4933 // portion)
4934 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
4935 ReloadVP();
4936
4937 // If we reached the end, stop the timer and finalize
4938 if (t >= 1.0) {
4939 m_easeTimer.Stop();
4940 m_animationActive = false;
4941 UpdateFollowButtonState();
4942 ZoomCanvasSimple(1.0001);
4943 DoCanvasUpdate();
4944 ReloadVP();
4945 }
4946}
4947
4948bool ChartCanvas::PanCanvas(double dx, double dy) {
4949 if (!ChartData) return false;
4950 extendedSectorLegs.clear();
4951
4952 double dlat, dlon;
4953 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
4954
4955 int iters = 0;
4956 for (;;) {
4957 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
4958
4959 if (iters++ > 5) return false;
4960 if (!std::isnan(dlat)) break;
4961
4962 dx *= .5, dy *= .5;
4963 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
4964 }
4965
4966 // avoid overshooting the poles
4967 if (dlat > 90)
4968 dlat = 90;
4969 else if (dlat < -90)
4970 dlat = -90;
4971
4972 if (dlon > 360.) dlon -= 360.;
4973 if (dlon < -360.) dlon += 360.;
4974
4975 // This should not really be necessary, but round-trip georef on some
4976 // charts is not perfect, So we can get creep on repeated unidimensional
4977 // pans, and corrupt chart cacheing.......
4978
4979 // But this only works on north-up projections
4980 // TODO: can we remove this now?
4981 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
4982 // .001 ) ) {
4983 //
4984 // if( dx == 0 ) dlon = clon;
4985 // if( dy == 0 ) dlat = clat;
4986 // }
4987
4988 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4989
4990 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
4991
4992 if (VPoint.b_quilt) {
4993 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
4994 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
4995 // Tweak the scale slightly for a new ref chart
4996 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
4997 if (pc) {
4998 double tweak_scale_ppm =
4999 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5000 SetVPScale(tweak_scale_ppm);
5001 }
5002 }
5003
5004 if (new_ref_dbIndex == -1) {
5005#pragma GCC diagnostic push
5006#pragma GCC diagnostic ignored "-Warray-bounds"
5007 // The compiler sees a -1 index being used. Does not happen, though.
5008
5009 // for whatever reason, no reference chart is known
5010 // Probably panned out of the coverage region
5011 // If any charts are anywhere on-screen, choose the smallest
5012 // scale chart on the screen to be a new reference chart.
5013 int trial_index = -1;
5014 if (m_pCurrentStack->nEntry) {
5015 int trial_index =
5016 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5017 }
5018
5019 if (trial_index < 0) {
5020 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5021 if (full_screen_array.size())
5022 trial_index = full_screen_array[full_screen_array.size() - 1];
5023 }
5024
5025 if (trial_index >= 0) {
5026 m_pQuilt->SetReferenceChart(trial_index);
5027 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5028 VPoint.rotation);
5029 ReloadVP();
5030 }
5031#pragma GCC diagnostic pop
5032 }
5033 }
5034
5035 // Turn off bFollow only if the ownship has left the screen
5036 if (m_bFollow) {
5037 double offx, offy;
5038 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5039
5040 double offset_angle = atan2(offy, offx);
5041 double offset_distance = sqrt((offy * offy) + (offx * offx));
5042 double chart_angle = GetVPRotation();
5043 double target_angle = chart_angle - offset_angle;
5044 double d_east_mod = offset_distance * cos(target_angle);
5045 double d_north_mod = offset_distance * sin(target_angle);
5046
5047 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5048 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5049
5050 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5051 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5052 m_bFollow = false; // update the follow flag
5053 UpdateFollowButtonState();
5054 }
5055 }
5056
5057 Refresh(false);
5058
5059 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5060
5061 return true;
5062}
5063
5064bool ChartCanvas::IsOwnshipOnScreen() {
5065 wxPoint r;
5067 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5068 ((r.y > 0) && r.y < GetCanvasHeight()))
5069 return true;
5070 else
5071 return false;
5072}
5073
5074void ChartCanvas::ReloadVP(bool b_adjust) {
5075 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5076
5077 LoadVP(VPoint, b_adjust);
5078}
5079
5080void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5081#ifdef ocpnUSE_GL
5082 if (g_bopengl && m_glcc) {
5083 m_glcc->Invalidate();
5084 if (m_glcc->GetSize() != GetSize()) {
5085 m_glcc->SetSize(GetSize());
5086 }
5087 } else
5088#endif
5089 {
5090 m_cache_vp.Invalidate();
5091 m_bm_cache_vp.Invalidate();
5092 }
5093
5094 VPoint.Invalidate();
5095
5096 if (m_pQuilt) m_pQuilt->Invalidate();
5097
5098 // Make sure that the Selected Group is sensible...
5099 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5100 // m_groupIndex = 0;
5101 // if( !CheckGroup( m_groupIndex ) )
5102 // m_groupIndex = 0;
5103
5104 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5105 vp.m_projection_type, b_adjust);
5106}
5107
5108void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5109 m_pQuilt->SetReferenceChart(dbIndex);
5110 VPoint.Invalidate();
5111 m_pQuilt->Invalidate();
5112}
5113
5114double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5115 if (m_pQuilt)
5116 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5117 else
5118 return vp.view_scale_ppm;
5119}
5120
5121// Verify and adjust the current reference chart,
5122// so that it will not lead to excessive overzoom or underzoom onscreen
5123int ChartCanvas::AdjustQuiltRefChart() {
5124 int ret = -1;
5125 if (m_pQuilt) {
5126 wxASSERT(ChartData);
5127 ChartBase *pc =
5128 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5129 if (pc) {
5130 double min_ref_scale =
5131 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5132 double max_ref_scale =
5133 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5134
5135 if (VPoint.chart_scale < min_ref_scale) {
5136 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5137 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5138 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5139 } else {
5140 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5141
5142 if (!brender_ok) {
5143 int target_stack_index = wxNOT_FOUND;
5144 int il = 0;
5145 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5146 if (index == m_pQuilt->GetRefChartdbIndex()) {
5147 target_stack_index = il;
5148 break;
5149 }
5150 il++;
5151 }
5152 if (wxNOT_FOUND == target_stack_index) // should never happen...
5153 target_stack_index = 0;
5154
5155 int ref_family = pc->GetChartFamily();
5156 int extended_array_count =
5157 m_pQuilt->GetExtendedStackIndexArray().size();
5158 while ((!brender_ok) &&
5159 ((int)target_stack_index < (extended_array_count - 1))) {
5160 target_stack_index++;
5161 int test_db_index =
5162 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5163
5164 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5165 IsChartQuiltableRef(test_db_index)) {
5166 // open the target, and check the min_scale
5167 ChartBase *ptest_chart =
5168 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5169 if (ptest_chart) {
5170 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5171 }
5172 }
5173 }
5174
5175 if (brender_ok) { // found a better reference chart
5176 int new_db_index =
5177 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5178 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5179 IsChartQuiltableRef(new_db_index)) {
5180 m_pQuilt->SetReferenceChart(new_db_index);
5181 ret = new_db_index;
5182 } else
5183 ret = m_pQuilt->GetRefChartdbIndex();
5184 } else
5185 ret = m_pQuilt->GetRefChartdbIndex();
5186
5187 } else
5188 ret = m_pQuilt->GetRefChartdbIndex();
5189 }
5190 } else
5191 ret = -1;
5192 }
5193
5194 return ret;
5195}
5196
5197void ChartCanvas::UpdateCanvasOnGroupChange() {
5198 delete m_pCurrentStack;
5199 m_pCurrentStack = new ChartStack;
5200 wxASSERT(ChartData);
5201 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5202 m_groupIndex);
5203
5204 if (m_pQuilt) {
5205 m_pQuilt->Compose(VPoint);
5206 SetFocus();
5207 }
5208}
5209
5210bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5211 double latNE, double lonNE) {
5212 // Center Point
5213 double latc = (latSW + latNE) / 2.0;
5214 double lonc = (lonSW + lonNE) / 2.0;
5215
5216 // Get scale in ppm (latitude)
5217 double ne_easting, ne_northing;
5218 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5219
5220 double sw_easting, sw_northing;
5221 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5222
5223 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5224
5225 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5226}
5227
5228bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5229 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5230 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5231}
5232
5233bool ChartCanvas::SetVPProjection(int projection) {
5234 if (!g_bopengl) // alternative projections require opengl
5235 return false;
5236
5237 // the view scale varies depending on geographic location and projection
5238 // rescale to keep the relative scale on the screen the same
5239 double prev_true_scale_ppm = m_true_scale_ppm;
5240 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5241 VPoint.skew, VPoint.rotation, projection) &&
5242 SetVPScale(wxMax(
5243 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5244 m_absolute_min_scale_ppm));
5245}
5246
5247bool ChartCanvas::SetViewPoint(double lat, double lon) {
5248 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5249 VPoint.rotation);
5250}
5251
5252bool ChartCanvas::SetVPRotation(double angle) {
5253 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5254 VPoint.skew, angle);
5255}
5256bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5257 double skew, double rotation, int projection,
5258 bool b_adjust, bool b_refresh) {
5259 bool b_ret = false;
5260 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5261 skew -= 2 * PI;
5262 // Any sensible change?
5263 if (VPoint.IsValid()) {
5264 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5265 (fabs(VPoint.skew - skew) < 1e-9) &&
5266 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5267 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5268 (VPoint.m_projection_type == projection ||
5269 projection == PROJECTION_UNKNOWN))
5270 return false;
5271 }
5272 if (VPoint.m_projection_type != projection)
5273 VPoint.InvalidateTransformCache(); // invalidate
5274
5275 // Take a local copy of the last viewport
5276 ViewPort last_vp = VPoint;
5277
5278 VPoint.skew = skew;
5279 VPoint.clat = lat;
5280 VPoint.clon = lon;
5281 VPoint.rotation = rotation;
5282 VPoint.view_scale_ppm = scale_ppm;
5283 if (projection != PROJECTION_UNKNOWN)
5284 VPoint.SetProjectionType(projection);
5285 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5286 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5287
5288 // don't allow latitude above 88 for mercator (90 is infinity)
5289 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5290 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5291 if (VPoint.clat > 89.5)
5292 VPoint.clat = 89.5;
5293 else if (VPoint.clat < -89.5)
5294 VPoint.clat = -89.5;
5295 }
5296
5297 // don't zoom out too far for transverse mercator polyconic until we resolve
5298 // issues
5299 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5300 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5301 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5302
5303 // SetVPRotation(rotation);
5304
5305 if (!g_bopengl) // tilt is not possible without opengl
5306 VPoint.tilt = 0;
5307
5308 if ((VPoint.pix_width <= 0) ||
5309 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5310 return false;
5311
5312 bool bwasValid = VPoint.IsValid();
5313 VPoint.Validate(); // Mark this ViewPoint as OK
5314
5315 // Has the Viewport scale changed? If so, invalidate the vp
5316 if (last_vp.view_scale_ppm != scale_ppm) {
5317 m_cache_vp.Invalidate();
5318 InvalidateGL();
5319 }
5320
5321 // A preliminary value, may be tweaked below
5322 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5323
5324 // recompute cursor position
5325 // and send to interested plugins if the mouse is actually in this window
5326 int mouseX = mouse_x;
5327 int mouseY = mouse_y;
5328 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5329 (mouseY < VPoint.pix_height)) {
5330 double lat, lon;
5331 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5332 m_cursor_lat = lat;
5333 m_cursor_lon = lon;
5334 SendCursorLatLonToAllPlugIns(lat, lon);
5335 }
5336
5337 if (!VPoint.b_quilt && m_singleChart) {
5338 VPoint.SetBoxes();
5339
5340 // Allow the chart to adjust the new ViewPort for performance optimization
5341 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5342 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5343
5344 // If there is a sensible change in the chart render, refresh the whole
5345 // screen
5346 if ((!m_cache_vp.IsValid()) ||
5347 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5348 Refresh(false);
5349 b_ret = true;
5350 } else {
5351 wxPoint cp_last, cp_this;
5352 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5353 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5354
5355 if (cp_last != cp_this) {
5356 Refresh(false);
5357 b_ret = true;
5358 }
5359 }
5360 // Create the stack
5361 if (m_pCurrentStack) {
5362 assert(ChartData != 0);
5363 int current_db_index;
5364 current_db_index =
5365 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5366
5367 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5368 m_groupIndex);
5369 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5370 }
5371
5372 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5373 }
5374
5375 // Handle the quilted case
5376 if (VPoint.b_quilt) {
5377 if (last_vp.view_scale_ppm != scale_ppm)
5378 m_pQuilt->InvalidateAllQuiltPatchs();
5379
5380 // Create the quilt
5381 if (ChartData /*&& ChartData->IsValid()*/) {
5382 if (!m_pCurrentStack) return false;
5383
5384 int current_db_index;
5385 current_db_index =
5386 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5387
5388 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5389 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5390
5391 // Check to see if the current quilt reference chart is in the new stack
5392 int current_ref_stack_index = -1;
5393 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5394 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5395 current_ref_stack_index = i;
5396 }
5397
5398 if (g_bFullScreenQuilt) {
5399 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5400 }
5401
5402 // We might need a new Reference Chart
5403 bool b_needNewRef = false;
5404
5405 // If the new stack does not contain the current ref chart....
5406 if ((-1 == current_ref_stack_index) &&
5407 (m_pQuilt->GetRefChartdbIndex() >= 0))
5408 b_needNewRef = true;
5409
5410 // Would the current Ref Chart be excessively underzoomed?
5411 // We need to check this here to be sure, since we cannot know where the
5412 // reference chart was assigned. For instance, the reference chart may
5413 // have been selected from the config file, or from a long jump with a
5414 // chart family switch implicit. Anyway, we check to be sure....
5415 bool renderable = true;
5416 ChartBase *referenceChart =
5417 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5418 if (referenceChart) {
5419 double chartMaxScale = referenceChart->GetNormalScaleMax(
5420 GetCanvasScaleFactor(), GetCanvasWidth());
5421 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5422 }
5423 if (!renderable) b_needNewRef = true;
5424
5425 // Need new refchart?
5426 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5427 const ChartTableEntry &cte_ref =
5428 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5429 int target_scale = cte_ref.GetScale();
5430 int target_type = cte_ref.GetChartType();
5431 int candidate_stack_index;
5432
5433 // reset the ref chart in a way that does not lead to excessive
5434 // underzoom, for performance reasons Try to find a chart that is the
5435 // same type, and has a scale of just smaller than the current ref
5436 // chart
5437
5438 candidate_stack_index = 0;
5439 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5440 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5441 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5442 int candidate_scale = cte_candidate.GetScale();
5443 int candidate_type = cte_candidate.GetChartType();
5444
5445 if ((candidate_scale >= target_scale) &&
5446 (candidate_type == target_type)) {
5447 bool renderable = true;
5448 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5449 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5450 if (tentative_referenceChart) {
5451 double chartMaxScale =
5452 tentative_referenceChart->GetNormalScaleMax(
5453 GetCanvasScaleFactor(), GetCanvasWidth());
5454 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5455 }
5456
5457 if (renderable) break;
5458 }
5459
5460 candidate_stack_index++;
5461 }
5462
5463 // If that did not work, look for a chart of just larger scale and
5464 // same type
5465 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5466 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5467 while (candidate_stack_index >= 0) {
5468 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5469 if (idx >= 0) {
5470 const ChartTableEntry &cte_candidate =
5471 ChartData->GetChartTableEntry(idx);
5472 int candidate_scale = cte_candidate.GetScale();
5473 int candidate_type = cte_candidate.GetChartType();
5474
5475 if ((candidate_scale <= target_scale) &&
5476 (candidate_type == target_type))
5477 break;
5478 }
5479 candidate_stack_index--;
5480 }
5481 }
5482
5483 // and if that did not work, chose stack entry 0
5484 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5485 (candidate_stack_index < 0))
5486 candidate_stack_index = 0;
5487
5488 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5489
5490 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5491 }
5492
5493 if (!g_bopengl) {
5494 // Preset the VPoint projection type to match what the quilt projection
5495 // type will be
5496 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5497
5498 // Always keep the default Mercator projection if the reference chart is
5499 // not in the PatchList or the scale is too small for it to render.
5500
5501 bool renderable = true;
5502 ChartBase *referenceChart =
5503 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5504 if (referenceChart) {
5505 double chartMaxScale = referenceChart->GetNormalScaleMax(
5506 GetCanvasScaleFactor(), GetCanvasWidth());
5507 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5508 proj = ChartData->GetDBChartProj(ref_db_index);
5509 } else
5510 proj = PROJECTION_MERCATOR;
5511
5512 VPoint.b_MercatorProjectionOverride =
5513 (m_pQuilt->GetnCharts() == 0 || !renderable);
5514
5515 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5516
5517 VPoint.SetProjectionType(proj);
5518 }
5519
5520 VPoint.SetBoxes();
5521
5522 // If this quilt will be a perceptible delta from the existing quilt,
5523 // then refresh the entire screen
5524 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5525 // Allow the quilt to adjust the new ViewPort for performance
5526 // optimization This will normally be only a fractional (i.e.
5527 // sub-pixel) adjustment...
5528 if (b_adjust) {
5529 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5530 }
5531
5532 // ChartData->ClearCacheInUseFlags();
5533 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5534
5535 // wxStopWatch sw;
5536
5537#ifdef __ANDROID__
5538 // This is an optimization for panning on touch screen systems.
5539 // The quilt composition is deferred until the OnPaint() message gets
5540 // finally removed and processed from the message queue.
5541 // Takes advantage of the fact that touch-screen pan gestures are
5542 // usually short in distance,
5543 // so not requiring a full quilt rebuild until the pan gesture is
5544 // complete.
5545 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5546 // qDebug() << "Force compose";
5547 m_pQuilt->Compose(VPoint);
5548 } else {
5549 m_pQuilt->Invalidate();
5550 }
5551#else
5552 m_pQuilt->Compose(VPoint);
5553#endif
5554
5555 // printf("comp time %ld\n", sw.Time());
5556
5557 // If the extended chart stack has changed, invalidate any cached
5558 // render bitmap
5559 // if(m_pQuilt->GetXStackHash() != hash1) {
5560 // m_bm_cache_vp.Invalidate();
5561 // InvalidateGL();
5562 // }
5563
5564 ChartData->PurgeCacheUnusedCharts(0.7);
5565
5566 if (b_refresh) Refresh(false);
5567
5568 b_ret = true;
5569 }
5570 }
5571
5572 VPoint.skew = 0.; // Quilting supports 0 Skew
5573 } else if (!g_bopengl) {
5574 OcpnProjType projection = PROJECTION_UNKNOWN;
5575 if (m_singleChart) // viewport projection must match chart projection
5576 // without opengl
5577 projection = m_singleChart->GetChartProjectionType();
5578 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5579 VPoint.SetProjectionType(projection);
5580 }
5581
5582 // Has the Viewport projection changed? If so, invalidate the vp
5583 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5584 m_cache_vp.Invalidate();
5585 InvalidateGL();
5586 }
5587
5588 UpdateCanvasControlBar(); // Refresh the Piano
5589
5590 VPoint.chart_scale = 1.0; // fallback default value
5591
5592 /*if (!VPoint.GetBBox().GetValid())*/ VPoint.SetBoxes();
5593
5594 if (VPoint.GetBBox().GetValid()) {
5595 // Update the viewpoint reference scale
5596 if (m_singleChart)
5597 VPoint.ref_scale = m_singleChart->GetNativeScale();
5598 else {
5599#ifdef __ANDROID__
5600 // This is an optimization for panning on touch screen systems.
5601 // See above.
5602 // Quilt might not be fully composed at this point, so for cm93
5603 // the reference scale may not be known.
5604 // In this case, do not update the VP ref_scale.
5605 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5606 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5607 }
5608#else
5609 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5610#endif
5611 }
5612
5613 // Calculate the on-screen displayed actual scale
5614 // by a simple traverse northward from the center point
5615 // of roughly one eighth of the canvas height
5616 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5617
5618 double delta_check =
5619 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5620 delta_check /= 8.;
5621
5622 double check_point = wxMin(89., VPoint.clat);
5623
5624 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5625
5626 double rhumbDist;
5627 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5628 VPoint.clon, 0, &rhumbDist);
5629
5630 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5631 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5632 // Calculate the distance between r1 and r in physical pixels.
5633 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5634 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5635
5636 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5637
5638 // A fall back in case of very high zoom-out, giving delta_y == 0
5639 // which can probably only happen with vector charts
5640 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5641
5642 // Another fallback, for highly zoomed out charts
5643 // This adjustment makes the displayed TrueScale correspond to the
5644 // same algorithm used to calculate the chart zoom-out limit for
5645 // ChartDummy.
5646 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5647
5648 if (m_true_scale_ppm)
5649 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5650 else
5651 VPoint.chart_scale = 1.0;
5652
5653 // Create a nice renderable string
5654 double round_factor = 1000.;
5655 if (VPoint.chart_scale <= 1000.)
5656 round_factor = 10.;
5657 else if (VPoint.chart_scale <= 10000.)
5658 round_factor = 100.;
5659 else if (VPoint.chart_scale <= 100000.)
5660 round_factor = 1000.;
5661
5662 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5663 double retina_coef = 1;
5664#ifdef ocpnUSE_GL
5665#ifdef __WXOSX__
5666 if (g_bopengl) {
5667 retina_coef = GetContentScaleFactor();
5668 }
5669#endif
5670#endif
5671
5672 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5673 // rounded to the nearest 10, 100 or 1000.
5674 //
5675 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5676 // true_scale_display. That does not make sense. The chart scale should be
5677 // the same as the true scale within the limits of the rounding factor.
5678 double true_scale_display =
5679 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5680 wxString text;
5681
5682 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5683
5684 if (m_displayed_scale_factor > 10.0)
5685 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5686 m_displayed_scale_factor);
5687 else if (m_displayed_scale_factor > 1.0)
5688 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5689 m_displayed_scale_factor);
5690 else if (m_displayed_scale_factor > 0.1) {
5691 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5692 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5693 } else if (m_displayed_scale_factor > 0.01) {
5694 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5695 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5696 } else {
5697 text.Printf(
5698 "%s %4.0f (---)", _("Scale"),
5699 true_scale_display); // Generally, no chart, so no chart scale factor
5700 }
5701
5702 m_scaleValue = true_scale_display;
5703 m_scaleText = text;
5704 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5705
5706 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5707 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5708 // Check to see if the text will fit in the StatusBar field...
5709 bool b_noshow = false;
5710 {
5711 int w = 0;
5712 int h;
5713 wxClientDC dc(parent_frame->GetStatusBar());
5714 if (dc.IsOk()) {
5715 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5716 dc.SetFont(*templateFont);
5717 dc.GetTextExtent(text, &w, &h);
5718
5719 // If text is too long for the allocated field, try to reduce the text
5720 // string a bit.
5721 wxRect rect;
5722 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5723 if (w && w > rect.width) {
5724 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5725 }
5726
5727 // Test again...if too big still, then give it up.
5728 dc.GetTextExtent(text, &w, &h);
5729
5730 if (w && w > rect.width) {
5731 b_noshow = true;
5732 }
5733 }
5734 }
5735
5736 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5737 }
5738 }
5739
5740 // Maintain member vLat/vLon
5741 m_vLat = VPoint.clat;
5742 m_vLon = VPoint.clon;
5743
5744 return b_ret;
5745}
5746
5747// Static Icon definitions for some symbols requiring
5748// scaling/rotation/translation Very specific wxDC draw commands are
5749// necessary to properly render these icons...See the code in
5750// ShipDraw()
5751
5752// This icon was adapted and scaled from the S52 Presentation Library
5753// version 3_03.
5754// Symbol VECGND02
5755
5756static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5757
5758// This ownship icon was adapted and scaled from the S52 Presentation
5759// Library version 3_03 Symbol OWNSHP05
5760static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5761 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5762
5763wxColour ChartCanvas::PredColor() {
5764 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5765 // visibility.
5766 if (SHIP_NORMAL == m_ownship_state)
5767 return GetGlobalColor("URED");
5768
5769 else if (SHIP_LOWACCURACY == m_ownship_state)
5770 return GetGlobalColor("YELO1");
5771
5772 return GetGlobalColor("NODTA");
5773}
5774
5775wxColour ChartCanvas::ShipColor() {
5776 // Establish ship color
5777 // It changes color based on GPS and Chart accuracy/availability
5778
5779 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5780
5781 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5782
5783 return GetGlobalColor("URED"); // default is OK
5784}
5785
5786void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5787 wxPoint2DDouble lShipMidPoint) {
5788 dc.SetPen(wxPen(PredColor(), 2));
5789
5790 if (SHIP_NORMAL == m_ownship_state)
5791 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5792 else
5793 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5794
5795 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5796 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5797
5798 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5799 lShipMidPoint.m_y);
5800 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5801 lShipMidPoint.m_y + 12);
5802}
5803
5804void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5805 wxPoint GPSOffsetPixels,
5806 wxPoint2DDouble lGPSPoint) {
5807 // if (m_animationActive) return;
5808 // Develop a uniform length for course predictor line dash length, based on
5809 // physical display size Use this reference length to size all other graphics
5810 // elements
5811 float ref_dim = m_display_size_mm / 24;
5812 ref_dim = wxMin(ref_dim, 12);
5813 ref_dim = wxMax(ref_dim, 6);
5814
5815 wxColour cPred;
5816 cPred.Set(g_cog_predictor_color);
5817 if (cPred == wxNullColour) cPred = PredColor();
5818
5819 // Establish some graphic element line widths dependent on the platform
5820 // display resolution
5821 // double nominal_line_width_pix = wxMax(1.0,
5822 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5823 // not less than 1 pixel
5824 double nominal_line_width_pix = wxMax(
5825 1.0,
5826 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5827
5828 // If the calculated value is greater than the config file spec value, then
5829 // use it.
5830 if (nominal_line_width_pix > g_cog_predictor_width)
5831 g_cog_predictor_width = nominal_line_width_pix;
5832
5833 // Calculate ownship Position Predictor
5834 wxPoint lPredPoint, lHeadPoint;
5835
5836 float pCog = std::isnan(gCog) ? 0 : gCog;
5837 float pSog = std::isnan(gSog) ? 0 : gSog;
5838
5839 double pred_lat, pred_lon;
5840 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5841 &pred_lat, &pred_lon);
5842 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5843
5844 // test to catch the case where COG/HDG line crosses the screen
5845 LLBBox box;
5846
5847 // Should we draw the Head vector?
5848 // Compare the points lHeadPoint and lPredPoint
5849 // If they differ by more than n pixels, and the head vector is valid, then
5850 // render the head vector
5851
5852 float ndelta_pix = 10.;
5853 double hdg_pred_lat, hdg_pred_lon;
5854 bool b_render_hdt = false;
5855 if (!std::isnan(gHdt)) {
5856 // Calculate ownship Heading pointer as a predictor
5857 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5858 &hdg_pred_lon);
5859 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5860 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5861 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5862 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5863 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5864 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5865 }
5866 }
5867
5868 // draw course over ground if they are longer than the ship
5869 wxPoint lShipMidPoint;
5870 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5871 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5872 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5873 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5874
5875 if (lpp >= img_height / 2) {
5876 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5877 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5878 !std::isnan(gSog)) {
5879 // COG Predictor
5880 float dash_length = ref_dim;
5881 wxDash dash_long[2];
5882 dash_long[0] =
5883 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
5884 g_cog_predictor_width); // Long dash , in mm <---------+
5885 dash_long[1] = dash_long[0] / 2.0; // Short gap
5886
5887 // On ultra-hi-res displays, do not allow the dashes to be greater than
5888 // 250, since it is defined as (char)
5889 if (dash_length > 250.) {
5890 dash_long[0] = 250. / g_cog_predictor_width;
5891 dash_long[1] = dash_long[0] / 2;
5892 }
5893
5894 wxPen ppPen2(cPred, g_cog_predictor_width,
5895 (wxPenStyle)g_cog_predictor_style);
5896 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5897 ppPen2.SetDashes(2, dash_long);
5898 dc.SetPen(ppPen2);
5899 dc.StrokeLine(
5900 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5901 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
5902
5903 if (g_cog_predictor_width > 1) {
5904 float line_width = g_cog_predictor_width / 3.;
5905
5906 wxDash dash_long3[2];
5907 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
5908 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
5909
5910 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
5911 (wxPenStyle)g_cog_predictor_style);
5912 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
5913 ppPen3.SetDashes(2, dash_long3);
5914 dc.SetPen(ppPen3);
5915 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
5916 lGPSPoint.m_y + GPSOffsetPixels.y,
5917 lPredPoint.x + GPSOffsetPixels.x,
5918 lPredPoint.y + GPSOffsetPixels.y);
5919 }
5920
5921 if (g_cog_predictor_endmarker) {
5922 // Prepare COG predictor endpoint icon
5923 double png_pred_icon_scale_factor = .4;
5924 if (g_ShipScaleFactorExp > 1.0)
5925 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5926 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
5927
5928 wxPoint icon[4];
5929
5930 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
5931 (float)(lPredPoint.x - lShipMidPoint.x));
5932 cog_rad += (float)PI;
5933
5934 for (int i = 0; i < 4; i++) {
5935 int j = i * 2;
5936 double pxa = (double)(s_png_pred_icon[j]);
5937 double pya = (double)(s_png_pred_icon[j + 1]);
5938
5939 pya *= png_pred_icon_scale_factor;
5940 pxa *= png_pred_icon_scale_factor;
5941
5942 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
5943 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
5944
5945 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
5946 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
5947 }
5948
5949 // Render COG endpoint icon
5950 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
5951 wxPENSTYLE_SOLID);
5952 dc.SetPen(ppPen1);
5953 dc.SetBrush(wxBrush(cPred));
5954
5955 dc.StrokePolygon(4, icon);
5956 }
5957 }
5958 }
5959
5960 // HDT Predictor
5961 if (b_render_hdt) {
5962 float hdt_dash_length = ref_dim * 0.4;
5963
5964 cPred.Set(g_ownship_HDTpredictor_color);
5965 if (cPred == wxNullColour) cPred = PredColor();
5966 float hdt_width =
5967 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
5968 : g_cog_predictor_width * 0.8);
5969 wxDash dash_short[2];
5970 dash_short[0] =
5971 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
5972 hdt_width); // Short dash , in mm <---------+
5973 dash_short[1] =
5974 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
5975 hdt_width); // Short gap |
5976
5977 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
5978 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
5979 ppPen2.SetDashes(2, dash_short);
5980
5981 dc.SetPen(ppPen2);
5982 dc.StrokeLine(
5983 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
5984 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
5985
5986 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
5987 dc.SetPen(ppPen1);
5988 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
5989
5990 if (g_ownship_HDTpredictor_endmarker) {
5991 double nominal_circle_size_pixels = wxMax(
5992 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
5993
5994 // Scale the circle to ChartScaleFactor, slightly softened....
5995 if (g_ShipScaleFactorExp > 1.0)
5996 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
5997
5998 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
5999 lHeadPoint.y + GPSOffsetPixels.y,
6000 nominal_circle_size_pixels / 2);
6001 }
6002 }
6003
6004 // Draw radar rings if activated
6005 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6006 double factor = 1.00;
6007 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6008 factor = 1 / 1.852;
6009 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6010 if (std::isnan(gSog))
6011 factor = 0.0;
6012 else
6013 factor = gSog / 60;
6014 }
6015 factor *= g_fNavAidRadarRingsStep;
6016
6017 double tlat, tlon;
6018 wxPoint r;
6019 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6020 GetCanvasPointPix(tlat, tlon, &r);
6021
6022 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6023 pow((double)(lGPSPoint.m_y - r.y), 2));
6024 int pix_radius = (int)lpp;
6025
6026 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6027
6028 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6029
6030 dc.SetPen(ppPen1);
6031 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6032
6033 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6034 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6035 }
6036}
6037
6038void ChartCanvas::ComputeShipScaleFactor(
6039 float icon_hdt, int ownShipWidth, int ownShipLength,
6040 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6041 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6042 float screenResolution = m_pix_per_mm;
6043
6044 // Calculate the true ship length in exact pixels
6045 double ship_bow_lat, ship_bow_lon;
6046 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6047 &ship_bow_lat, &ship_bow_lon);
6048 wxPoint lShipBowPoint;
6049 wxPoint2DDouble b_point =
6050 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6051 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6052
6053 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6054 powf((float)(b_point.m_y - a_point.m_y), 2));
6055
6056 // And in mm
6057 float shipLength_mm = shipLength_px / screenResolution;
6058
6059 // Set minimum ownship drawing size
6060 float ownship_min_mm = g_n_ownship_min_mm;
6061 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6062
6063 // Calculate Nautical Miles distance from midships to gps antenna
6064 float hdt_ant = icon_hdt + 180.;
6065 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6066 float dx = g_n_gps_antenna_offset_x / 1852.;
6067 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6068 {
6069 hdt_ant = icon_hdt;
6070 dy = -dy;
6071 }
6072
6073 // If the drawn ship size is going to be clamped, adjust the gps antenna
6074 // offsets
6075 if (shipLength_mm < ownship_min_mm) {
6076 dy /= shipLength_mm / ownship_min_mm;
6077 dx /= shipLength_mm / ownship_min_mm;
6078 }
6079
6080 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6081
6082 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6083 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6084 &ship_mid_lon1);
6085
6086 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6087 &lShipMidPoint);
6088
6089 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6090 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6091
6092 float scale_factor = shipLength_px / ownShipLength;
6093
6094 // Calculate a scale factor that would produce a reasonably sized icon
6095 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6096
6097 // And choose the correct one
6098 scale_factor = wxMax(scale_factor, scale_factor_min);
6099
6100 scale_factor_y = scale_factor;
6101 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6102 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6103}
6104
6105void ChartCanvas::ShipDraw(ocpnDC &dc) {
6106 if (!GetVP().IsValid()) return;
6107
6108 wxPoint GPSOffsetPixels(0, 0);
6109 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6110
6111 // COG/SOG may be undefined in NMEA data stream
6112 float pCog = std::isnan(gCog) ? 0 : gCog;
6113 float pSog = std::isnan(gSog) ? 0 : gSog;
6114
6115 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6116
6117 lShipMidPoint = lGPSPoint;
6118
6119 // Draw the icon rotated to the COG
6120 // or to the Hdt if available
6121 float icon_hdt = pCog;
6122 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6123
6124 // COG may be undefined in NMEA data stream
6125 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6126
6127 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6128 // predictor
6129 double osd_head_lat, osd_head_lon;
6130 wxPoint osd_head_point;
6131
6132 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6133 &osd_head_lon);
6134
6135 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6136
6137 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6138 (float)(osd_head_point.x - lShipMidPoint.m_x));
6139 icon_rad += (float)PI;
6140
6141 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6142
6143 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6144 // nominal size and is just barely outside the viewport ....
6145 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6146
6147 // TODO: fix to include actual size of boat that will be rendered
6148 int img_height = 0;
6149 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6150 if (GetVP().chart_scale >
6151 300000) // According to S52, this should be 50,000
6152 {
6153 ShipDrawLargeScale(dc, lShipMidPoint);
6154 img_height = 20;
6155 } else {
6156 wxImage pos_image;
6157
6158 // Substitute user ownship image if found
6159 if (m_pos_image_user)
6160 pos_image = m_pos_image_user->Copy();
6161 else if (SHIP_NORMAL == m_ownship_state)
6162 pos_image = m_pos_image_red->Copy();
6163 if (SHIP_LOWACCURACY == m_ownship_state)
6164 pos_image = m_pos_image_yellow->Copy();
6165 else if (SHIP_NORMAL != m_ownship_state)
6166 pos_image = m_pos_image_grey->Copy();
6167
6168 // Substitute user ownship image if found
6169 if (m_pos_image_user) {
6170 pos_image = m_pos_image_user->Copy();
6171
6172 if (SHIP_LOWACCURACY == m_ownship_state)
6173 pos_image = m_pos_image_user_yellow->Copy();
6174 else if (SHIP_NORMAL != m_ownship_state)
6175 pos_image = m_pos_image_user_grey->Copy();
6176 }
6177
6178 img_height = pos_image.GetHeight();
6179
6180 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6181 g_OwnShipIconType > 0) // use large ship
6182 {
6183 int ownShipWidth = 22; // Default values from s_ownship_icon
6184 int ownShipLength = 84;
6185 if (g_OwnShipIconType == 1) {
6186 ownShipWidth = pos_image.GetWidth();
6187 ownShipLength = pos_image.GetHeight();
6188 }
6189
6190 float scale_factor_x, scale_factor_y;
6191 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6192 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6193 scale_factor_x, scale_factor_y);
6194
6195 if (g_OwnShipIconType == 1) { // Scaled bitmap
6196 pos_image.Rescale(ownShipWidth * scale_factor_x,
6197 ownShipLength * scale_factor_y,
6198 wxIMAGE_QUALITY_HIGH);
6199 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6200 wxImage rot_image =
6201 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6202
6203 // Simple sharpening algorithm.....
6204 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6205 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6206 if (rot_image.GetAlpha(ip, jp) > 64)
6207 rot_image.SetAlpha(ip, jp, 255);
6208
6209 wxBitmap os_bm(rot_image);
6210
6211 int w = os_bm.GetWidth();
6212 int h = os_bm.GetHeight();
6213 img_height = h;
6214
6215 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6216 lShipMidPoint.m_y - h / 2, true);
6217
6218 // Maintain dirty box,, missing in __WXMSW__ library
6219 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6220 lShipMidPoint.m_y - h / 2);
6221 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6222 lShipMidPoint.m_y - h / 2 + h);
6223 }
6224
6225 else if (g_OwnShipIconType == 2) { // Scaled Vector
6226 wxPoint ownship_icon[10];
6227
6228 for (int i = 0; i < 10; i++) {
6229 int j = i * 2;
6230 float pxa = (float)(s_ownship_icon[j]);
6231 float pya = (float)(s_ownship_icon[j + 1]);
6232 pya *= scale_factor_y;
6233 pxa *= scale_factor_x;
6234
6235 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6236 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6237
6238 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6239 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6240 }
6241
6242 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6243 dc.SetPen(ppPen1);
6244 dc.SetBrush(wxBrush(ShipColor()));
6245
6246 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6247
6248 // draw reference point (midships) cross
6249 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6250 ownship_icon[7].y);
6251 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6252 ownship_icon[9].y);
6253 }
6254
6255 img_height = ownShipLength * scale_factor_y;
6256
6257 // Reference point, where the GPS antenna is
6258 int circle_rad = 3;
6259 if (m_pos_image_user) circle_rad = 1;
6260
6261 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6262 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6263 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6264 } else { // Fixed bitmap icon.
6265 /* non opengl, or suboptimal opengl via ocpndc: */
6266 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6267 wxImage rot_image =
6268 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6269
6270 // Simple sharpening algorithm.....
6271 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6272 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6273 if (rot_image.GetAlpha(ip, jp) > 64)
6274 rot_image.SetAlpha(ip, jp, 255);
6275
6276 wxBitmap os_bm(rot_image);
6277
6278 if (g_ShipScaleFactorExp > 1) {
6279 wxImage scaled_image = os_bm.ConvertToImage();
6280 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6281 1.0; // soften the scale factor a bit
6282 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6283 scaled_image.GetHeight() * factor,
6284 wxIMAGE_QUALITY_HIGH));
6285 }
6286 int w = os_bm.GetWidth();
6287 int h = os_bm.GetHeight();
6288 img_height = h;
6289
6290 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6291 lShipMidPoint.m_y - h / 2, true);
6292
6293 // Reference point, where the GPS antenna is
6294 int circle_rad = 3;
6295 if (m_pos_image_user) circle_rad = 1;
6296
6297 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6298 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6299 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6300
6301 // Maintain dirty box,, missing in __WXMSW__ library
6302 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6303 lShipMidPoint.m_y - h / 2);
6304 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6305 lShipMidPoint.m_y - h / 2 + h);
6306 }
6307 } // ownship draw
6308 }
6309
6310 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6311}
6312
6313/* @ChartCanvas::CalcGridSpacing
6314 **
6315 ** Calculate the major and minor spacing between the lat/lon grid
6316 **
6317 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6318 *window
6319 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6320 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6321 ** @return [void]
6322 */
6323void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6324 float &MinorSpacing) {
6325 // table for calculating the distance between the grids
6326 // [0] view_scale ppm
6327 // [1] spacing between major grid lines in degrees
6328 // [2] spacing between minor grid lines in degrees
6329 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6330 {.000001f, 45.0f, 15.0f},
6331 {.0002f, 30.0f, 10.0f},
6332 {.0003f, 10.0f, 2.0f},
6333 {.0008f, 5.0f, 1.0f},
6334 {.001f, 2.0f, 30.0f / 60.0f},
6335 {.003f, 1.0f, 20.0f / 60.0f},
6336 {.006f, 0.5f, 10.0f / 60.0f},
6337 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6338 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6339 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6340 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6341 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6342 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6343 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6344 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6345
6346 unsigned int tabi;
6347 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6348 if (view_scale_ppm < lltab[tabi][0]) break;
6349 MajorSpacing = lltab[tabi][1]; // major latitude distance
6350 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6351 return;
6352}
6353/* @ChartCanvas::CalcGridText *************************************
6354 **
6355 ** Calculates text to display at the major grid lines
6356 **
6357 ** @param [r] latlon [float] latitude or longitude of grid line
6358 ** @param [r] spacing [float] distance between two major grid lines
6359 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6360 **
6361 ** @return
6362 */
6363
6364wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6365 int deg = (int)fabs(latlon); // degrees
6366 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6367 char postfix;
6368
6369 // calculate postfix letter (NSEW)
6370 if (latlon > 0.0) {
6371 if (bPostfix) {
6372 postfix = 'N';
6373 } else {
6374 postfix = 'E';
6375 }
6376 } else if (latlon < 0.0) {
6377 if (bPostfix) {
6378 postfix = 'S';
6379 } else {
6380 postfix = 'W';
6381 }
6382 } else {
6383 postfix = ' '; // no postfix for equator and greenwich
6384 }
6385 // calculate text, display minutes only if spacing is smaller than one degree
6386
6387 wxString ret;
6388 if (spacing >= 1.0) {
6389 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6390 } else if (spacing >= (1.0 / 60.0)) {
6391 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6392 } else {
6393 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6394 }
6395
6396 return ret;
6397}
6398
6399/* @ChartCanvas::GridDraw *****************************************
6400 **
6401 ** Draws major and minor Lat/Lon Grid on the chart
6402 ** - distance between Grid-lm ines are calculated automatic
6403 ** - major grid lines will be across the whole chart window
6404 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6405 **
6406 ** @param [w] dc [wxDC&] the wx drawing context
6407 **
6408 ** @return [void]
6409 ************************************************************************/
6410void ChartCanvas::GridDraw(ocpnDC &dc) {
6411 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6412
6413 double nlat, elon, slat, wlon;
6414 float lat, lon;
6415 float dlon;
6416 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6417 wxCoord w, h;
6418 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6419 dc.SetPen(GridPen);
6420 if (!m_pgridFont) SetupGridFont();
6421 dc.SetFont(*m_pgridFont);
6422 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6423
6424 w = m_canvas_width;
6425 h = m_canvas_height;
6426
6427 GetCanvasPixPoint(0, 0, nlat,
6428 wlon); // get lat/lon of upper left point of the window
6429 GetCanvasPixPoint(w, h, slat,
6430 elon); // get lat/lon of lower right point of the window
6431 dlon =
6432 elon -
6433 wlon; // calculate how many degrees of longitude are shown in the window
6434 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6435 {
6436 dlon = dlon + 360.0;
6437 }
6438 // calculate distance between latitude grid lines
6439 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6440
6441 // calculate position of first major latitude grid line
6442 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6443
6444 // Draw Major latitude grid lines and text
6445 while (lat < nlat) {
6446 wxPoint r;
6447 wxString st =
6448 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6449 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6450 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6451 dc.DrawText(st, 0, r.y); // draw text
6452 lat = lat + gridlatMajor;
6453
6454 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6455 }
6456
6457 // calculate position of first minor latitude grid line
6458 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6459
6460 // Draw minor latitude grid lines
6461 while (lat < nlat) {
6462 wxPoint r;
6463 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6464 dc.DrawLine(0, r.y, 10, r.y, false);
6465 dc.DrawLine(w - 10, r.y, w, r.y, false);
6466 lat = lat + gridlatMinor;
6467 }
6468
6469 // calculate distance between grid lines
6470 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6471
6472 // calculate position of first major latitude grid line
6473 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6474
6475 // draw major longitude grid lines
6476 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6477 wxPoint r;
6478 wxString st = CalcGridText(lon, gridlonMajor, false);
6479 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6480 dc.DrawLine(r.x, 0, r.x, h, false);
6481 dc.DrawText(st, r.x, 0);
6482 lon = lon + gridlonMajor;
6483 if (lon > 180.0) {
6484 lon = lon - 360.0;
6485 }
6486
6487 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6488 }
6489
6490 // calculate position of first minor longitude grid line
6491 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6492 // draw minor longitude grid lines
6493 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6494 wxPoint r;
6495 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6496 dc.DrawLine(r.x, 0, r.x, 10, false);
6497 dc.DrawLine(r.x, h - 10, r.x, h, false);
6498 lon = lon + gridlonMinor;
6499 if (lon > 180.0) {
6500 lon = lon - 360.0;
6501 }
6502 }
6503}
6504
6505void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6506 if (0 ) {
6507 double blat, blon, tlat, tlon;
6508 wxPoint r;
6509
6510 int x_origin = m_bDisplayGrid ? 60 : 20;
6511 int y_origin = m_canvas_height - 50;
6512
6513 float dist;
6514 int count;
6515 wxPen pen1, pen2;
6516
6517 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6518 {
6519 dist = 10.0;
6520 count = 5;
6521 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6522 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6523 } else // Draw 1 mile scale as SCALEB10
6524 {
6525 dist = 1.0;
6526 count = 10;
6527 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6528 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6529 }
6530
6531 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6532 double rotation = -VPoint.rotation;
6533
6534 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6535 GetCanvasPointPix(tlat, tlon, &r);
6536 int l1 = (y_origin - r.y) / count;
6537
6538 for (int i = 0; i < count; i++) {
6539 int y = l1 * i;
6540 if (i & 1)
6541 dc.SetPen(pen1);
6542 else
6543 dc.SetPen(pen2);
6544
6545 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6546 }
6547 } else {
6548 double blat, blon, tlat, tlon;
6549
6550 int x_origin = 5.0 * GetPixPerMM();
6551 int chartbar_height = GetChartbarHeight();
6552 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6553 // if (style->chartStatusWindowTransparent)
6554 // chartbar_height = 0;
6555 int y_origin = m_canvas_height - chartbar_height - 5;
6556#ifdef __WXOSX__
6557 if (!g_bopengl)
6558 y_origin =
6559 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6560#endif
6561
6562 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6563 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6564
6565 double d;
6566 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6567 d /= 2;
6568
6569 int unit = g_iDistanceFormat;
6570 if (d < .5 &&
6571 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6572 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6573
6574 // nice number
6575 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6576 float places = floor(logdist), rem = logdist - places;
6577 dist = pow(10, places);
6578
6579 if (rem < .2)
6580 dist /= 5;
6581 else if (rem < .5)
6582 dist /= 2;
6583
6584 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6585 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6586 double rotation = -VPoint.rotation;
6587
6588 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6589 &tlat, &tlon);
6590 wxPoint r;
6591 GetCanvasPointPix(tlat, tlon, &r);
6592 int l1 = r.x - x_origin;
6593
6594 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6595 12); // Store this for later reference
6596
6597 dc.SetPen(pen1);
6598
6599 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6600 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6601 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6602
6603 if (!m_pgridFont) SetupGridFont();
6604 dc.SetFont(*m_pgridFont);
6605 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6606 int w, h;
6607 dc.GetTextExtent(s, &w, &h);
6608 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6609 if (g_bopengl) {
6610 w /= dpi_factor;
6611 h /= dpi_factor;
6612 }
6613 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6614 }
6615}
6616
6617void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6618 // Constants?
6619 double da_min = 2.;
6620 double da_max = 6.;
6621 double ra_min = 0.;
6622 double ra_max = 40.;
6623
6624 wxPen pen_save = dc.GetPen();
6625
6626 wxDateTime now = wxDateTime::Now();
6627
6628 dc.SetPen(pen);
6629
6630 int x0, y0, x1, y1;
6631
6632 x0 = x1 = x + radius; // Start point
6633 y0 = y1 = y;
6634 double angle = 0.;
6635 int i = 0;
6636
6637 while (angle < 360.) {
6638 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6639 angle += da;
6640
6641 if (angle > 360.) angle = 360.;
6642
6643 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6644
6645 double r;
6646 if (i & 1)
6647 r = radius + ra;
6648 else
6649 r = radius - ra;
6650
6651 x1 = (int)(x + cos(angle * PI / 180.) * r);
6652 y1 = (int)(y + sin(angle * PI / 180.) * r);
6653
6654 dc.DrawLine(x0, y0, x1, y1);
6655
6656 x0 = x1;
6657 y0 = y1;
6658
6659 i++;
6660 }
6661
6662 dc.DrawLine(x + radius, y, x1, y1); // closure
6663
6664 dc.SetPen(pen_save);
6665}
6666
6667static bool bAnchorSoundPlaying = false;
6668
6669static void onAnchorSoundFinished(void *ptr) {
6670 g_anchorwatch_sound->UnLoad();
6671 bAnchorSoundPlaying = false;
6672}
6673
6674void ChartCanvas::AlertDraw(ocpnDC &dc) {
6675 // Visual and audio alert for anchorwatch goes here
6676 bool play_sound = false;
6677 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6678 if (AnchorAlertOn1) {
6679 wxPoint TargetPoint;
6681 &TargetPoint);
6682 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6683 TargetPoint.y, 100);
6684 play_sound = true;
6685 }
6686 } else
6687 AnchorAlertOn1 = false;
6688
6689 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6690 if (AnchorAlertOn2) {
6691 wxPoint TargetPoint;
6693 &TargetPoint);
6694 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6695 TargetPoint.y, 100);
6696 play_sound = true;
6697 }
6698 } else
6699 AnchorAlertOn2 = false;
6700
6701 if (play_sound) {
6702 if (!bAnchorSoundPlaying) {
6703 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6704 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6705 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6706 if (g_anchorwatch_sound->IsOk()) {
6707 bAnchorSoundPlaying = true;
6708 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6709 g_anchorwatch_sound->Play();
6710 }
6711 }
6712 }
6713}
6714
6715void ChartCanvas::UpdateShips() {
6716 // Get the rectangle in the current dc which bounds the "ownship" symbol
6717
6718 wxClientDC dc(this);
6719 if (!dc.IsOk()) return;
6720
6721 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6722 if (!test_bitmap.IsOk()) return;
6723
6724 wxMemoryDC temp_dc(test_bitmap);
6725
6726 temp_dc.ResetBoundingBox();
6727 temp_dc.DestroyClippingRegion();
6728 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6729
6730 // Draw the ownship on the temp_dc
6731 ocpnDC ocpndc = ocpnDC(temp_dc);
6732 ShipDraw(ocpndc);
6733
6734 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6735 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6736 if (p) {
6737 wxPoint px;
6738 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6739 ocpndc.CalcBoundingBox(px.x, px.y);
6740 }
6741 }
6742
6743 ship_draw_rect =
6744 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6745 temp_dc.MaxY() - temp_dc.MinY());
6746
6747 wxRect own_ship_update_rect = ship_draw_rect;
6748
6749 if (!own_ship_update_rect.IsEmpty()) {
6750 // The required invalidate rectangle is the union of the last drawn
6751 // rectangle and this drawn rectangle
6752 own_ship_update_rect.Union(ship_draw_last_rect);
6753 own_ship_update_rect.Inflate(2);
6754 }
6755
6756 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6757
6758 ship_draw_last_rect = ship_draw_rect;
6759
6760 temp_dc.SelectObject(wxNullBitmap);
6761}
6762
6763void ChartCanvas::UpdateAlerts() {
6764 // Get the rectangle in the current dc which bounds the detected Alert
6765 // targets
6766
6767 // Use this dc
6768 wxClientDC dc(this);
6769
6770 // Get dc boundary
6771 int sx, sy;
6772 dc.GetSize(&sx, &sy);
6773
6774 // Need a bitmap
6775 wxBitmap test_bitmap(sx, sy, -1);
6776
6777 // Create a memory DC
6778 wxMemoryDC temp_dc;
6779 temp_dc.SelectObject(test_bitmap);
6780
6781 temp_dc.ResetBoundingBox();
6782 temp_dc.DestroyClippingRegion();
6783 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6784
6785 // Draw the Alert Targets on the temp_dc
6786 ocpnDC ocpndc = ocpnDC(temp_dc);
6787 AlertDraw(ocpndc);
6788
6789 // Retrieve the drawing extents
6790 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6791 temp_dc.MaxX() - temp_dc.MinX(),
6792 temp_dc.MaxY() - temp_dc.MinY());
6793
6794 if (!alert_rect.IsEmpty())
6795 alert_rect.Inflate(2); // clear all drawing artifacts
6796
6797 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6798 // The required invalidate rectangle is the union of the last drawn
6799 // rectangle and this drawn rectangle
6800 wxRect alert_update_rect = alert_draw_rect;
6801 alert_update_rect.Union(alert_rect);
6802
6803 // Invalidate the rectangular region
6804 RefreshRect(alert_update_rect, false);
6805 }
6806
6807 // Save this rectangle for next time
6808 alert_draw_rect = alert_rect;
6809
6810 temp_dc.SelectObject(wxNullBitmap); // clean up
6811}
6812
6813void ChartCanvas::UpdateAIS() {
6814 if (!g_pAIS) return;
6815
6816 // Get the rectangle in the current dc which bounds the detected AIS targets
6817
6818 // Use this dc
6819 wxClientDC dc(this);
6820
6821 // Get dc boundary
6822 int sx, sy;
6823 dc.GetSize(&sx, &sy);
6824
6825 wxRect ais_rect;
6826
6827 // How many targets are there?
6828
6829 // If more than "some number", it will be cheaper to refresh the entire
6830 // screen than to build update rectangles for each target.
6831 if (g_pAIS->GetTargetList().size() > 10) {
6832 ais_rect = wxRect(0, 0, sx, sy); // full screen
6833 } else {
6834 // Need a bitmap
6835 wxBitmap test_bitmap(sx, sy, -1);
6836
6837 // Create a memory DC
6838 wxMemoryDC temp_dc;
6839 temp_dc.SelectObject(test_bitmap);
6840
6841 temp_dc.ResetBoundingBox();
6842 temp_dc.DestroyClippingRegion();
6843 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6844
6845 // Draw the AIS Targets on the temp_dc
6846 ocpnDC ocpndc = ocpnDC(temp_dc);
6847 AISDraw(ocpndc, GetVP(), this);
6848 AISDrawAreaNotices(ocpndc, GetVP(), this);
6849
6850 // Retrieve the drawing extents
6851 ais_rect =
6852 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6853 temp_dc.MaxY() - temp_dc.MinY());
6854
6855 if (!ais_rect.IsEmpty())
6856 ais_rect.Inflate(2); // clear all drawing artifacts
6857
6858 temp_dc.SelectObject(wxNullBitmap); // clean up
6859 }
6860
6861 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6862 // The required invalidate rectangle is the union of the last drawn
6863 // rectangle and this drawn rectangle
6864 wxRect ais_update_rect = ais_draw_rect;
6865 ais_update_rect.Union(ais_rect);
6866
6867 // Invalidate the rectangular region
6868 RefreshRect(ais_update_rect, false);
6869 }
6870
6871 // Save this rectangle for next time
6872 ais_draw_rect = ais_rect;
6873}
6874
6875void ChartCanvas::ToggleCPAWarn() {
6876 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6877 wxString mess;
6878 if (g_bCPAWarn) {
6879 g_bTCPA_Max = true;
6880 mess = _("ON");
6881 } else {
6882 g_bTCPA_Max = false;
6883 mess = _("OFF");
6884 }
6885 // Print to status bar if available.
6886 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
6887 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
6888 } else {
6889 if (!g_AisFirstTimeUse) {
6890 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
6891 _("CPA") + " " + mess, 4, 4);
6892 }
6893 }
6894}
6895
6896void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
6897
6898void ChartCanvas::OnSize(wxSizeEvent &event) {
6899 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
6900 // GetClientSize returns the size of the canvas area in logical pixels.
6901 GetClientSize(&m_canvas_width, &m_canvas_height);
6902
6903#ifdef __WXOSX__
6904 // Support scaled HDPI displays.
6905 m_displayScale = GetContentScaleFactor();
6906#endif
6907
6908 // Convert to physical pixels.
6909 m_canvas_width *= m_displayScale;
6910 m_canvas_height *= m_displayScale;
6911
6912 // Resize the current viewport
6913 VPoint.pix_width = m_canvas_width;
6914 VPoint.pix_height = m_canvas_height;
6915 VPoint.SetPixelScale(m_displayScale);
6916
6917 // Get some canvas metrics
6918
6919 // Rescale to current value, in order to rebuild VPoint data
6920 // structures for new canvas size
6922
6923 m_absolute_min_scale_ppm =
6924 m_canvas_width /
6925 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
6926
6927 // Inform the parent Frame that I am being resized...
6928 gFrame->ProcessCanvasResize();
6929
6930 // if MUIBar is active, size the bar
6931 // if(g_useMUI && !m_muiBar){ // rebuild if
6932 // necessary
6933 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
6934 // m_muiBarHOSize = m_muiBar->GetSize();
6935 // }
6936
6937 if (m_muiBar) {
6938 SetMUIBarPosition();
6939 UpdateFollowButtonState();
6940 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
6941 }
6942
6943 // Set up the scroll margins
6944 xr_margin = m_canvas_width * 95 / 100;
6945 xl_margin = m_canvas_width * 5 / 100;
6946 yt_margin = m_canvas_height * 5 / 100;
6947 yb_margin = m_canvas_height * 95 / 100;
6948
6949 if (m_pQuilt)
6950 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
6951
6952 // Resize the scratch BM
6953 delete pscratch_bm;
6954 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6955 m_brepaint_piano = true;
6956
6957 // Resize the Route Calculation BM
6958 m_dc_route.SelectObject(wxNullBitmap);
6959 delete proute_bm;
6960 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
6961 m_dc_route.SelectObject(*proute_bm);
6962
6963 // Resize the saved Bitmap
6964 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6965
6966 // Resize the working Bitmap
6967 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
6968
6969 // Rescale again, to capture all the changes for new canvas size
6971
6972#ifdef ocpnUSE_GL
6973 if (/*g_bopengl &&*/ m_glcc) {
6974 // FIXME (dave) This can go away?
6975 m_glcc->OnSize(event);
6976 }
6977#endif
6978
6979 FormatPianoKeys();
6980 // Invalidate the whole window
6981 ReloadVP();
6982}
6983
6984void ChartCanvas::ProcessNewGUIScale() {
6985 // m_muiBar->Hide();
6986 delete m_muiBar;
6987 m_muiBar = 0;
6988
6989 CreateMUIBar();
6990}
6991
6992void ChartCanvas::CreateMUIBar() {
6993 if (g_useMUI && !m_muiBar) { // rebuild if necessary
6994
6995 // We need to update the m_bENCGroup flag, at least for the initial creation
6996 // of a MUIBar
6997 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
6998
6999 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7000 m_muiBar->SetColorScheme(m_cs);
7001 m_muiBarHOSize = m_muiBar->m_size;
7002 }
7003
7004 if (m_muiBar) {
7005 SetMUIBarPosition();
7006 UpdateFollowButtonState();
7007 m_muiBar->UpdateDynamicValues();
7008 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7009 }
7010}
7011
7012void ChartCanvas::SetMUIBarPosition() {
7013 // if MUIBar is active, size the bar
7014 if (m_muiBar) {
7015 // We estimate the piano width based on the canvas width
7016 int pianoWidth = GetClientSize().x * 0.6f;
7017 // If the piano already exists, we can use its exact width
7018 // if(m_Piano)
7019 // pianoWidth = m_Piano->GetWidth();
7020
7021 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7022 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7023 delete m_muiBar;
7024 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7025 m_muiBar->SetColorScheme(m_cs);
7026 }
7027 }
7028
7029 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7030 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7031 delete m_muiBar;
7032 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7033 m_muiBar->SetColorScheme(m_cs);
7034 }
7035 }
7036
7037 m_muiBar->SetBestPosition();
7038 }
7039}
7040
7041void ChartCanvas::DestroyMuiBar() {
7042 if (m_muiBar) {
7043 delete m_muiBar;
7044 m_muiBar = NULL;
7045 }
7046}
7047
7048void ChartCanvas::ShowCompositeInfoWindow(
7049 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7050 if (n_charts > 0) {
7051 if (NULL == m_pCIWin) {
7052 m_pCIWin = new ChInfoWin(this);
7053 m_pCIWin->Hide();
7054 }
7055
7056 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7057 wxString s;
7058
7059 s = _("Composite of ");
7060
7061 wxString s1;
7062 s1.Printf("%d ", n_charts);
7063 if (n_charts > 1)
7064 s1 += _("charts");
7065 else
7066 s1 += _("chart");
7067 s += s1;
7068 s += '\n';
7069
7070 s1.Printf(_("Chart scale"));
7071 s1 += ": ";
7072 wxString s2;
7073 s2.Printf("1:%d\n", scale);
7074 s += s1;
7075 s += s2;
7076
7077 s1 = _("Zoom in for more information");
7078 s += s1;
7079 s += '\n';
7080
7081 int char_width = s1.Length();
7082 int char_height = 3;
7083
7084 if (g_bChartBarEx) {
7085 s += '\n';
7086 int j = 0;
7087 for (int i : index_vector) {
7088 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7089 wxString path = cte.GetFullSystemPath();
7090 s += path;
7091 s += '\n';
7092 char_height++;
7093 char_width = wxMax(char_width, path.Length());
7094 if (j++ >= 9) break;
7095 }
7096 if (j >= 9) {
7097 s += " .\n .\n .\n";
7098 char_height += 3;
7099 }
7100 s += '\n';
7101 char_height += 1;
7102
7103 char_width += 4; // Fluff
7104 }
7105
7106 m_pCIWin->SetString(s);
7107
7108 m_pCIWin->FitToChars(char_width, char_height);
7109
7110 wxPoint p;
7111 p.x = x / GetContentScaleFactor();
7112 if ((p.x + m_pCIWin->GetWinSize().x) >
7113 (m_canvas_width / GetContentScaleFactor()))
7114 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7115 m_pCIWin->GetWinSize().x) /
7116 2; // centered
7117
7118 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7119 4 - m_pCIWin->GetWinSize().y;
7120
7121 m_pCIWin->dbIndex = 0;
7122 m_pCIWin->chart_scale = 0;
7123 m_pCIWin->SetPosition(p);
7124 m_pCIWin->SetBitmap();
7125 m_pCIWin->Refresh();
7126 m_pCIWin->Show();
7127 }
7128 } else {
7129 HideChartInfoWindow();
7130 }
7131}
7132
7133void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7134 if (dbIndex >= 0) {
7135 if (NULL == m_pCIWin) {
7136 m_pCIWin = new ChInfoWin(this);
7137 m_pCIWin->Hide();
7138 }
7139
7140 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7141 wxString s;
7142 ChartBase *pc = NULL;
7143
7144 // TOCTOU race but worst case will reload chart.
7145 // need to lock it or the background spooler may evict charts in
7146 // OpenChartFromDBAndLock
7147 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7148 pc = ChartData->OpenChartFromDBAndLock(
7149 dbIndex, FULL_INIT); // this must come from cache
7150
7151 int char_width, char_height;
7152 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7153 if (pc) ChartData->UnLockCacheChart(dbIndex);
7154
7155 m_pCIWin->SetString(s);
7156 m_pCIWin->FitToChars(char_width, char_height);
7157
7158 wxPoint p;
7159 p.x = x / GetContentScaleFactor();
7160 if ((p.x + m_pCIWin->GetWinSize().x) >
7161 (m_canvas_width / GetContentScaleFactor()))
7162 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7163 m_pCIWin->GetWinSize().x) /
7164 2; // centered
7165
7166 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7167 4 - m_pCIWin->GetWinSize().y;
7168
7169 m_pCIWin->dbIndex = dbIndex;
7170 m_pCIWin->SetPosition(p);
7171 m_pCIWin->SetBitmap();
7172 m_pCIWin->Refresh();
7173 m_pCIWin->Show();
7174 }
7175 } else {
7176 HideChartInfoWindow();
7177 }
7178}
7179
7180void ChartCanvas::HideChartInfoWindow() {
7181 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7182 m_pCIWin->Hide();
7183 m_pCIWin->Destroy();
7184 m_pCIWin = NULL;
7185
7186#ifdef __ANDROID__
7187 androidForceFullRepaint();
7188#endif
7189 }
7190}
7191
7192void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7193 wxMouseEvent ev(wxEVT_MOTION);
7194 ev.m_x = mouse_x;
7195 ev.m_y = mouse_y;
7196 ev.m_leftDown = mouse_leftisdown;
7197
7198 wxEvtHandler *evthp = GetEventHandler();
7199
7200 ::wxPostEvent(evthp, ev);
7201}
7202
7203void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7204 if ((m_panx_target_final - m_panx_target_now) ||
7205 (m_pany_target_final - m_pany_target_now)) {
7206 DoTimedMovementTarget();
7207 } else
7208 DoTimedMovement();
7209}
7210
7211void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7212
7213bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7214 int delta) {
7215 if (m_disable_edge_pan) return false;
7216
7217 bool bft = false;
7218 int pan_margin = m_canvas_width * margin / 100;
7219 int pan_timer_set = 200;
7220 double pan_delta = GetVP().pix_width * delta / 100;
7221 int pan_x = 0;
7222 int pan_y = 0;
7223
7224 if (x > m_canvas_width - pan_margin) {
7225 bft = true;
7226 pan_x = pan_delta;
7227 }
7228
7229 else if (x < pan_margin) {
7230 bft = true;
7231 pan_x = -pan_delta;
7232 }
7233
7234 if (y < pan_margin) {
7235 bft = true;
7236 pan_y = -pan_delta;
7237 }
7238
7239 else if (y > m_canvas_height - pan_margin) {
7240 bft = true;
7241 pan_y = pan_delta;
7242 }
7243
7244 // Of course, if dragging, and the mouse left button is not down, we must
7245 // stop the event injection
7246 if (bdragging) {
7247 if (!g_btouch) {
7248 wxMouseState state = ::wxGetMouseState();
7249#if wxCHECK_VERSION(3, 0, 0)
7250 if (!state.LeftIsDown())
7251#else
7252 if (!state.LeftDown())
7253#endif
7254 bft = false;
7255 }
7256 }
7257 if ((bft) && !pPanTimer->IsRunning()) {
7258 PanCanvas(pan_x, pan_y);
7259 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7260 return true;
7261 }
7262
7263 // This mouse event must not be due to pan timer event injector
7264 // Mouse is out of the pan zone, so prevent any orphan event injection
7265 if ((!bft) && pPanTimer->IsRunning()) {
7266 pPanTimer->Stop();
7267 }
7268
7269 return (false);
7270}
7271
7272// Look for waypoints at the current position.
7273// Used to determine what a mouse event should act on.
7274
7275void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7276 bool setBeingEdited) {
7277 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7278 m_pRoutePointEditTarget = NULL;
7279 m_pFoundPoint = NULL;
7280
7281 SelectItem *pFind = NULL;
7282 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7283 SelectableItemList SelList = pSelect->FindSelectionList(
7284 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7285 for (SelectItem *pFind : SelList) {
7286 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7287
7288 // Get an array of all routes using this point
7289 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7290 // TODO: delete m_pEditRouteArray after use?
7291
7292 // Use route array to determine actual visibility for the point
7293 bool brp_viz = false;
7294 if (m_pEditRouteArray) {
7295 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7296 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7297 if (pr->IsVisible()) {
7298 brp_viz = true;
7299 break;
7300 }
7301 }
7302 } else
7303 brp_viz = frp->IsVisible(); // isolated point
7304
7305 if (brp_viz) {
7306 // Use route array to rubberband all affected routes
7307 if (m_pEditRouteArray) // Editing Waypoint as part of route
7308 {
7309 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7310 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7311 pr->m_bIsBeingEdited = setBeingEdited;
7312 }
7313 m_bRouteEditing = setBeingEdited;
7314 } else // editing Mark
7315 {
7316 frp->m_bRPIsBeingEdited = setBeingEdited;
7317 m_bMarkEditing = setBeingEdited;
7318 }
7319
7320 m_pRoutePointEditTarget = frp;
7321 m_pFoundPoint = pFind;
7322 break; // out of the while(node)
7323 }
7324 } // for (SelectItem...
7325}
7326std::shared_ptr<PI_PointContext> ChartCanvas::GetCanvasContextAtPoint(int x,
7327 int y) {
7328 // General Right Click
7329 // Look for selectable objects
7330 double slat, slon;
7331 GetCanvasPixPoint(x, y, slat, slon);
7332
7333 SelectItem *pFindAIS;
7334 SelectItem *pFindRP;
7335 SelectItem *pFindRouteSeg;
7336 SelectItem *pFindTrackSeg;
7337 SelectItem *pFindCurrent = NULL;
7338 SelectItem *pFindTide = NULL;
7339
7340 // Get all the selectable things at the selected point
7341 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7342 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7343 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7344 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7345 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7346
7347 if (m_bShowCurrent)
7348 pFindCurrent =
7349 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7350
7351 if (m_bShowTide) // look for tide stations
7352 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7353
7354 int seltype = 0;
7355
7356 // Try for AIS targets first
7357 int FoundAIS_MMSI = 0;
7358 if (pFindAIS) {
7359 FoundAIS_MMSI = pFindAIS->GetUserData();
7360
7361 // Make sure the target data is available
7362 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7363 seltype |= SELTYPE_AISTARGET;
7364 }
7365
7366 // Now the various Route Parts
7367
7368 RoutePoint *FoundRoutePoint = NULL;
7369 Route *SelectedRoute = NULL;
7370
7371 if (pFindRP) {
7372 RoutePoint *pFirstVizPoint = NULL;
7373 RoutePoint *pFoundActiveRoutePoint = NULL;
7374 RoutePoint *pFoundVizRoutePoint = NULL;
7375 Route *pSelectedActiveRoute = NULL;
7376 Route *pSelectedVizRoute = NULL;
7377
7378 // There is at least one routepoint, so get the whole list
7379 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7380 SelectableItemList SelList =
7381 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7382 for (SelectItem *pFindSel : SelList) {
7383 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7384
7385 // Get an array of all routes using this point
7386 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7387
7388 // Use route array (if any) to determine actual visibility for this point
7389 bool brp_viz = false;
7390 if (proute_array) {
7391 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7392 Route *pr = (Route *)proute_array->Item(ir);
7393 if (pr->IsVisible()) {
7394 brp_viz = true;
7395 break;
7396 }
7397 }
7398 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7399 // but still exists as a waypoint
7400 brp_viz = prp->IsVisible(); // so treat as isolated point
7401
7402 } else
7403 brp_viz = prp->IsVisible(); // isolated point
7404
7405 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7406
7407 // Use route array to choose the appropriate route
7408 // Give preference to any active route, otherwise select the first visible
7409 // route in the array for this point
7410 if (proute_array) {
7411 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7412 Route *pr = (Route *)proute_array->Item(ir);
7413 if (pr->m_bRtIsActive) {
7414 pSelectedActiveRoute = pr;
7415 pFoundActiveRoutePoint = prp;
7416 break;
7417 }
7418 }
7419
7420 if (NULL == pSelectedVizRoute) {
7421 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7422 Route *pr = (Route *)proute_array->Item(ir);
7423 if (pr->IsVisible()) {
7424 pSelectedVizRoute = pr;
7425 pFoundVizRoutePoint = prp;
7426 break;
7427 }
7428 }
7429 }
7430
7431 delete proute_array;
7432 }
7433 }
7434
7435 // Now choose the "best" selections
7436 if (pFoundActiveRoutePoint) {
7437 FoundRoutePoint = pFoundActiveRoutePoint;
7438 SelectedRoute = pSelectedActiveRoute;
7439 } else if (pFoundVizRoutePoint) {
7440 FoundRoutePoint = pFoundVizRoutePoint;
7441 SelectedRoute = pSelectedVizRoute;
7442 } else
7443 // default is first visible point in list
7444 FoundRoutePoint = pFirstVizPoint;
7445
7446 if (SelectedRoute) {
7447 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7448 } else if (FoundRoutePoint) {
7449 seltype |= SELTYPE_MARKPOINT;
7450 }
7451
7452 // Highlight the selected point, to verify the proper right click selection
7453#if 0
7454 if (m_pFoundRoutePoint) {
7455 m_pFoundRoutePoint->m_bPtIsSelected = true;
7456 wxRect wp_rect;
7457 RoutePointGui(*m_pFoundRoutePoint)
7458 .CalculateDCRect(m_dc_route, this, &wp_rect);
7459 RefreshRect(wp_rect, true);
7460 }
7461#endif
7462 }
7463
7464 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7465 // routes But call the popup handler with identifier appropriate to the type
7466 if (pFindRouteSeg) // there is at least one select item
7467 {
7468 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7469 SelectableItemList SelList =
7470 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7471
7472 if (NULL == SelectedRoute) // the case where a segment only is selected
7473 {
7474 // Choose the first visible route containing segment in the list
7475 for (SelectItem *pFindSel : SelList) {
7476 Route *pr = (Route *)pFindSel->m_pData3;
7477 if (pr->IsVisible()) {
7478 SelectedRoute = pr;
7479 break;
7480 }
7481 }
7482 }
7483
7484 if (SelectedRoute) {
7485 if (NULL == FoundRoutePoint)
7486 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7487
7488 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7489 seltype |= SELTYPE_ROUTESEGMENT;
7490 }
7491 }
7492
7493#if 0
7494 if (pFindTrackSeg) {
7495 m_pSelectedTrack = NULL;
7496 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7497 SelectableItemList SelList =
7498 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7499
7500 // Choose the first visible track containing segment in the list
7501 wxSelectableItemListNode *node = SelList.GetFirst();
7502 while (node) {
7503 SelectItem *pFindSel = node->GetData();
7504
7505 Track *pt = (Track *)pFindSel->m_pData3;
7506 if (pt->IsVisible()) {
7507 m_pSelectedTrack = pt;
7508 break;
7509 }
7510 node = node->GetNext();
7511 }
7512
7513 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7514 }
7515#endif
7516
7517#if 0
7518 bool bseltc = false;
7519 // if(0 == seltype)
7520 {
7521 if (pFindCurrent) {
7522 // There may be multiple current entries at the same point.
7523 // For example, there often is a current substation (with directions
7524 // specified) co-located with its master. We want to select the
7525 // substation, so that the direction will be properly indicated on the
7526 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
7527 // substation)
7528 IDX_entry *pIDX_best_candidate;
7529
7530 SelectItem *pFind = NULL;
7531 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7532 SelectableItemList SelList = pSelectTC->FindSelectionList(
7533 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_CURRENTPOINT);
7534
7535 // Default is first entry
7536 wxSelectableItemListNode *node = SelList.GetFirst();
7537 pFind = node->GetData();
7538 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7539
7540 if (SelList.GetCount() > 1) {
7541 node = node->GetNext();
7542 while (node) {
7543 pFind = node->GetData();
7544 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
7545 if (pIDX_candidate->IDX_type == 'c') {
7546 pIDX_best_candidate = pIDX_candidate;
7547 break;
7548 }
7549
7550 node = node->GetNext();
7551 } // while (node)
7552 } else {
7553 wxSelectableItemListNode *node = SelList.GetFirst();
7554 pFind = node->GetData();
7555 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
7556 }
7557
7558 m_pIDXCandidate = pIDX_best_candidate;
7559
7560 if (0 == seltype) {
7561 DrawTCWindow(x, y, (void *)pIDX_best_candidate);
7562 Refresh(false);
7563 bseltc = true;
7564 } else
7565 seltype |= SELTYPE_CURRENTPOINT;
7566 }
7567
7568 else if (pFindTide) {
7569 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
7570
7571 if (0 == seltype) {
7572 DrawTCWindow(x, y, (void *)pFindTide->m_pData1);
7573 Refresh(false);
7574 bseltc = true;
7575 } else
7576 seltype |= SELTYPE_TIDEPOINT;
7577 }
7578 }
7579#endif
7580
7581 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7582
7583 // Populate the return struct
7584 auto rstruct = std::make_shared<PI_PointContext>();
7585 rstruct->object_type = OBJECT_CHART;
7586 rstruct->object_ident = "";
7587
7588 if (seltype == SELTYPE_AISTARGET) {
7589 rstruct->object_type = OBJECT_AISTARGET;
7590 wxString val;
7591 val.Printf("%d", FoundAIS_MMSI);
7592 rstruct->object_ident = val.ToStdString();
7593 } else if (seltype & SELTYPE_MARKPOINT) {
7594 if (FoundRoutePoint) {
7595 rstruct->object_type = OBJECT_ROUTEPOINT;
7596 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7597 }
7598 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7599 if (SelectedRoute) {
7600 rstruct->object_type = OBJECT_ROUTESEGMENT;
7601 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7602 }
7603 }
7604
7605 return rstruct;
7606}
7607
7608void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7609 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7610 singleClickEventIsValid = false;
7611 m_DoubleClickTimer->Stop();
7612}
7613
7614bool leftIsDown;
7615
7616bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7617 if (!m_bChartDragging && !m_bDrawingRoute) {
7618 /*
7619 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7620 * mouse event coordinates are in logical pixels.
7621 */
7622 if (m_Compass && m_Compass->IsShown()) {
7623 wxRect logicalRect = m_Compass->GetLogicalRect();
7624 bool isInCompass = logicalRect.Contains(event.GetPosition());
7625 if (isInCompass || m_mouseWasInCompass) {
7626 if (m_Compass->MouseEvent(event)) {
7627 cursor_region = CENTER;
7628 if (!g_btouch) SetCanvasCursor(event);
7629 m_mouseWasInCompass = isInCompass;
7630 return true;
7631 }
7632 }
7633 m_mouseWasInCompass = isInCompass;
7634 }
7635
7636 if (m_notification_button && m_notification_button->IsShown()) {
7637 wxRect logicalRect = m_notification_button->GetLogicalRect();
7638 bool isinButton = logicalRect.Contains(event.GetPosition());
7639 if (isinButton) {
7640 SetCursor(*pCursorArrow);
7641 if (event.LeftDown()) HandleNotificationMouseClick();
7642 return true;
7643 }
7644 }
7645
7646 if (MouseEventToolbar(event)) return true;
7647
7648 if (MouseEventChartBar(event)) return true;
7649
7650 if (MouseEventMUIBar(event)) return true;
7651
7652 if (MouseEventIENCBar(event)) return true;
7653 }
7654 return false;
7655}
7656
7657void ChartCanvas::HandleNotificationMouseClick() {
7658 if (!m_NotificationsList) {
7659 m_NotificationsList = new NotificationsList(this);
7660
7661 // calculate best size for Notification list
7662 m_NotificationsList->RecalculateSize();
7663 m_NotificationsList->Hide();
7664 }
7665
7666 if (m_NotificationsList->IsShown()) {
7667 m_NotificationsList->Hide();
7668 } else {
7669 m_NotificationsList->RecalculateSize();
7670 m_NotificationsList->ReloadNotificationList();
7671 m_NotificationsList->Show();
7672 }
7673}
7674bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7675 if (!g_bShowChartBar) return false;
7676
7677 if (!m_Piano->MouseEvent(event)) return false;
7678
7679 cursor_region = CENTER;
7680 if (!g_btouch) SetCanvasCursor(event);
7681 return true;
7682}
7683
7684bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7685 if (!IsPrimaryCanvas()) return false;
7686
7687 if (g_MainToolbar) {
7688 if (!g_MainToolbar->MouseEvent(event))
7689 return false;
7690 else
7691 g_MainToolbar->RefreshToolbar();
7692 }
7693
7694 cursor_region = CENTER;
7695 if (!g_btouch) SetCanvasCursor(event);
7696 return true;
7697}
7698
7699bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7700 if (!IsPrimaryCanvas()) return false;
7701
7702 if (g_iENCToolbar) {
7703 if (!g_iENCToolbar->MouseEvent(event))
7704 return false;
7705 else {
7706 g_iENCToolbar->RefreshToolbar();
7707 return true;
7708 }
7709 }
7710 return false;
7711}
7712
7713bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7714 if (m_muiBar) {
7715 if (!m_muiBar->MouseEvent(event)) return false;
7716 }
7717
7718 cursor_region = CENTER;
7719 if (!g_btouch) SetCanvasCursor(event);
7720 if (m_muiBar)
7721 return true;
7722 else
7723 return false;
7724}
7725
7726bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7727 int x, y;
7728
7729 bool bret = false;
7730
7731 event.GetPosition(&x, &y);
7732
7733 x *= m_displayScale;
7734 y *= m_displayScale;
7735
7736 m_MouseDragging = event.Dragging();
7737
7738 // Some systems produce null drag events, where the pointer position has not
7739 // changed from the previous value. Detect this case, and abort further
7740 // processing (FS#1748)
7741#ifdef __WXMSW__
7742 if (event.Dragging()) {
7743 if ((x == mouse_x) && (y == mouse_y)) return true;
7744 }
7745#endif
7746
7747 mouse_x = x;
7748 mouse_y = y;
7749 mouse_leftisdown = event.LeftDown();
7751
7752 // Establish the event region
7753 cursor_region = CENTER;
7754
7755 int chartbar_height = GetChartbarHeight();
7756
7757 if (m_Compass && m_Compass->IsShown() &&
7758 m_Compass->GetRect().Contains(event.GetPosition())) {
7759 cursor_region = CENTER;
7760 } else if (x > xr_margin) {
7761 cursor_region = MID_RIGHT;
7762 } else if (x < xl_margin) {
7763 cursor_region = MID_LEFT;
7764 } else if (y > yb_margin - chartbar_height &&
7765 y < m_canvas_height - chartbar_height) {
7766 cursor_region = MID_TOP;
7767 } else if (y < yt_margin) {
7768 cursor_region = MID_BOT;
7769 } else {
7770 cursor_region = CENTER;
7771 }
7772
7773 if (!g_btouch) SetCanvasCursor(event);
7774
7775 // Protect from leftUp's coming from event handlers in child
7776 // windows who return focus to the canvas.
7777 leftIsDown = event.LeftDown();
7778
7779#ifndef __WXOSX__
7780 if (event.LeftDown()) {
7781 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7782 // The menu bar is temporarily visible due to alt having been pressed.
7783 // Clicking will hide it, and do nothing else.
7784 g_bTempShowMenuBar = false;
7785 parent_frame->ApplyGlobalSettings(false);
7786 return (true);
7787 }
7788 }
7789#endif
7790
7791 // Update modifiers here; some window managers never send the key event
7792 m_modkeys = 0;
7793 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7794 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7795
7796#ifdef __WXMSW__
7797 // TODO Test carefully in other platforms, remove ifdef....
7798 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7799 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7800#endif
7801
7802 event.SetEventObject(this);
7803 if (SendMouseEventToPlugins(event))
7804 return (true); // PlugIn did something, and does not want the canvas to
7805 // do anything else
7806
7807 // Capture LeftUp's and time them, unless it already came from the timer.
7808
7809 // Detect end of chart dragging
7810 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7811 StartChartDragInertia();
7812 }
7813
7814 if (b_handle_dclick && event.LeftUp() && !singleClickEventIsValid) {
7815 // Ignore the second LeftUp after the DClick.
7816 if (m_DoubleClickTimer->IsRunning()) {
7817 m_DoubleClickTimer->Stop();
7818 return (true);
7819 }
7820
7821 // Save the event for later running if there is no DClick.
7822 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7823 singleClickEvent = event;
7824 singleClickEventIsValid = true;
7825 return (true);
7826 }
7827
7828 // This logic is necessary on MSW to handle the case where
7829 // a context (right-click) menu is dismissed without action
7830 // by clicking on the chart surface.
7831 // We need to avoid an unintentional pan by eating some clicks...
7832#ifdef __WXMSW__
7833 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7834 if (g_click_stop > 0) {
7835 g_click_stop--;
7836 return (true);
7837 }
7838 }
7839#endif
7840
7841 // Kick off the Rotation control timer
7842 if (GetUpMode() == COURSE_UP_MODE) {
7843 m_b_rot_hidef = false;
7844 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7845 } else
7846 pRotDefTimer->Stop();
7847
7848 // Retrigger the route leg / AIS target popup timer
7849 bool bRoll = !g_btouch;
7850#ifdef __ANDROID__
7851 bRoll = g_bRollover;
7852#endif
7853 if (bRoll) {
7854 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7855 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7856 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7857 m_RolloverPopupTimer.Start(
7858 10,
7859 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7860 else
7861 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7862 }
7863
7864 // Retrigger the cursor tracking timer
7865 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7866
7867// Show cursor position on Status Bar, if present
7868// except for GTK, under which status bar updates are very slow
7869// due to Update() call.
7870// In this case, as a workaround, update the status window
7871// after an interval timer (pCurTrackTimer) pops, which will happen
7872// whenever the mouse has stopped moving for specified interval.
7873// See the method OnCursorTrackTimerEvent()
7874#if !defined(__WXGTK__) && !defined(__WXQT__)
7875 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7876#endif
7877
7878 // Send the current cursor lat/lon to all PlugIns requesting it
7879 if (g_pi_manager) {
7880 // Occasionally, MSW will produce nonsense events on right click....
7881 // This results in an error in cursor geo position, so we skip this case
7882 if ((x >= 0) && (y >= 0))
7883 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7884 }
7885
7886 if (!g_btouch) {
7887 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7888 wxPoint p = ClientToScreen(wxPoint(x, y));
7889 }
7890 }
7891
7892 if (1 ) {
7893 // Route Creation Rubber Banding
7894 if (m_routeState >= 2) {
7895 r_rband.x = x;
7896 r_rband.y = y;
7897 m_bDrawingRoute = true;
7898
7899 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7900 Refresh(false);
7901 }
7902
7903 // Measure Tool Rubber Banding
7904 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7905 r_rband.x = x;
7906 r_rband.y = y;
7907 m_bDrawingRoute = true;
7908
7909 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7910 Refresh(false);
7911 }
7912 }
7913 return bret;
7914}
7915
7916int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7917 // On general Right Click
7918 // Look for selectable objects
7919 double slat = lat;
7920 double slon = lon;
7921
7922#if defined(__WXMAC__) || defined(__ANDROID__)
7923 wxScreenDC sdc;
7924 ocpnDC dc(sdc);
7925#else
7926 wxClientDC cdc(GetParent());
7927 ocpnDC dc(cdc);
7928#endif
7929
7930 SelectItem *pFindAIS;
7931 SelectItem *pFindRP;
7932 SelectItem *pFindRouteSeg;
7933 SelectItem *pFindTrackSeg;
7934 SelectItem *pFindCurrent = NULL;
7935 SelectItem *pFindTide = NULL;
7936
7937 // Deselect any current objects
7938 if (m_pSelectedRoute) {
7939 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7940 m_pSelectedRoute->DeSelectRoute();
7941#ifdef ocpnUSE_GL
7942 if (g_bopengl && m_glcc) {
7943 InvalidateGL();
7944 Update();
7945 } else
7946#endif
7947 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
7948 }
7949
7950 if (m_pFoundRoutePoint) {
7951 m_pFoundRoutePoint->m_bPtIsSelected = false;
7952 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
7953 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
7954 }
7955
7958 if (g_btouch && m_pRoutePointEditTarget) {
7959 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
7960 m_pRoutePointEditTarget->m_bPtIsSelected = false;
7961 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
7962 }
7963
7964 // Get all the selectable things at the cursor
7965 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7966 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7967 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7968 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7969 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7970
7971 if (m_bShowCurrent)
7972 pFindCurrent =
7973 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7974
7975 if (m_bShowTide) // look for tide stations
7976 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7977
7978 int seltype = 0;
7979
7980 // Try for AIS targets first
7981 if (pFindAIS) {
7982 m_FoundAIS_MMSI = pFindAIS->GetUserData();
7983
7984 // Make sure the target data is available
7985 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
7986 seltype |= SELTYPE_AISTARGET;
7987 }
7988
7989 // Now examine the various Route parts
7990
7991 m_pFoundRoutePoint = NULL;
7992 if (pFindRP) {
7993 RoutePoint *pFirstVizPoint = NULL;
7994 RoutePoint *pFoundActiveRoutePoint = NULL;
7995 RoutePoint *pFoundVizRoutePoint = NULL;
7996 Route *pSelectedActiveRoute = NULL;
7997 Route *pSelectedVizRoute = NULL;
7998
7999 // There is at least one routepoint, so get the whole list
8000 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8001 SelectableItemList SelList =
8002 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8003 for (SelectItem *pFindSel : SelList) {
8004 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8005
8006 // Get an array of all routes using this point
8007 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8008
8009 // Use route array (if any) to determine actual visibility for this point
8010 bool brp_viz = false;
8011 if (proute_array) {
8012 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8013 Route *pr = (Route *)proute_array->Item(ir);
8014 if (pr->IsVisible()) {
8015 brp_viz = true;
8016 break;
8017 }
8018 }
8019 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8020 // but still exists as a waypoint
8021 brp_viz = prp->IsVisible(); // so treat as isolated point
8022
8023 } else
8024 brp_viz = prp->IsVisible(); // isolated point
8025
8026 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8027
8028 // Use route array to choose the appropriate route
8029 // Give preference to any active route, otherwise select the first visible
8030 // route in the array for this point
8031 m_pSelectedRoute = NULL;
8032 if (proute_array) {
8033 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8034 Route *pr = (Route *)proute_array->Item(ir);
8035 if (pr->m_bRtIsActive) {
8036 pSelectedActiveRoute = pr;
8037 pFoundActiveRoutePoint = prp;
8038 break;
8039 }
8040 }
8041
8042 if (NULL == pSelectedVizRoute) {
8043 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8044 Route *pr = (Route *)proute_array->Item(ir);
8045 if (pr->IsVisible()) {
8046 pSelectedVizRoute = pr;
8047 pFoundVizRoutePoint = prp;
8048 break;
8049 }
8050 }
8051 }
8052
8053 delete proute_array;
8054 }
8055 }
8056
8057 // Now choose the "best" selections
8058 if (pFoundActiveRoutePoint) {
8059 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8060 m_pSelectedRoute = pSelectedActiveRoute;
8061 } else if (pFoundVizRoutePoint) {
8062 m_pFoundRoutePoint = pFoundVizRoutePoint;
8063 m_pSelectedRoute = pSelectedVizRoute;
8064 } else
8065 // default is first visible point in list
8066 m_pFoundRoutePoint = pFirstVizPoint;
8067
8068 if (m_pSelectedRoute) {
8069 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8070 } else if (m_pFoundRoutePoint) {
8071 seltype |= SELTYPE_MARKPOINT;
8072 }
8073
8074 // Highlight the selected point, to verify the proper right click selection
8075 if (m_pFoundRoutePoint) {
8076 m_pFoundRoutePoint->m_bPtIsSelected = true;
8077 wxRect wp_rect;
8078 RoutePointGui(*m_pFoundRoutePoint)
8079 .CalculateDCRect(m_dc_route, this, &wp_rect);
8080 RefreshRect(wp_rect, true);
8081 }
8082 }
8083
8084 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8085 // routes But call the popup handler with identifier appropriate to the type
8086 if (pFindRouteSeg) // there is at least one select item
8087 {
8088 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8089 SelectableItemList SelList =
8090 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8091
8092 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8093 {
8094 // Choose the first visible route containing segment in the list
8095 for (SelectItem *pFindSel : SelList) {
8096 Route *pr = (Route *)pFindSel->m_pData3;
8097 if (pr->IsVisible()) {
8098 m_pSelectedRoute = pr;
8099 break;
8100 }
8101 }
8102 }
8103
8104 if (m_pSelectedRoute) {
8105 if (NULL == m_pFoundRoutePoint)
8106 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8107
8108 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8109 if (m_pSelectedRoute->m_bRtIsSelected) {
8110#ifdef ocpnUSE_GL
8111 if (g_bopengl && m_glcc) {
8112 InvalidateGL();
8113 Update();
8114 } else
8115#endif
8116 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8117 }
8118 seltype |= SELTYPE_ROUTESEGMENT;
8119 }
8120 }
8121
8122 if (pFindTrackSeg) {
8123 m_pSelectedTrack = NULL;
8124 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8125 SelectableItemList SelList =
8126 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8127
8128 // Choose the first visible track containing segment in the list
8129 for (SelectItem *pFindSel : SelList) {
8130 Track *pt = (Track *)pFindSel->m_pData3;
8131 if (pt->IsVisible()) {
8132 m_pSelectedTrack = pt;
8133 break;
8134 }
8135 }
8136 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8137 }
8138
8139 {
8140 if (pFindCurrent) {
8141 // There may be multiple current entries at the same point.
8142 // For example, there often is a current substation (with directions
8143 // specified) co-located with its master. We want to select the
8144 // substation, so that the direction will be properly indicated on the
8145 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8146 // substation)
8147 IDX_entry *pIDX_best_candidate;
8148
8149 SelectItem *pFind = NULL;
8150 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8151 SelectableItemList SelList =
8152 pSelectTC->FindSelectionList(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8153
8154 // Default is first entry
8155 pFind = *SelList.begin();
8156 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8157
8158 auto node = SelList.begin();
8159 if (SelList.size() > 1) {
8160 for (++node; node != SelList.end(); ++node) {
8161 pFind = *node;
8162 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8163 if (pIDX_candidate->IDX_type == 'c') {
8164 pIDX_best_candidate = pIDX_candidate;
8165 break;
8166 }
8167 } // while (node)
8168 } else {
8169 pFind = *SelList.begin();
8170 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8171 }
8172
8173 m_pIDXCandidate = pIDX_best_candidate;
8174 seltype |= SELTYPE_CURRENTPOINT;
8175 }
8176
8177 else if (pFindTide) {
8178 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8179 seltype |= SELTYPE_TIDEPOINT;
8180 }
8181 }
8182
8183 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8184
8185 return seltype;
8186}
8187
8188void ChartCanvas::CallPopupMenu(int x, int y) {
8189 last_drag.x = x;
8190 last_drag.y = y;
8191 if (m_routeState) { // creating route?
8192 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8193 return;
8194 }
8195
8197
8198 // If tide or current point is selected, then show the TC dialog immediately
8199 // without context menu
8200 if (SELTYPE_CURRENTPOINT == seltype) {
8201 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8202 Refresh(false);
8203 return;
8204 }
8205
8206 if (SELTYPE_TIDEPOINT == seltype) {
8207 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8208 Refresh(false);
8209 return;
8210 }
8211
8212 InvokeCanvasMenu(x, y, seltype);
8213
8214 // Clean up if not deleted in InvokeCanvasMenu
8215 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8216 m_pSelectedRoute->m_bRtIsSelected = false;
8217 }
8218
8219 m_pSelectedRoute = NULL;
8220
8221 if (m_pFoundRoutePoint) {
8222 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8223 m_pFoundRoutePoint->m_bPtIsSelected = false;
8224 }
8225 m_pFoundRoutePoint = NULL;
8226
8227 Refresh(true);
8228 // Refresh(false); // needed for MSW, not GTK Why??
8229}
8230
8231bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8232 // For now just bail out completely if the point clicked is not on the chart
8233 if (std::isnan(m_cursor_lat)) return false;
8234
8235 // Mouse Clicks
8236 bool ret = false; // return true if processed
8237
8238 int x, y, mx, my;
8239 event.GetPosition(&x, &y);
8240 mx = x;
8241 my = y;
8242
8243 // Calculate meaningful SelectRadius
8244 float SelectRadius;
8245 SelectRadius = g_Platform->GetSelectRadiusPix() /
8246 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8247
8249 // We start with Double Click processing. The first left click just starts a
8250 // timer and is remembered, then we actually do something if there is a
8251 // LeftDClick. If there is, the two single clicks are ignored.
8252
8253 if (event.LeftDClick() && (cursor_region == CENTER)) {
8254 m_DoubleClickTimer->Start();
8255 singleClickEventIsValid = false;
8256
8257 double zlat, zlon;
8259 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8260
8261 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8262 if (m_bShowAIS) {
8263 SelectItem *pFindAIS;
8264 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8265
8266 if (pFindAIS) {
8267 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8268 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8269 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8270 }
8271 return true;
8272 }
8273 }
8274
8275 SelectableItemList rpSelList =
8276 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8277 bool b_onRPtarget = false;
8278 for (SelectItem *pFind : rpSelList) {
8279 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8280 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8281 b_onRPtarget = true;
8282 break;
8283 }
8284 }
8285
8286 // Double tap with selected RoutePoint or Mark
8287
8288 if (m_pRoutePointEditTarget) {
8289 if (b_onRPtarget) {
8290 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8291 return true;
8292 } else {
8293 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8294 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8295 if (g_btouch)
8296 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8297 wxRect wp_rect;
8298 RoutePointGui(*m_pRoutePointEditTarget)
8299 .CalculateDCRect(m_dc_route, this, &wp_rect);
8300 m_pRoutePointEditTarget = NULL; // cancel selection
8301 RefreshRect(wp_rect, true);
8302 return true;
8303 }
8304 } else {
8305 auto node = rpSelList.begin();
8306 if (node != rpSelList.end()) {
8307 SelectItem *pFind = *node;
8308 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8309 if (frp) {
8310 wxArrayPtrVoid *proute_array =
8312
8313 // Use route array (if any) to determine actual visibility for this
8314 // point
8315 bool brp_viz = false;
8316 if (proute_array) {
8317 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8318 Route *pr = (Route *)proute_array->Item(ir);
8319 if (pr->IsVisible()) {
8320 brp_viz = true;
8321 break;
8322 }
8323 }
8324 delete proute_array;
8325 if (!brp_viz &&
8326 frp->IsShared()) // is not visible as part of route, but still
8327 // exists as a waypoint
8328 brp_viz = frp->IsVisible(); // so treat as isolated point
8329 } else
8330 brp_viz = frp->IsVisible(); // isolated point
8331
8332 if (brp_viz) {
8333 ShowMarkPropertiesDialog(frp);
8334 return true;
8335 }
8336 }
8337 }
8338 }
8339
8340 SelectItem *cursorItem;
8341 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8342
8343 if (cursorItem) {
8344 Route *pr = (Route *)cursorItem->m_pData3;
8345 if (pr->IsVisible()) {
8346 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8347 return true;
8348 }
8349 }
8350
8351 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8352
8353 if (cursorItem) {
8354 Track *pt = (Track *)cursorItem->m_pData3;
8355 if (pt->IsVisible()) {
8356 ShowTrackPropertiesDialog(pt);
8357 return true;
8358 }
8359 }
8360
8361 // Found no object to act on, so show chart info.
8362
8363 ShowObjectQueryWindow(x, y, zlat, zlon);
8364 return true;
8365 }
8366
8368 if (event.LeftDown()) {
8369 // This really should not be needed, but....
8370 // on Windows, when using wxAUIManager, sometimes the focus is lost
8371 // when clicking into another pane, e.g.the AIS target list, and then back
8372 // to this pane. Oddly, some mouse events are not lost, however. Like this
8373 // one....
8374 SetFocus();
8375
8376 last_drag.x = mx;
8377 last_drag.y = my;
8378 leftIsDown = true;
8379
8380 if (!g_btouch) {
8381 if (m_routeState) // creating route?
8382 {
8383 double rlat, rlon;
8384 bool appending = false;
8385 bool inserting = false;
8386 Route *tail = 0;
8387
8388 SetCursor(*pCursorPencil);
8389 rlat = m_cursor_lat;
8390 rlon = m_cursor_lon;
8391
8392 m_bRouteEditing = true;
8393
8394 if (m_routeState == 1) {
8395 m_pMouseRoute = new Route();
8396 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8397 pRouteList->push_back(m_pMouseRoute);
8398 r_rband.x = x;
8399 r_rband.y = y;
8400 }
8401
8402 // Check to see if there is a nearby point which may be reused
8403 RoutePoint *pMousePoint = NULL;
8404
8405 // Calculate meaningful SelectRadius
8406 double nearby_radius_meters =
8407 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8408
8409 RoutePoint *pNearbyPoint =
8410 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8411 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8412 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8413 wxArrayPtrVoid *proute_array =
8414 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8415
8416 // Use route array (if any) to determine actual visibility for this
8417 // point
8418 bool brp_viz = false;
8419 if (proute_array) {
8420 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8421 Route *pr = (Route *)proute_array->Item(ir);
8422 if (pr->IsVisible()) {
8423 brp_viz = true;
8424 break;
8425 }
8426 }
8427 delete proute_array;
8428 if (!brp_viz &&
8429 pNearbyPoint->IsShared()) // is not visible as part of route,
8430 // but still exists as a waypoint
8431 brp_viz =
8432 pNearbyPoint->IsVisible(); // so treat as isolated point
8433 } else
8434 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8435
8436 if (brp_viz) {
8437 wxString msg = _("Use nearby waypoint?");
8438 // Don't add a mark without name to the route. Name it if needed
8439 const bool noname(pNearbyPoint->GetName() == "");
8440 if (noname) {
8441 msg =
8442 _("Use nearby nameless waypoint and name it M with"
8443 " a unique number?");
8444 }
8445 // Avoid route finish on focus change for message dialog
8446 m_FinishRouteOnKillFocus = false;
8447 int dlg_return =
8448 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8449 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8450 m_FinishRouteOnKillFocus = true;
8451 if (dlg_return == wxID_YES) {
8452 if (noname) {
8453 if (m_pMouseRoute) {
8454 int last_wp_num = m_pMouseRoute->GetnPoints();
8455 // AP-ECRMB will truncate to 6 characters
8456 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8457 wxString wp_name = wxString::Format(
8458 "M%002i-%s", last_wp_num + 1, guid_short);
8459 pNearbyPoint->SetName(wp_name);
8460 } else
8461 pNearbyPoint->SetName("WPXX");
8462 }
8463 pMousePoint = pNearbyPoint;
8464
8465 // Using existing waypoint, so nothing to delete for undo.
8466 if (m_routeState > 1)
8467 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8468 Undo_HasParent, NULL);
8469
8470 tail =
8471 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8472 bool procede = false;
8473 if (tail) {
8474 procede = true;
8475 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8476 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8477 procede = false;
8478 }
8479
8480 if (procede) {
8481 int dlg_return;
8482 m_FinishRouteOnKillFocus = false;
8483 if (m_routeState ==
8484 1) { // first point in new route, preceeding route to be
8485 // added? Not touch case
8486
8487 wxString dmsg =
8488 _("Insert first part of this route in the new route?");
8489 if (tail->GetIndexOf(pMousePoint) ==
8490 tail->GetnPoints()) // Starting on last point of another
8491 // route?
8492 dmsg = _("Insert this route in the new route?");
8493
8494 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8495 dlg_return = OCPNMessageBox(
8496 this, dmsg, _("OpenCPN Route Create"),
8497 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8498 m_FinishRouteOnKillFocus = true;
8499
8500 if (dlg_return == wxID_YES) {
8501 inserting = true; // part of the other route will be
8502 // preceeding the new route
8503 }
8504 }
8505 } else {
8506 wxString dmsg =
8507 _("Append last part of this route to the new route?");
8508 if (tail->GetIndexOf(pMousePoint) == 1)
8509 dmsg = _(
8510 "Append this route to the new route?"); // Picking the
8511 // first point
8512 // of another
8513 // route?
8514
8515 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8516 dlg_return = OCPNMessageBox(
8517 this, dmsg, _("OpenCPN Route Create"),
8518 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8519 m_FinishRouteOnKillFocus = true;
8520
8521 if (dlg_return == wxID_YES) {
8522 appending = true; // part of the other route will be
8523 // appended to the new route
8524 }
8525 }
8526 }
8527 }
8528
8529 // check all other routes to see if this point appears in any
8530 // other route If it appears in NO other route, then it should e
8531 // considered an isolated mark
8532 if (!FindRouteContainingWaypoint(pMousePoint))
8533 pMousePoint->SetShared(true);
8534 }
8535 }
8536 }
8537
8538 if (NULL == pMousePoint) { // need a new point
8539 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8540 "", wxEmptyString);
8541 pMousePoint->SetNameShown(false);
8542
8543 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8544
8545 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8546
8547 if (m_routeState > 1)
8548 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8549 Undo_IsOrphanded, NULL);
8550 }
8551
8552 if (m_pMouseRoute) {
8553 if (m_routeState == 1) {
8554 // First point in the route.
8555 m_pMouseRoute->AddPoint(pMousePoint);
8556 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8557 } else {
8558 if (m_pMouseRoute->m_NextLegGreatCircle) {
8559 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8560 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8561 &rhumbBearing, &rhumbDist);
8562 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8563 rlat, &gcDist, &gcBearing, NULL);
8564 double gcDistNM = gcDist / 1852.0;
8565
8566 // Empirically found expression to get reasonable route segments.
8567 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8568 pow(rhumbDist - gcDistNM - 1, 0.5);
8569
8570 wxString msg;
8571 msg << _("For this leg the Great Circle route is ")
8572 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8573 << _(" shorter than rhumbline.\n\n")
8574 << _("Would you like include the Great Circle routing points "
8575 "for this leg?");
8576
8577 m_FinishRouteOnKillFocus = false;
8578 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8579 // does not fully capture mouse
8580
8581 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8582 wxYES_NO | wxNO_DEFAULT);
8583
8584 m_disable_edge_pan = false;
8585 m_FinishRouteOnKillFocus = true;
8586
8587 if (answer == wxID_YES) {
8588 RoutePoint *gcPoint;
8589 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8590 wxRealPoint gcCoord;
8591
8592 for (int i = 1; i <= segmentCount; i++) {
8593 double fraction = (double)i * (1.0 / (double)segmentCount);
8594 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8595 gcDist * fraction, gcBearing,
8596 &gcCoord.x, &gcCoord.y, NULL);
8597
8598 if (i < segmentCount) {
8599 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8600 wxEmptyString);
8601 gcPoint->SetNameShown(false);
8602 // pConfig->AddNewWayPoint(gcPoint, -1);
8603 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8604
8605 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8606 gcPoint);
8607 } else {
8608 gcPoint = pMousePoint; // Last point, previously exsisting!
8609 }
8610
8611 m_pMouseRoute->AddPoint(gcPoint);
8612 pSelect->AddSelectableRouteSegment(
8613 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8614 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8615 prevGcPoint = gcPoint;
8616 }
8617
8618 undo->CancelUndoableAction(true);
8619
8620 } else {
8621 m_pMouseRoute->AddPoint(pMousePoint);
8622 pSelect->AddSelectableRouteSegment(
8623 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8624 pMousePoint, m_pMouseRoute);
8625 undo->AfterUndoableAction(m_pMouseRoute);
8626 }
8627 } else {
8628 // Ordinary rhumblinesegment.
8629 m_pMouseRoute->AddPoint(pMousePoint);
8630 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8631 rlon, m_prev_pMousePoint,
8632 pMousePoint, m_pMouseRoute);
8633 undo->AfterUndoableAction(m_pMouseRoute);
8634 }
8635 }
8636 }
8637 m_prev_rlat = rlat;
8638 m_prev_rlon = rlon;
8639 m_prev_pMousePoint = pMousePoint;
8640 if (m_pMouseRoute)
8641 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8642
8643 m_routeState++;
8644
8645 if (appending ||
8646 inserting) { // Appending a route or making a new route
8647 int connect = tail->GetIndexOf(pMousePoint);
8648 if (connect == 1) {
8649 inserting = false; // there is nothing to insert
8650 appending = true; // so append
8651 }
8652 int length = tail->GetnPoints();
8653
8654 int i;
8655 int start, stop;
8656 if (appending) {
8657 start = connect + 1;
8658 stop = length;
8659 } else { // inserting
8660 start = 1;
8661 stop = connect;
8662 m_pMouseRoute->RemovePoint(
8663 m_pMouseRoute
8664 ->GetLastPoint()); // Remove the first and only point
8665 }
8666 for (i = start; i <= stop; i++) {
8667 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8668 if (m_pMouseRoute)
8669 m_pMouseRoute->m_lastMousePointIndex =
8670 m_pMouseRoute->GetnPoints();
8671 m_routeState++;
8672 gFrame->RefreshAllCanvas();
8673 ret = true;
8674 }
8675 m_prev_rlat =
8676 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8677 m_prev_rlon =
8678 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8679 m_pMouseRoute->FinalizeForRendering();
8680 }
8681 gFrame->RefreshAllCanvas();
8682 ret = true;
8683 }
8684
8685 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8686 {
8687 SetCursor(*pCursorPencil);
8688
8689 if (!m_pMeasureRoute) {
8690 m_pMeasureRoute = new Route();
8691 pRouteList->push_back(m_pMeasureRoute);
8692 }
8693
8694 if (m_nMeasureState == 1) {
8695 r_rband.x = x;
8696 r_rband.y = y;
8697 }
8698
8699 RoutePoint *pMousePoint =
8700 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8701 wxEmptyString, wxEmptyString);
8702 pMousePoint->m_bShowName = false;
8703 pMousePoint->SetShowWaypointRangeRings(false);
8704
8705 m_pMeasureRoute->AddPoint(pMousePoint);
8706
8707 m_prev_rlat = m_cursor_lat;
8708 m_prev_rlon = m_cursor_lon;
8709 m_prev_pMousePoint = pMousePoint;
8710 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8711
8712 m_nMeasureState++;
8713 gFrame->RefreshAllCanvas();
8714 ret = true;
8715 }
8716
8717 else {
8718 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8719 }
8720 } // !g_btouch
8721 else { // g_btouch
8722
8723 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8724 // if near screen edge, pan with injection
8725 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8726 // return;
8727 // }
8728 }
8729 }
8730
8731 if (ret) return true;
8732 }
8733
8734 if (event.Dragging()) {
8735 // in touch screen mode ensure the finger/cursor is on the selected point's
8736 // radius to allow dragging
8737 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8738 if (g_btouch) {
8739 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8740 SelectItem *pFind = NULL;
8741 SelectableItemList SelList = pSelect->FindSelectionList(
8742 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8743 for (SelectItem *pFind : SelList) {
8744 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8745 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8746 }
8747 }
8748
8749 // Check for use of dragHandle
8750 if (m_pRoutePointEditTarget &&
8751 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8752 SelectItem *pFind = NULL;
8753 SelectableItemList SelList = pSelect->FindSelectionList(
8754 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8755 for (SelectItem *pFind : SelList) {
8756 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8757 if (m_pRoutePointEditTarget == frp) {
8758 m_bIsInRadius = true;
8759 break;
8760 }
8761 }
8762
8763 if (!m_dragoffsetSet) {
8764 RoutePointGui(*m_pRoutePointEditTarget)
8765 .PresetDragOffset(this, mouse_x, mouse_y);
8766 m_dragoffsetSet = true;
8767 }
8768 }
8769 }
8770
8771 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8772 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8773
8774 if (NULL == g_pMarkInfoDialog) {
8775 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8776 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8777 DraggingAllowed = false;
8778
8779 if (m_pRoutePointEditTarget &&
8780 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8781 DraggingAllowed = false;
8782
8783 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8784
8785 if (DraggingAllowed) {
8786 if (!undo->InUndoableAction()) {
8787 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8788 Undo_NeedsCopy, m_pFoundPoint);
8789 }
8790
8791 // Get the update rectangle for the union of the un-edited routes
8792 wxRect pre_rect;
8793
8794 if (!g_bopengl && m_pEditRouteArray) {
8795 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8796 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8797 // Need to validate route pointer
8798 // Route may be gone due to drgging close to ownship with
8799 // "Delete On Arrival" state set, as in the case of
8800 // navigating to an isolated waypoint on a temporary route
8801 if (g_pRouteMan->IsRouteValid(pr)) {
8802 wxRect route_rect;
8803 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8804 pre_rect.Union(route_rect);
8805 }
8806 }
8807 }
8808
8809 double new_cursor_lat = m_cursor_lat;
8810 double new_cursor_lon = m_cursor_lon;
8811
8812 if (CheckEdgePan(x, y, true, 5, 2))
8813 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8814
8815 // update the point itself
8816 if (g_btouch) {
8817 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8818 // new_cursor_lat, new_cursor_lon);
8819 RoutePointGui(*m_pRoutePointEditTarget)
8820 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8821 // update the Drag Handle entry in the pSelect list
8822 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8823 m_pRoutePointEditTarget,
8824 SELTYPE_DRAGHANDLE);
8825 m_pFoundPoint->m_slat =
8826 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8827 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8828 } else {
8829 m_pRoutePointEditTarget->m_lat =
8830 new_cursor_lat; // update the RoutePoint entry
8831 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8832 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8833 m_pFoundPoint->m_slat =
8834 new_cursor_lat; // update the SelectList entry
8835 m_pFoundPoint->m_slon = new_cursor_lon;
8836 }
8837
8838 // Update the MarkProperties Dialog, if currently shown
8839 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8840 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8841 g_pMarkInfoDialog->UpdateProperties(true);
8842 }
8843
8844 if (g_bopengl) {
8845 // InvalidateGL();
8846 Refresh(false);
8847 } else {
8848 // Get the update rectangle for the edited route
8849 wxRect post_rect;
8850
8851 if (m_pEditRouteArray) {
8852 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8853 ir++) {
8854 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8855 if (g_pRouteMan->IsRouteValid(pr)) {
8856 wxRect route_rect;
8857 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8858 post_rect.Union(route_rect);
8859 }
8860 }
8861 }
8862
8863 // Invalidate the union region
8864 pre_rect.Union(post_rect);
8865 RefreshRect(pre_rect, false);
8866 }
8867 gFrame->RefreshCanvasOther(this);
8868 m_bRoutePoinDragging = true;
8869 }
8870 ret = true;
8871 } // if Route Editing
8872
8873 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8874 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8875
8876 if (NULL == g_pMarkInfoDialog) {
8877 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8878 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8879 DraggingAllowed = false;
8880
8881 if (m_pRoutePointEditTarget &&
8882 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8883 DraggingAllowed = false;
8884
8885 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8886
8887 if (DraggingAllowed) {
8888 if (!undo->InUndoableAction()) {
8889 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8890 Undo_NeedsCopy, m_pFoundPoint);
8891 }
8892
8893 // The mark may be an anchorwatch
8894 double lpp1 = 0.;
8895 double lpp2 = 0.;
8896 double lppmax;
8897
8898 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8899 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8900 }
8901 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8902 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
8903 }
8904 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
8905
8906 // Get the update rectangle for the un-edited mark
8907 wxRect pre_rect;
8908 if (!g_bopengl) {
8909 RoutePointGui(*m_pRoutePointEditTarget)
8910 .CalculateDCRect(m_dc_route, this, &pre_rect);
8911 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
8912 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
8913 (int)(lppmax - (pre_rect.height / 2)));
8914 }
8915
8916 // update the point itself
8917 if (g_btouch) {
8918 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8919 // m_cursor_lat, m_cursor_lon);
8920 RoutePointGui(*m_pRoutePointEditTarget)
8921 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8922 // update the Drag Handle entry in the pSelect list
8923 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
8924 m_pRoutePointEditTarget,
8925 SELTYPE_DRAGHANDLE);
8926 m_pFoundPoint->m_slat =
8927 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8928 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8929 } else {
8930 m_pRoutePointEditTarget->m_lat =
8931 m_cursor_lat; // update the RoutePoint entry
8932 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
8933 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8934 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
8935 m_pFoundPoint->m_slon = m_cursor_lon;
8936 }
8937
8938 // Update the MarkProperties Dialog, if currently shown
8939 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8940 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8941 g_pMarkInfoDialog->UpdateProperties(true);
8942 }
8943
8944 // Invalidate the union region
8945 if (g_bopengl) {
8946 if (!g_btouch) InvalidateGL();
8947 Refresh(false);
8948 } else {
8949 // Get the update rectangle for the edited mark
8950 wxRect post_rect;
8951 RoutePointGui(*m_pRoutePointEditTarget)
8952 .CalculateDCRect(m_dc_route, this, &post_rect);
8953 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
8954 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
8955 (int)(lppmax - (post_rect.height / 2)));
8956
8957 // Invalidate the union region
8958 pre_rect.Union(post_rect);
8959 RefreshRect(pre_rect, false);
8960 }
8961 gFrame->RefreshCanvasOther(this);
8962 m_bRoutePoinDragging = true;
8963 }
8964 ret = true;
8965 }
8966
8967 if (ret) return true;
8968 } // dragging
8969
8970 if (event.LeftUp()) {
8971 bool b_startedit_route = false;
8972 m_dragoffsetSet = false;
8973
8974 if (g_btouch) {
8975 m_bChartDragging = false;
8976 m_bIsInRadius = false;
8977
8978 if (m_routeState) // creating route?
8979 {
8980 if (m_bedge_pan) {
8981 m_bedge_pan = false;
8982 return false;
8983 }
8984
8985 double rlat, rlon;
8986 bool appending = false;
8987 bool inserting = false;
8988 Route *tail = 0;
8989
8990 rlat = m_cursor_lat;
8991 rlon = m_cursor_lon;
8992
8993 if (m_pRoutePointEditTarget) {
8994 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8995 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8996 if (!g_bopengl) {
8997 wxRect wp_rect;
8998 RoutePointGui(*m_pRoutePointEditTarget)
8999 .CalculateDCRect(m_dc_route, this, &wp_rect);
9000 RefreshRect(wp_rect, true);
9001 }
9002 m_pRoutePointEditTarget = NULL;
9003 }
9004 m_bRouteEditing = true;
9005
9006 if (m_routeState == 1) {
9007 m_pMouseRoute = new Route();
9008 m_pMouseRoute->SetHiLite(50);
9009 pRouteList->push_back(m_pMouseRoute);
9010 r_rband.x = x;
9011 r_rband.y = y;
9012 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9013 }
9014
9015 // Check to see if there is a nearby point which may be reused
9016 RoutePoint *pMousePoint = NULL;
9017
9018 // Calculate meaningful SelectRadius
9019 double nearby_radius_meters =
9020 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9021
9022 RoutePoint *pNearbyPoint =
9023 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9024 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9025 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9026 int dlg_return;
9027#ifndef __WXOSX__
9028 m_FinishRouteOnKillFocus =
9029 false; // Avoid route finish on focus change for message dialog
9030 dlg_return = OCPNMessageBox(
9031 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9032 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9033 m_FinishRouteOnKillFocus = true;
9034#else
9035 dlg_return = wxID_YES;
9036#endif
9037 if (dlg_return == wxID_YES) {
9038 pMousePoint = pNearbyPoint;
9039
9040 // Using existing waypoint, so nothing to delete for undo.
9041 if (m_routeState > 1)
9042 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9043 Undo_HasParent, NULL);
9044 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9045
9046 bool procede = false;
9047 if (tail) {
9048 procede = true;
9049 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9050 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9051 procede = false;
9052 }
9053
9054 if (procede) {
9055 int dlg_return;
9056 m_FinishRouteOnKillFocus = false;
9057 if (m_routeState == 1) { // first point in new route, preceeding
9058 // route to be added? touch case
9059
9060 wxString dmsg =
9061 _("Insert first part of this route in the new route?");
9062 if (tail->GetIndexOf(pMousePoint) ==
9063 tail->GetnPoints()) // Starting on last point of another
9064 // route?
9065 dmsg = _("Insert this route in the new route?");
9066
9067 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9068 dlg_return =
9069 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9070 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9071 m_FinishRouteOnKillFocus = true;
9072
9073 if (dlg_return == wxID_YES) {
9074 inserting = true; // part of the other route will be
9075 // preceeding the new route
9076 }
9077 }
9078 } else {
9079 wxString dmsg =
9080 _("Append last part of this route to the new route?");
9081 if (tail->GetIndexOf(pMousePoint) == 1)
9082 dmsg = _(
9083 "Append this route to the new route?"); // Picking the
9084 // first point of
9085 // another route?
9086
9087 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9088 dlg_return =
9089 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9090 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9091 m_FinishRouteOnKillFocus = true;
9092
9093 if (dlg_return == wxID_YES) {
9094 appending = true; // part of the other route will be
9095 // appended to the new route
9096 }
9097 }
9098 }
9099 }
9100
9101 // check all other routes to see if this point appears in any other
9102 // route If it appears in NO other route, then it should e
9103 // considered an isolated mark
9104 if (!FindRouteContainingWaypoint(pMousePoint))
9105 pMousePoint->SetShared(true);
9106 }
9107 }
9108
9109 if (NULL == pMousePoint) { // need a new point
9110 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9111 "", wxEmptyString);
9112 pMousePoint->SetNameShown(false);
9113
9114 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9115
9116 if (m_routeState > 1)
9117 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9118 Undo_IsOrphanded, NULL);
9119 }
9120
9121 if (m_routeState == 1) {
9122 // First point in the route.
9123 m_pMouseRoute->AddPoint(pMousePoint);
9124 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9125
9126 } else {
9127 if (m_pMouseRoute->m_NextLegGreatCircle) {
9128 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9129 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9130 &rhumbBearing, &rhumbDist);
9131 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9132 &gcDist, &gcBearing, NULL);
9133 double gcDistNM = gcDist / 1852.0;
9134
9135 // Empirically found expression to get reasonable route segments.
9136 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9137 pow(rhumbDist - gcDistNM - 1, 0.5);
9138
9139 wxString msg;
9140 msg << _("For this leg the Great Circle route is ")
9141 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9142 << _(" shorter than rhumbline.\n\n")
9143 << _("Would you like include the Great Circle routing points "
9144 "for this leg?");
9145
9146#ifndef __WXOSX__
9147 m_FinishRouteOnKillFocus = false;
9148 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9149 wxYES_NO | wxNO_DEFAULT);
9150 m_FinishRouteOnKillFocus = true;
9151#else
9152 int answer = wxID_NO;
9153#endif
9154
9155 if (answer == wxID_YES) {
9156 RoutePoint *gcPoint;
9157 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9158 wxRealPoint gcCoord;
9159
9160 for (int i = 1; i <= segmentCount; i++) {
9161 double fraction = (double)i * (1.0 / (double)segmentCount);
9162 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9163 gcDist * fraction, gcBearing,
9164 &gcCoord.x, &gcCoord.y, NULL);
9165
9166 if (i < segmentCount) {
9167 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9168 wxEmptyString);
9169 gcPoint->SetNameShown(false);
9170 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9171 gcPoint);
9172 } else {
9173 gcPoint = pMousePoint; // Last point, previously exsisting!
9174 }
9175
9176 m_pMouseRoute->AddPoint(gcPoint);
9177 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9178
9179 pSelect->AddSelectableRouteSegment(
9180 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9181 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9182 prevGcPoint = gcPoint;
9183 }
9184
9185 undo->CancelUndoableAction(true);
9186
9187 } else {
9188 m_pMouseRoute->AddPoint(pMousePoint);
9189 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9190 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9191 rlon, m_prev_pMousePoint,
9192 pMousePoint, m_pMouseRoute);
9193 undo->AfterUndoableAction(m_pMouseRoute);
9194 }
9195 } else {
9196 // Ordinary rhumblinesegment.
9197 m_pMouseRoute->AddPoint(pMousePoint);
9198 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9199
9200 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9201 rlon, m_prev_pMousePoint,
9202 pMousePoint, m_pMouseRoute);
9203 undo->AfterUndoableAction(m_pMouseRoute);
9204 }
9205 }
9206
9207 m_prev_rlat = rlat;
9208 m_prev_rlon = rlon;
9209 m_prev_pMousePoint = pMousePoint;
9210 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9211
9212 m_routeState++;
9213
9214 if (appending ||
9215 inserting) { // Appending a route or making a new route
9216 int connect = tail->GetIndexOf(pMousePoint);
9217 if (connect == 1) {
9218 inserting = false; // there is nothing to insert
9219 appending = true; // so append
9220 }
9221 int length = tail->GetnPoints();
9222
9223 int i;
9224 int start, stop;
9225 if (appending) {
9226 start = connect + 1;
9227 stop = length;
9228 } else { // inserting
9229 start = 1;
9230 stop = connect;
9231 m_pMouseRoute->RemovePoint(
9232 m_pMouseRoute
9233 ->GetLastPoint()); // Remove the first and only point
9234 }
9235 for (i = start; i <= stop; i++) {
9236 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9237 if (m_pMouseRoute)
9238 m_pMouseRoute->m_lastMousePointIndex =
9239 m_pMouseRoute->GetnPoints();
9240 m_routeState++;
9241 gFrame->RefreshAllCanvas();
9242 ret = true;
9243 }
9244 m_prev_rlat =
9245 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9246 m_prev_rlon =
9247 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9248 m_pMouseRoute->FinalizeForRendering();
9249 }
9250
9251 Refresh(true);
9252 ret = true;
9253 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9254 {
9255 if (m_bedge_pan) {
9256 m_bedge_pan = false;
9257 return false;
9258 }
9259
9260 if (m_nMeasureState == 1) {
9261 m_pMeasureRoute = new Route();
9262 pRouteList->push_back(m_pMeasureRoute);
9263 r_rband.x = x;
9264 r_rband.y = y;
9265 }
9266
9267 if (m_pMeasureRoute) {
9268 RoutePoint *pMousePoint =
9269 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9270 wxEmptyString, wxEmptyString);
9271 pMousePoint->m_bShowName = false;
9272
9273 m_pMeasureRoute->AddPoint(pMousePoint);
9274
9275 m_prev_rlat = m_cursor_lat;
9276 m_prev_rlon = m_cursor_lon;
9277 m_prev_pMousePoint = pMousePoint;
9278 m_pMeasureRoute->m_lastMousePointIndex =
9279 m_pMeasureRoute->GetnPoints();
9280
9281 m_nMeasureState++;
9282 } else {
9283 CancelMeasureRoute();
9284 }
9285
9286 Refresh(true);
9287 ret = true;
9288 } else {
9289 bool bSelectAllowed = true;
9290 if (NULL == g_pMarkInfoDialog) {
9291 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9292 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9293 bSelectAllowed = false;
9294
9295 /*if this left up happens at the end of a route point dragging and if
9296 the cursor/thumb is on the draghandle icon, not on the point iself a new
9297 selection will select nothing and the drag will never be ended, so the
9298 legs around this point never selectable. At this step we don't need a
9299 new selection, just keep the previoulsly selected and dragged point */
9300 if (m_bRoutePoinDragging) bSelectAllowed = false;
9301
9302 if (bSelectAllowed) {
9303 bool b_was_editing_mark = m_bMarkEditing;
9304 bool b_was_editing_route = m_bRouteEditing;
9305 FindRoutePointsAtCursor(SelectRadius,
9306 true); // Possibly selecting a point in a
9307 // route for later dragging
9308
9309 /*route and a mark points in layer can't be dragged so should't be
9310 * selected and no draghandle icon*/
9311 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9312 m_pRoutePointEditTarget = NULL;
9313
9314 if (!b_was_editing_route) {
9315 if (m_pEditRouteArray) {
9316 b_startedit_route = true;
9317
9318 // Hide the track and route rollover during route point edit, not
9319 // needed, and may be confusing
9320 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9321 m_pTrackRolloverWin->IsActive(false);
9322 }
9323 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9324 m_pRouteRolloverWin->IsActive(false);
9325 }
9326
9327 wxRect pre_rect;
9328 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9329 ir++) {
9330 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9331 // Need to validate route pointer
9332 // Route may be gone due to drgging close to ownship with
9333 // "Delete On Arrival" state set, as in the case of
9334 // navigating to an isolated waypoint on a temporary route
9335 if (g_pRouteMan->IsRouteValid(pr)) {
9336 // pr->SetHiLite(50);
9337 wxRect route_rect;
9338 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9339 pre_rect.Union(route_rect);
9340 }
9341 }
9342 RefreshRect(pre_rect, true);
9343 }
9344 } else {
9345 b_startedit_route = false;
9346 }
9347
9348 // Mark editing
9349 if (m_pRoutePointEditTarget) {
9350 if (b_was_editing_mark ||
9351 b_was_editing_route) { // kill previous hilight
9352 if (m_lastRoutePointEditTarget) {
9353 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9354 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9355 RoutePointGui(*m_lastRoutePointEditTarget)
9356 .EnableDragHandle(false);
9357 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9358 SELTYPE_DRAGHANDLE);
9359 }
9360 }
9361
9362 if (m_pRoutePointEditTarget) {
9363 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9364 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9365 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9366 wxPoint2DDouble dragHandlePoint =
9367 RoutePointGui(*m_pRoutePointEditTarget)
9368 .GetDragHandlePoint(this);
9369 pSelect->AddSelectablePoint(
9370 dragHandlePoint.m_y, dragHandlePoint.m_x,
9371 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9372 }
9373 } else { // Deselect everything
9374 if (m_lastRoutePointEditTarget) {
9375 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9376 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9377 RoutePointGui(*m_lastRoutePointEditTarget)
9378 .EnableDragHandle(false);
9379 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9380 SELTYPE_DRAGHANDLE);
9381
9382 // Clear any routes being edited, probably orphans
9383 wxArrayPtrVoid *lastEditRouteArray =
9385 m_lastRoutePointEditTarget);
9386 if (lastEditRouteArray) {
9387 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9388 ir++) {
9389 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9390 if (g_pRouteMan->IsRouteValid(pr)) {
9391 pr->m_bIsBeingEdited = false;
9392 }
9393 }
9394 delete lastEditRouteArray;
9395 }
9396 }
9397 }
9398
9399 // Do the refresh
9400
9401 if (g_bopengl) {
9402 InvalidateGL();
9403 Refresh(false);
9404 } else {
9405 if (m_lastRoutePointEditTarget) {
9406 wxRect wp_rect;
9407 RoutePointGui(*m_lastRoutePointEditTarget)
9408 .CalculateDCRect(m_dc_route, this, &wp_rect);
9409 RefreshRect(wp_rect, true);
9410 }
9411
9412 if (m_pRoutePointEditTarget) {
9413 wxRect wp_rect;
9414 RoutePointGui(*m_pRoutePointEditTarget)
9415 .CalculateDCRect(m_dc_route, this, &wp_rect);
9416 RefreshRect(wp_rect, true);
9417 }
9418 }
9419 }
9420 } // bSelectAllowed
9421
9422 // Check to see if there is a route or AIS target under the cursor
9423 // If so, start the rollover timer which creates the popup
9424 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9425 bool b_start_rollover = false;
9426 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9427 SelectItem *pFind = pSelectAIS->FindSelection(
9428 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9429 if (pFind) b_start_rollover = true;
9430 }
9431
9432 if (!b_start_rollover && !b_startedit_route) {
9433 SelectableItemList SelList = pSelect->FindSelectionList(
9434 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9435 for (SelectItem *pFindSel : SelList) {
9436 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9437 if (pr && pr->IsVisible()) {
9438 b_start_rollover = true;
9439 break;
9440 }
9441 } // while
9442 }
9443
9444 if (!b_start_rollover && !b_startedit_route) {
9445 SelectableItemList SelList = pSelect->FindSelectionList(
9446 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9447 for (SelectItem *pFindSel : SelList) {
9448 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9449 if (tr && tr->IsVisible()) {
9450 b_start_rollover = true;
9451 break;
9452 }
9453 } // while
9454 }
9455
9456 if (b_start_rollover)
9457 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9458 wxTIMER_ONE_SHOT);
9459 Route *tail = 0;
9460 Route *current = 0;
9461 bool appending = false;
9462 bool inserting = false;
9463 int connect = 0;
9464 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9465 // drag
9466 if (m_pRoutePointEditTarget) {
9467 // Check to see if there is a nearby point which may replace the
9468 // dragged one
9469 RoutePoint *pMousePoint = NULL;
9470
9471 int index_last;
9472 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9473 double nearby_radius_meters =
9474 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9475 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9476 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9477 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9478 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9479 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9480 bool duplicate =
9481 false; // ensure we won't create duplicate point in routes
9482 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9483 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9484 ir++) {
9485 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9486 if (pr && pr->pRoutePointList) {
9487 auto *list = pr->pRoutePointList;
9488 auto pos =
9489 std::find(list->begin(), list->end(), pNearbyPoint);
9490 if (pos != list->end()) {
9491 duplicate = true;
9492 break;
9493 }
9494 }
9495 }
9496 }
9497
9498 // Special case:
9499 // Allow "re-use" of a route's waypoints iff it is a simple
9500 // isolated route. This allows, for instance, creation of a closed
9501 // polygon route
9502 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9503
9504 if (!duplicate) {
9505 int dlg_return;
9506 dlg_return =
9507 OCPNMessageBox(this,
9508 _("Replace this RoutePoint by the nearby "
9509 "Waypoint?"),
9510 _("OpenCPN RoutePoint change"),
9511 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9512 if (dlg_return == wxID_YES) {
9513 /*double confirmation if the dragged point has been manually
9514 * created which can be important and could be deleted
9515 * unintentionally*/
9516
9517 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9518 pNearbyPoint);
9519 current =
9520 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9521
9522 if (tail && current && (tail != current)) {
9523 int dlg_return1;
9524 connect = tail->GetIndexOf(pNearbyPoint);
9525 int index_current_route =
9526 current->GetIndexOf(m_pRoutePointEditTarget);
9527 index_last = current->GetIndexOf(current->GetLastPoint());
9528 dlg_return1 = wxID_NO;
9529 if (index_last ==
9530 index_current_route) { // we are dragging the last
9531 // point of the route
9532 if (connect != tail->GetnPoints()) { // anything to do?
9533
9534 wxString dmsg(
9535 _("Last part of route to be appended to dragged "
9536 "route?"));
9537 if (connect == 1)
9538 dmsg =
9539 _("Full route to be appended to dragged route?");
9540
9541 dlg_return1 = OCPNMessageBox(
9542 this, dmsg, _("OpenCPN Route Create"),
9543 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9544 if (dlg_return1 == wxID_YES) {
9545 appending = true;
9546 }
9547 }
9548 } else if (index_current_route ==
9549 1) { // dragging the first point of the route
9550 if (connect != 1) { // anything to do?
9551
9552 wxString dmsg(
9553 _("First part of route to be inserted into dragged "
9554 "route?"));
9555 if (connect == tail->GetnPoints())
9556 dmsg = _(
9557 "Full route to be inserted into dragged route?");
9558
9559 dlg_return1 = OCPNMessageBox(
9560 this, dmsg, _("OpenCPN Route Create"),
9561 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9562 if (dlg_return1 == wxID_YES) {
9563 inserting = true;
9564 }
9565 }
9566 }
9567 }
9568
9569 if (m_pRoutePointEditTarget->IsShared()) {
9570 // dlg_return = wxID_NO;
9571 dlg_return = OCPNMessageBox(
9572 this,
9573 _("Do you really want to delete and replace this "
9574 "WayPoint") +
9575 "\n" + _("which has been created manually?"),
9576 ("OpenCPN RoutePoint warning"),
9577 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9578 }
9579 }
9580 if (dlg_return == wxID_YES) {
9581 pMousePoint = pNearbyPoint;
9582 if (pMousePoint->m_bIsolatedMark) {
9583 pMousePoint->SetShared(true);
9584 }
9585 pMousePoint->m_bIsolatedMark =
9586 false; // definitely no longer isolated
9587 pMousePoint->m_bIsInRoute = true;
9588 }
9589 }
9590 }
9591 }
9592 if (!pMousePoint)
9593 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9594
9595 if (m_pEditRouteArray) {
9596 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9597 ir++) {
9598 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9599 if (g_pRouteMan->IsRouteValid(pr)) {
9600 if (pMousePoint) { // remove the dragged point and insert the
9601 // nearby
9602 auto *list = pr->pRoutePointList;
9603 auto pos = std::find(list->begin(), list->end(),
9604 m_pRoutePointEditTarget);
9605
9606 pSelect->DeleteAllSelectableRoutePoints(pr);
9607 pSelect->DeleteAllSelectableRouteSegments(pr);
9608
9609 pr->pRoutePointList->insert(pos, pMousePoint);
9610 pos = std::find(list->begin(), list->end(),
9611 m_pRoutePointEditTarget);
9612 pr->pRoutePointList->erase(pos);
9613
9614 pSelect->AddAllSelectableRouteSegments(pr);
9615 pSelect->AddAllSelectableRoutePoints(pr);
9616 }
9617 pr->FinalizeForRendering();
9618 pr->UpdateSegmentDistances();
9619 if (m_bRoutePoinDragging) {
9620 // pConfig->UpdateRoute(pr);
9621 NavObj_dB::GetInstance().UpdateRoute(pr);
9622 }
9623 }
9624 }
9625 }
9626
9627 // Update the RouteProperties Dialog, if currently shown
9628 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9629 if (m_pEditRouteArray) {
9630 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9631 ir++) {
9632 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9633 if (g_pRouteMan->IsRouteValid(pr)) {
9634 if (pRoutePropDialog->GetRoute() == pr) {
9635 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9636 }
9637 /* cannot edit track points anyway
9638 else if ( ( NULL !=
9639 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9640 pTrackPropDialog->m_pTrack == pr ) {
9641 pTrackPropDialog->SetTrackAndUpdate(
9642 pr );
9643 }
9644 */
9645 }
9646 }
9647 }
9648 }
9649 if (pMousePoint) { // clear all about the dragged point
9650 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9651 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9652 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9653 // Hide mark properties dialog if open on the replaced point
9654 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9655 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9656 g_pMarkInfoDialog->Hide();
9657
9658 delete m_pRoutePointEditTarget;
9659 m_lastRoutePointEditTarget = NULL;
9660 m_pRoutePointEditTarget = NULL;
9661 undo->AfterUndoableAction(pMousePoint);
9662 undo->InvalidateUndo();
9663 }
9664 }
9665 }
9666
9667 else if (m_bMarkEditing) { // End of way point drag
9668 if (m_pRoutePointEditTarget)
9669 if (m_bRoutePoinDragging) {
9670 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9671 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9672 }
9673 }
9674
9675 if (m_pRoutePointEditTarget)
9676 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9677
9678 if (!m_pRoutePointEditTarget) {
9679 delete m_pEditRouteArray;
9680 m_pEditRouteArray = NULL;
9681 m_bRouteEditing = false;
9682 }
9683 m_bRoutePoinDragging = false;
9684
9685 if (appending) { // Appending to the route of which the last point is
9686 // dragged onto another route
9687
9688 // copy tail from connect until length to end of current after dragging
9689
9690 int length = tail->GetnPoints();
9691 for (int i = connect + 1; i <= length; i++) {
9692 current->AddPointAndSegment(tail->GetPoint(i), false);
9693 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9694 m_routeState++;
9695 gFrame->RefreshAllCanvas();
9696 ret = true;
9697 }
9698 current->FinalizeForRendering();
9699 current->m_bIsBeingEdited = false;
9700 FinishRoute();
9701 g_pRouteMan->DeleteRoute(tail);
9702 }
9703 if (inserting) {
9704 pSelect->DeleteAllSelectableRoutePoints(current);
9705 pSelect->DeleteAllSelectableRouteSegments(current);
9706 for (int i = 1; i < connect; i++) { // numbering in the tail route
9707 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9708 }
9709 pSelect->AddAllSelectableRouteSegments(current);
9710 pSelect->AddAllSelectableRoutePoints(current);
9711 current->FinalizeForRendering();
9712 current->m_bIsBeingEdited = false;
9713 g_pRouteMan->DeleteRoute(tail);
9714 }
9715
9716 // Update the RouteProperties Dialog, if currently shown
9717 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9718 if (m_pEditRouteArray) {
9719 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9720 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9721 if (g_pRouteMan->IsRouteValid(pr)) {
9722 if (pRoutePropDialog->GetRoute() == pr) {
9723 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9724 }
9725 }
9726 }
9727 }
9728 }
9729
9730 } // g_btouch
9731
9732 else { // !g_btouch
9733 if (m_bRouteEditing) { // End of RoutePoint drag
9734 Route *tail = 0;
9735 Route *current = 0;
9736 bool appending = false;
9737 bool inserting = false;
9738 int connect = 0;
9739 int index_last;
9740 if (m_pRoutePointEditTarget) {
9741 m_pRoutePointEditTarget->m_bBlink = false;
9742 // Check to see if there is a nearby point which may replace the
9743 // dragged one
9744 RoutePoint *pMousePoint = NULL;
9745 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9746 double nearby_radius_meters =
9747 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9748 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9749 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9750 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9751 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9752 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9753 bool duplicate = false; // don't create duplicate point in routes
9754 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9755 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9756 ir++) {
9757 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9758 if (pr && pr->pRoutePointList) {
9759 auto *list = pr->pRoutePointList;
9760 auto pos =
9761 std::find(list->begin(), list->end(), pNearbyPoint);
9762 if (pos != list->end()) {
9763 duplicate = true;
9764 break;
9765 }
9766 }
9767 }
9768 }
9769
9770 // Special case:
9771 // Allow "re-use" of a route's waypoints iff it is a simple
9772 // isolated route. This allows, for instance, creation of a closed
9773 // polygon route
9774 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9775
9776 if (!duplicate) {
9777 int dlg_return;
9778 dlg_return =
9779 OCPNMessageBox(this,
9780 _("Replace this RoutePoint by the nearby "
9781 "Waypoint?"),
9782 _("OpenCPN RoutePoint change"),
9783 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9784 if (dlg_return == wxID_YES) {
9785 /*double confirmation if the dragged point has been manually
9786 * created which can be important and could be deleted
9787 * unintentionally*/
9788 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9789 pNearbyPoint);
9790 current =
9791 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9792
9793 if (tail && current && (tail != current)) {
9794 int dlg_return1;
9795 connect = tail->GetIndexOf(pNearbyPoint);
9796 int index_current_route =
9797 current->GetIndexOf(m_pRoutePointEditTarget);
9798 index_last = current->GetIndexOf(current->GetLastPoint());
9799 dlg_return1 = wxID_NO;
9800 if (index_last ==
9801 index_current_route) { // we are dragging the last
9802 // point of the route
9803 if (connect != tail->GetnPoints()) { // anything to do?
9804
9805 wxString dmsg(
9806 _("Last part of route to be appended to dragged "
9807 "route?"));
9808 if (connect == 1)
9809 dmsg =
9810 _("Full route to be appended to dragged route?");
9811
9812 dlg_return1 = OCPNMessageBox(
9813 this, dmsg, _("OpenCPN Route Create"),
9814 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9815 if (dlg_return1 == wxID_YES) {
9816 appending = true;
9817 }
9818 }
9819 } else if (index_current_route ==
9820 1) { // dragging the first point of the route
9821 if (connect != 1) { // anything to do?
9822
9823 wxString dmsg(
9824 _("First part of route to be inserted into dragged "
9825 "route?"));
9826 if (connect == tail->GetnPoints())
9827 dmsg = _(
9828 "Full route to be inserted into dragged route?");
9829
9830 dlg_return1 = OCPNMessageBox(
9831 this, dmsg, _("OpenCPN Route Create"),
9832 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9833 if (dlg_return1 == wxID_YES) {
9834 inserting = true;
9835 }
9836 }
9837 }
9838 }
9839
9840 if (m_pRoutePointEditTarget->IsShared()) {
9841 dlg_return = wxID_NO;
9842 dlg_return = OCPNMessageBox(
9843 this,
9844 _("Do you really want to delete and replace this "
9845 "WayPoint") +
9846 "\n" + _("which has been created manually?"),
9847 ("OpenCPN RoutePoint warning"),
9848 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9849 }
9850 }
9851 if (dlg_return == wxID_YES) {
9852 pMousePoint = pNearbyPoint;
9853 if (pMousePoint->m_bIsolatedMark) {
9854 pMousePoint->SetShared(true);
9855 }
9856 pMousePoint->m_bIsolatedMark =
9857 false; // definitely no longer isolated
9858 pMousePoint->m_bIsInRoute = true;
9859 }
9860 }
9861 }
9862 }
9863 if (!pMousePoint)
9864 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9865
9866 if (m_pEditRouteArray) {
9867 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9868 ir++) {
9869 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9870 if (g_pRouteMan->IsRouteValid(pr)) {
9871 if (pMousePoint) { // replace dragged point by nearby one
9872 auto *list = pr->pRoutePointList;
9873 auto pos = std::find(list->begin(), list->end(),
9874 m_pRoutePointEditTarget);
9875
9876 pSelect->DeleteAllSelectableRoutePoints(pr);
9877 pSelect->DeleteAllSelectableRouteSegments(pr);
9878
9879 pr->pRoutePointList->insert(pos, pMousePoint);
9880 pos = std::find(list->begin(), list->end(),
9881 m_pRoutePointEditTarget);
9882 if (pos != list->end()) list->erase(pos);
9883 // pr->pRoutePointList->erase(pos + 1);
9884
9885 pSelect->AddAllSelectableRouteSegments(pr);
9886 pSelect->AddAllSelectableRoutePoints(pr);
9887 }
9888 pr->FinalizeForRendering();
9889 pr->UpdateSegmentDistances();
9890 pr->m_bIsBeingEdited = false;
9891
9892 if (m_bRoutePoinDragging) {
9893 // Special case optimization.
9894 // Dragging a single point of a route
9895 // without any point additions or re-ordering
9896 if (!pMousePoint)
9897 NavObj_dB::GetInstance().UpdateRoutePoint(
9898 m_pRoutePointEditTarget);
9899 else
9900 NavObj_dB::GetInstance().UpdateRoute(pr);
9901 }
9902 pr->SetHiLite(0);
9903 }
9904 }
9905 Refresh(false);
9906 }
9907
9908 if (appending) {
9909 // copy tail from connect until length to end of current after
9910 // dragging
9911
9912 int length = tail->GetnPoints();
9913 for (int i = connect + 1; i <= length; i++) {
9914 current->AddPointAndSegment(tail->GetPoint(i), false);
9915 if (current)
9916 current->m_lastMousePointIndex = current->GetnPoints();
9917 m_routeState++;
9918 gFrame->RefreshAllCanvas();
9919 ret = true;
9920 }
9921 current->FinalizeForRendering();
9922 current->m_bIsBeingEdited = false;
9923 FinishRoute();
9924 g_pRouteMan->DeleteRoute(tail);
9925 }
9926 if (inserting) {
9927 pSelect->DeleteAllSelectableRoutePoints(current);
9928 pSelect->DeleteAllSelectableRouteSegments(current);
9929 for (int i = 1; i < connect; i++) { // numbering in the tail route
9930 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9931 }
9932 pSelect->AddAllSelectableRouteSegments(current);
9933 pSelect->AddAllSelectableRoutePoints(current);
9934 current->FinalizeForRendering();
9935 current->m_bIsBeingEdited = false;
9936 g_pRouteMan->DeleteRoute(tail);
9937 }
9938
9939 // Update the RouteProperties Dialog, if currently shown
9940 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9941 if (m_pEditRouteArray) {
9942 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9943 ir++) {
9944 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9945 if (g_pRouteMan->IsRouteValid(pr)) {
9946 if (pRoutePropDialog->GetRoute() == pr) {
9947 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9948 }
9949 }
9950 }
9951 }
9952 }
9953
9954 if (pMousePoint) {
9955 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9956 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9957 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9958 // Hide mark properties dialog if open on the replaced point
9959 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9960 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9961 g_pMarkInfoDialog->Hide();
9962
9963 delete m_pRoutePointEditTarget;
9964 m_lastRoutePointEditTarget = NULL;
9965 undo->AfterUndoableAction(pMousePoint);
9966 undo->InvalidateUndo();
9967 } else {
9968 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9969 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9970
9971 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9972 }
9973
9974 delete m_pEditRouteArray;
9975 m_pEditRouteArray = NULL;
9976 }
9977
9978 InvalidateGL();
9979 m_bRouteEditing = false;
9980 m_pRoutePointEditTarget = NULL;
9981
9982 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
9983 ret = true;
9984 }
9985
9986 else if (m_bMarkEditing) { // end of Waypoint drag
9987 if (m_pRoutePointEditTarget) {
9988 if (m_bRoutePoinDragging) {
9989 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9990 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9991 }
9992 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9993 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9994 if (!g_bopengl) {
9995 wxRect wp_rect;
9996 RoutePointGui(*m_pRoutePointEditTarget)
9997 .CalculateDCRect(m_dc_route, this, &wp_rect);
9998 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9999 RefreshRect(wp_rect, true);
10000 }
10001 }
10002 m_pRoutePointEditTarget = NULL;
10003 m_bMarkEditing = false;
10004 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10005 ret = true;
10006 }
10007
10008 else if (leftIsDown) { // left click for chart center
10009 leftIsDown = false;
10010 ret = false;
10011
10012 if (!g_btouch) {
10013 if (!m_bChartDragging && !m_bMeasure_Active) {
10014 } else {
10015 m_bChartDragging = false;
10016 }
10017 }
10018 }
10019 m_bRoutePoinDragging = false;
10020 } // !btouch
10021
10022 if (ret) return true;
10023 } // left up
10024
10025 if (event.RightDown()) {
10026 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10027 last_drag.x = mx;
10028 last_drag.y = my;
10029
10030 if (g_btouch) {
10031 // if( m_pRoutePointEditTarget )
10032 // return false;
10033 }
10034
10035 ret = true;
10036 m_FinishRouteOnKillFocus = false;
10037 CallPopupMenu(mx, my);
10038 m_FinishRouteOnKillFocus = true;
10039 } // Right down
10040
10041 return ret;
10042}
10043
10044bool panleftIsDown;
10045
10046bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10047 // Skip all mouse processing if shift is held.
10048 // This allows plugins to implement shift+drag behaviors.
10049 if (event.ShiftDown()) {
10050 return false;
10051 }
10052 int x, y;
10053 event.GetPosition(&x, &y);
10054
10055 x *= m_displayScale;
10056 y *= m_displayScale;
10057
10058 // Check for wheel rotation
10059 // ideally, should be just longer than the time between
10060 // processing accumulated mouse events from the event queue
10061 // as would happen during screen redraws.
10062 int wheel_dir = event.GetWheelRotation();
10063
10064 if (wheel_dir) {
10065 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10066 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10067
10068 double factor = g_mouse_zoom_sensitivity;
10069 if (wheel_dir < 0) factor = 1 / factor;
10070
10071 if (g_bsmoothpanzoom) {
10072 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10073 if (wheel_dir == m_last_wheel_dir) {
10074 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10075 // m_zoom_target /= factor;
10076 } else
10077 StopMovement();
10078 } else {
10079 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10080 m_wheelstopwatch.Start(0);
10081 // m_zoom_target = VPoint.chart_scale / factor;
10082 }
10083 }
10084
10085 m_last_wheel_dir = wheel_dir;
10086
10087 ZoomCanvas(factor, true, false);
10088 }
10089
10090 if (event.LeftDown()) {
10091 // Skip the first left click if it will cause a canvas focus shift
10092 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10093 return false;
10094 }
10095
10096 last_drag.x = x, last_drag.y = y;
10097 panleftIsDown = true;
10098 }
10099
10100 if (event.LeftUp()) {
10101 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10102 // seen here.
10103 panleftIsDown = false;
10104
10105 if (!g_btouch) {
10106 if (!m_bChartDragging && !m_bMeasure_Active) {
10107 switch (cursor_region) {
10108 case MID_RIGHT: {
10109 PanCanvas(100, 0);
10110 break;
10111 }
10112
10113 case MID_LEFT: {
10114 PanCanvas(-100, 0);
10115 break;
10116 }
10117
10118 case MID_TOP: {
10119 PanCanvas(0, 100);
10120 break;
10121 }
10122
10123 case MID_BOT: {
10124 PanCanvas(0, -100);
10125 break;
10126 }
10127
10128 case CENTER: {
10129 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10130 break;
10131 }
10132 }
10133 } else {
10134 m_bChartDragging = false;
10135 }
10136 }
10137 }
10138 }
10139
10140 if (event.Dragging() && event.LeftIsDown()) {
10141 /*
10142 * fixed dragging.
10143 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10144 * before the drag event. Hence, as there is no mouse down event, last_drag
10145 * is not reset before the drag. And that results in one single drag
10146 * session, meaning you cannot drag the map a few miles north, lift your
10147 * finger, and the go even further north. Instead, the map resets itself
10148 * always to the very first drag start (since there is not reset of
10149 * last_drag).
10150 *
10151 * Besides, should not left down and dragging be enough of a situation to
10152 * start a drag procedure?
10153 *
10154 * Anyways, guarded it to be active in touch situations only.
10155 */
10156
10157 if (g_btouch && !m_inPinch) {
10158 struct timespec now;
10159 clock_gettime(CLOCK_MONOTONIC, &now);
10160 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10161
10162 if (false == m_bChartDragging) {
10163 // Reset drag calculation members
10164 last_drag.x = x, last_drag.y = y;
10165 m_bChartDragging = true;
10166 m_chart_drag_total_time = 0;
10167 m_chart_drag_total_x = 0;
10168 m_chart_drag_total_y = 0;
10169 m_inertia_last_drag_x = x;
10170 m_inertia_last_drag_y = y;
10171 m_drag_vec_x.clear();
10172 m_drag_vec_y.clear();
10173 m_drag_vec_t.clear();
10174 m_last_drag_time = tnow;
10175 }
10176
10177 // Calculate and store drag dynamics.
10178 uint64_t delta_t = tnow - m_last_drag_time;
10179 double delta_tf = delta_t / 1e9;
10180
10181 m_chart_drag_total_time += delta_tf;
10182 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10183 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10184
10185 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10186 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10187 m_drag_vec_t.push_back(delta_tf);
10188
10189 m_inertia_last_drag_x = x;
10190 m_inertia_last_drag_y = y;
10191 m_last_drag_time = tnow;
10192
10193 if ((last_drag.x != x) || (last_drag.y != y)) {
10194 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10195 // dragging on route create.
10196 // github #2994
10197 m_bChartDragging = true;
10198 StartTimedMovement();
10199 m_pan_drag.x += last_drag.x - x;
10200 m_pan_drag.y += last_drag.y - y;
10201 last_drag.x = x, last_drag.y = y;
10202 }
10203 }
10204 } else if (!g_btouch) {
10205 if ((last_drag.x != x) || (last_drag.y != y)) {
10206 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10207 // dragging on route create.
10208 // github #2994
10209 m_bChartDragging = true;
10210 StartTimedMovement();
10211 m_pan_drag.x += last_drag.x - x;
10212 m_pan_drag.y += last_drag.y - y;
10213 last_drag.x = x, last_drag.y = y;
10214 }
10215 }
10216 }
10217
10218 // Handle some special cases
10219 if (g_btouch) {
10220 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10221 // deactivate next LeftUp to ovoid creating an unexpected point
10222 m_DoubleClickTimer->Start();
10223 singleClickEventIsValid = false;
10224 }
10225 }
10226 }
10227
10228 return true;
10229}
10230
10231void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10232 if (MouseEventOverlayWindows(event)) return;
10233
10234 if (MouseEventSetup(event)) return; // handled, no further action required
10235
10236 if (!MouseEventProcessObjects(event)) MouseEventProcessCanvas(event);
10237}
10238
10239void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10240 // Switch to the appropriate cursor on mouse movement
10241
10242 wxCursor *ptarget_cursor = pCursorArrow;
10243 if (!pPlugIn_Cursor) {
10244 ptarget_cursor = pCursorArrow;
10245 if ((!m_routeState) &&
10246 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10247 if (cursor_region == MID_RIGHT) {
10248 ptarget_cursor = pCursorRight;
10249 } else if (cursor_region == MID_LEFT) {
10250 ptarget_cursor = pCursorLeft;
10251 } else if (cursor_region == MID_TOP) {
10252 ptarget_cursor = pCursorDown;
10253 } else if (cursor_region == MID_BOT) {
10254 ptarget_cursor = pCursorUp;
10255 } else {
10256 ptarget_cursor = pCursorArrow;
10257 }
10258 } else if (m_bMeasure_Active ||
10259 m_routeState) // If Measure tool use Pencil Cursor
10260 ptarget_cursor = pCursorPencil;
10261 } else {
10262 ptarget_cursor = pPlugIn_Cursor;
10263 }
10264
10265 SetCursor(*ptarget_cursor);
10266}
10267
10268void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10269 SetCursor(*pCursorArrow);
10270}
10271
10272void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10273 ChartPlugInWrapper *target_plugin_chart = NULL;
10274 s57chart *Chs57 = NULL;
10275 wxFileName file;
10276 wxArrayString files;
10277
10278 ChartBase *target_chart = GetChartAtCursor();
10279 if (target_chart) {
10280 file.Assign(target_chart->GetFullPath());
10281 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10282 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10283 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10284 else
10285 Chs57 = dynamic_cast<s57chart *>(target_chart);
10286 } else { // target_chart = null, might be mbtiles
10287 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10288 unsigned int im = stackIndexArray.size();
10289 int scale = 2147483647; // max 32b integer
10290 if (VPoint.b_quilt && im > 0) {
10291 for (unsigned int is = 0; is < im; is++) {
10292 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10293 CHART_TYPE_MBTILES) {
10294 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10295 double lat, lon;
10296 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10297 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10298 .GetBBox()
10299 .Contains(lat, lon)) {
10300 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10301 scale) {
10302 scale =
10303 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10304 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10305 }
10306 }
10307 }
10308 }
10309 }
10310 }
10311
10312 std::vector<Ais8_001_22 *> area_notices;
10313
10314 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10315 float vp_scale = GetVPScale();
10316
10317 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10318 auto target_data = target.second;
10319 if (!target_data->area_notices.empty()) {
10320 for (auto &ani : target_data->area_notices) {
10321 Ais8_001_22 &area_notice = ani.second;
10322
10323 BoundingBox bbox;
10324
10325 for (Ais8_001_22_SubAreaList::iterator sa =
10326 area_notice.sub_areas.begin();
10327 sa != area_notice.sub_areas.end(); ++sa) {
10328 switch (sa->shape) {
10329 case AIS8_001_22_SHAPE_CIRCLE: {
10330 wxPoint target_point;
10331 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10332 bbox.Expand(target_point);
10333 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10334 break;
10335 }
10336 case AIS8_001_22_SHAPE_RECT: {
10337 wxPoint target_point;
10338 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10339 bbox.Expand(target_point);
10340 if (sa->e_dim_m > sa->n_dim_m)
10341 bbox.EnLarge(sa->e_dim_m * vp_scale);
10342 else
10343 bbox.EnLarge(sa->n_dim_m * vp_scale);
10344 break;
10345 }
10346 case AIS8_001_22_SHAPE_POLYGON:
10347 case AIS8_001_22_SHAPE_POLYLINE: {
10348 for (int i = 0; i < 4; ++i) {
10349 double lat = sa->latitude;
10350 double lon = sa->longitude;
10351 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10352 &lat, &lon);
10353 wxPoint target_point;
10354 GetCanvasPointPix(lat, lon, &target_point);
10355 bbox.Expand(target_point);
10356 }
10357 break;
10358 }
10359 case AIS8_001_22_SHAPE_SECTOR: {
10360 double lat1 = sa->latitude;
10361 double lon1 = sa->longitude;
10362 double lat, lon;
10363 wxPoint target_point;
10364 GetCanvasPointPix(lat1, lon1, &target_point);
10365 bbox.Expand(target_point);
10366 for (int i = 0; i < 18; ++i) {
10367 ll_gc_ll(
10368 lat1, lon1,
10369 sa->left_bound_deg +
10370 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10371 sa->radius_m / 1852.0, &lat, &lon);
10372 GetCanvasPointPix(lat, lon, &target_point);
10373 bbox.Expand(target_point);
10374 }
10375 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10376 &lat, &lon);
10377 GetCanvasPointPix(lat, lon, &target_point);
10378 bbox.Expand(target_point);
10379 break;
10380 }
10381 }
10382 }
10383
10384 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10385 area_notices.push_back(&area_notice);
10386 }
10387 }
10388 }
10389 }
10390 }
10391
10392 if (target_chart || !area_notices.empty() || file.HasName()) {
10393 // Go get the array of all objects at the cursor lat/lon
10394 int sel_rad_pix = 5;
10395 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10396
10397 // Make sure we always get the lights from an object, even if we are
10398 // currently not displaying lights on the chart.
10399
10400 SetCursor(wxCURSOR_WAIT);
10401 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10402 if (!lightsVis) SetShowENCLights(true);
10403 ;
10404
10405 ListOfObjRazRules *rule_list = NULL;
10406 ListOfPI_S57Obj *pi_rule_list = NULL;
10407 if (Chs57)
10408 rule_list =
10409 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10410 else if (target_plugin_chart)
10411 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10412 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10413
10414 ListOfObjRazRules *overlay_rule_list = NULL;
10415 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10416 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10417
10418 if (CHs57_Overlay) {
10419 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10420 zlat, zlon, SelectRadius, &GetVP());
10421 }
10422
10423 if (!lightsVis) SetShowENCLights(false);
10424
10425 wxString objText;
10426 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10427 wxString face = dFont->GetFaceName();
10428
10429 if (NULL == g_pObjectQueryDialog) {
10431 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10432 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10433 }
10434
10435 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10436 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10437
10438#ifdef __WXOSX__
10439 // Auto Adjustment for dark mode
10440 fg = g_pObjectQueryDialog->GetForegroundColour();
10441#endif
10442
10443 objText.Printf(
10444 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10445 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10446
10447#ifdef __WXOSX__
10448 int points = dFont->GetPointSize();
10449#else
10450 int points = dFont->GetPointSize() + 1;
10451#endif
10452
10453 int sizes[7];
10454 for (int i = -2; i < 5; i++) {
10455 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10456 }
10457 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10458
10459 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10460
10461 if (overlay_rule_list && CHs57_Overlay) {
10462 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10463 objText << "<hr noshade>";
10464 }
10465
10466 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10467 an != area_notices.end(); ++an) {
10468 objText << "<b>AIS Area Notice:</b> ";
10469 objText << ais8_001_22_notice_names[(*an)->notice_type];
10470 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10471 (*an)->sub_areas.begin();
10472 sa != (*an)->sub_areas.end(); ++sa)
10473 if (!sa->text.empty()) objText << sa->text;
10474 objText << "<br>expires: " << (*an)->expiry_time.Format();
10475 objText << "<hr noshade>";
10476 }
10477
10478 if (Chs57)
10479 objText << Chs57->CreateObjDescriptions(rule_list);
10480 else if (target_plugin_chart)
10481 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10482 pi_rule_list);
10483
10484 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10485
10486 // Add the additional info files
10487 wxString AddFiles, filenameOK;
10488 int filecount = 0;
10489 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10490 // plugin
10491
10492 AddFiles = wxString::Format(
10493 "<hr noshade><br><b>Additional info files attached to: </b> "
10494 "<font "
10495 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10496 "cellpadding=3>",
10497 file.GetFullName());
10498 file.Normalize();
10499 file.Assign(file.GetPath(), "");
10500 wxDir dir(file.GetFullPath());
10501 wxString filename;
10502 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10503 while (cont) {
10504 file.Assign(dir.GetNameWithSep().append(filename));
10505 wxString FormatString =
10506 "<td valign=top><font size=-2><a "
10507 "href=\"%s\">%s</a></font></td>";
10508 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10509 filenameOK = file.GetFullPath(); // remember last valid name
10510 // we are making a 3 columns table. New row only every third file
10511 if (3 * ((int)filecount / 3) == filecount)
10512 FormatString.Prepend("<tr>"); // new row
10513 else
10514 FormatString.Prepend(
10515 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10516 // spacer column
10517
10518 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10519 file.GetFullName());
10520 filecount++;
10521 }
10522 cont = dir.GetNext(&filename);
10523 }
10524 objText << AddFiles << "</table>";
10525 }
10526 objText << "</font>";
10527 objText << "</body></html>";
10528
10529 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10530 g_pObjectQueryDialog->SetHTMLPage(objText);
10531 g_pObjectQueryDialog->Show();
10532 }
10533 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10534 // generate an event to avoid double code
10535 wxHtmlLinkInfo hli(filenameOK);
10536 wxHtmlLinkEvent hle(1, hli);
10537 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10538 }
10539
10540 if (rule_list) rule_list->Clear();
10541 delete rule_list;
10542
10543 if (overlay_rule_list) overlay_rule_list->Clear();
10544 delete overlay_rule_list;
10545
10546 if (pi_rule_list) pi_rule_list->Clear();
10547 delete pi_rule_list;
10548
10549 SetCursor(wxCURSOR_ARROW);
10550 }
10551}
10552
10553void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10554 bool bNew = false;
10555 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10556 // Dialog
10557 g_pMarkInfoDialog = new MarkInfoDlg(this);
10558 bNew = true;
10559 }
10560
10561 if (1 /*g_bresponsive*/) {
10562 wxSize canvas_size = GetSize();
10563
10564 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10565 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10566
10567 g_pMarkInfoDialog->Layout();
10568
10569 wxPoint canvas_pos = GetPosition();
10570 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10571
10572 bool newFit = false;
10573 if (canvas_size.x < fitted_size.x) {
10574 fitted_size.x = canvas_size.x - 40;
10575 if (canvas_size.y < fitted_size.y)
10576 fitted_size.y -= 40; // scrollbar added
10577 }
10578 if (canvas_size.y < fitted_size.y) {
10579 fitted_size.y = canvas_size.y - 40;
10580 if (canvas_size.x < fitted_size.x)
10581 fitted_size.x -= 40; // scrollbar added
10582 }
10583
10584 if (newFit) {
10585 g_pMarkInfoDialog->SetSize(fitted_size);
10586 g_pMarkInfoDialog->Centre();
10587 }
10588 }
10589
10590 markPoint->m_bRPIsBeingEdited = false;
10591
10592 wxString title_base = _("Mark Properties");
10593 if (markPoint->m_bIsInRoute) {
10594 title_base = _("Waypoint Properties");
10595 }
10596 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10597 g_pMarkInfoDialog->UpdateProperties();
10598 if (markPoint->m_bIsInLayer) {
10599 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10600 GetLayerName(markPoint->m_LayerID)));
10601 g_pMarkInfoDialog->SetDialogTitle(caption);
10602 } else
10603 g_pMarkInfoDialog->SetDialogTitle(title_base);
10604
10605 g_pMarkInfoDialog->Show();
10606 g_pMarkInfoDialog->Raise();
10607 g_pMarkInfoDialog->InitialFocus();
10608 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10609}
10610
10611void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10612 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10613 pRoutePropDialog->SetRouteAndUpdate(selected);
10614 // pNew->UpdateProperties();
10615 pRoutePropDialog->Show();
10616 pRoutePropDialog->Raise();
10617 return;
10618 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10619 this); // There is one global instance of the RouteProp Dialog
10620
10621 if (g_bresponsive) {
10622 wxSize canvas_size = GetSize();
10623 wxPoint canvas_pos = GetPosition();
10624 wxSize fitted_size = pRoutePropDialog->GetSize();
10625 ;
10626
10627 if (canvas_size.x < fitted_size.x) {
10628 fitted_size.x = canvas_size.x;
10629 if (canvas_size.y < fitted_size.y)
10630 fitted_size.y -= 20; // scrollbar added
10631 }
10632 if (canvas_size.y < fitted_size.y) {
10633 fitted_size.y = canvas_size.y;
10634 if (canvas_size.x < fitted_size.x)
10635 fitted_size.x -= 20; // scrollbar added
10636 }
10637
10638 pRoutePropDialog->SetSize(fitted_size);
10639 pRoutePropDialog->Centre();
10640
10641 // int xp = (canvas_size.x - fitted_size.x)/2;
10642 // int yp = (canvas_size.y - fitted_size.y)/2;
10643
10644 wxPoint xxp = ClientToScreen(canvas_pos);
10645 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10646 }
10647
10648 pRoutePropDialog->SetRouteAndUpdate(selected);
10649
10650 pRoutePropDialog->Show();
10651
10652 Refresh(false);
10653}
10654
10655void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10656 pTrackPropDialog = TrackPropDlg::getInstance(
10657 this); // There is one global instance of the RouteProp Dialog
10658
10659 pTrackPropDialog->SetTrackAndUpdate(selected);
10661
10662 pTrackPropDialog->Show();
10663
10664 Refresh(false);
10665}
10666
10667void pupHandler_PasteWaypoint() {
10668 Kml kml;
10669
10670 int pasteBuffer = kml.ParsePasteBuffer();
10671 RoutePoint *pasted = kml.GetParsedRoutePoint();
10672 if (!pasted) return;
10673
10674 double nearby_radius_meters =
10675 g_Platform->GetSelectRadiusPix() /
10676 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10677
10678 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10679 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10680
10681 int answer = wxID_NO;
10682 if (nearPoint && !nearPoint->m_bIsInLayer) {
10683 wxString msg;
10684 msg << _(
10685 "There is an existing waypoint at the same location as the one you are "
10686 "pasting. Would you like to merge the pasted data with it?\n\n");
10687 msg << _("Answering 'No' will create a new waypoint at the same location.");
10688 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10689 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10690 }
10691
10692 if (answer == wxID_YES) {
10693 nearPoint->SetName(pasted->GetName());
10694 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10695 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10696 pRouteManagerDialog->UpdateWptListCtrl();
10697 }
10698
10699 if (answer == wxID_NO) {
10700 RoutePoint *newPoint = new RoutePoint(pasted);
10701 newPoint->m_bIsolatedMark = true;
10702 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10703 newPoint);
10704 // pConfig->AddNewWayPoint(newPoint, -1);
10705 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10706
10707 pWayPointMan->AddRoutePoint(newPoint);
10708 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10709 pRouteManagerDialog->UpdateWptListCtrl();
10710 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10711 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10712 }
10713
10714 gFrame->InvalidateAllGL();
10715 gFrame->RefreshAllCanvas(false);
10716}
10717
10718void pupHandler_PasteRoute() {
10719 Kml kml;
10720
10721 int pasteBuffer = kml.ParsePasteBuffer();
10722 Route *pasted = kml.GetParsedRoute();
10723 if (!pasted) return;
10724
10725 double nearby_radius_meters =
10726 g_Platform->GetSelectRadiusPix() /
10727 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10728
10729 RoutePoint *curPoint;
10730 RoutePoint *nearPoint;
10731 RoutePoint *prevPoint = NULL;
10732
10733 bool mergepoints = false;
10734 bool createNewRoute = true;
10735 int existingWaypointCounter = 0;
10736
10737 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10738 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10739 nearPoint = pWayPointMan->GetNearbyWaypoint(
10740 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10741 if (nearPoint) {
10742 mergepoints = true;
10743 existingWaypointCounter++;
10744 // Small hack here to avoid both extending RoutePoint and repeating all
10745 // the GetNearbyWaypoint calculations. Use existin data field in
10746 // RoutePoint as temporary storage.
10747 curPoint->m_bPtIsSelected = true;
10748 }
10749 }
10750
10751 int answer = wxID_NO;
10752 if (mergepoints) {
10753 wxString msg;
10754 msg << _(
10755 "There are existing waypoints at the same location as some of the ones "
10756 "you are pasting. Would you like to just merge the pasted data into "
10757 "them?\n\n");
10758 msg << _("Answering 'No' will create all new waypoints for this route.");
10759 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10760 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10761
10762 if (answer == wxID_CANCEL) {
10763 return;
10764 }
10765 }
10766
10767 // If all waypoints exist since before, and a route with the same name, we
10768 // don't create a new route.
10769 if (mergepoints && answer == wxID_YES &&
10770 existingWaypointCounter == pasted->GetnPoints()) {
10771 for (Route *proute : *pRouteList) {
10772 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10773 createNewRoute = false;
10774 break;
10775 }
10776 }
10777 }
10778
10779 Route *newRoute = 0;
10780 RoutePoint *newPoint = 0;
10781
10782 if (createNewRoute) {
10783 newRoute = new Route();
10784 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10785 }
10786
10787 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10788 curPoint = pasted->GetPoint(i);
10789 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10790 curPoint->m_bPtIsSelected = false;
10791 newPoint = pWayPointMan->GetNearbyWaypoint(
10792 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10793 newPoint->SetName(curPoint->GetName());
10794 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10795
10796 if (createNewRoute) newRoute->AddPoint(newPoint);
10797 } else {
10798 curPoint->m_bPtIsSelected = false;
10799
10800 newPoint = new RoutePoint(curPoint);
10801 newPoint->m_bIsolatedMark = false;
10802 newPoint->SetIconName("circle");
10803 newPoint->m_bIsVisible = true;
10804 newPoint->m_bShowName = false;
10805 newPoint->SetShared(false);
10806
10807 newRoute->AddPoint(newPoint);
10808 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10809 newPoint);
10810 // pConfig->AddNewWayPoint(newPoint, -1);
10811 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10812 pWayPointMan->AddRoutePoint(newPoint);
10813 }
10814 if (i > 1 && createNewRoute)
10815 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10816 curPoint->m_lat, curPoint->m_lon,
10817 prevPoint, newPoint, newRoute);
10818 prevPoint = newPoint;
10819 }
10820
10821 if (createNewRoute) {
10822 pRouteList->push_back(newRoute);
10823 // pConfig->AddNewRoute(newRoute); // use auto next num
10824 NavObj_dB::GetInstance().InsertRoute(newRoute);
10825
10826 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10827 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10828 }
10829
10830 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10831 pRouteManagerDialog->UpdateRouteListCtrl();
10832 pRouteManagerDialog->UpdateWptListCtrl();
10833 }
10834 gFrame->InvalidateAllGL();
10835 gFrame->RefreshAllCanvas(false);
10836 }
10837 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10838 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10839}
10840
10841void pupHandler_PasteTrack() {
10842 Kml kml;
10843
10844 int pasteBuffer = kml.ParsePasteBuffer();
10845 Track *pasted = kml.GetParsedTrack();
10846 if (!pasted) return;
10847
10848 TrackPoint *curPoint;
10849
10850 Track *newTrack = new Track();
10851 TrackPoint *newPoint;
10852 TrackPoint *prevPoint = NULL;
10853
10854 newTrack->SetName(pasted->GetName());
10855
10856 for (int i = 0; i < pasted->GetnPoints(); i++) {
10857 curPoint = pasted->GetPoint(i);
10858
10859 newPoint = new TrackPoint(curPoint);
10860
10861 wxDateTime now = wxDateTime::Now();
10862 newPoint->SetCreateTime(curPoint->GetCreateTime());
10863
10864 newTrack->AddPoint(newPoint);
10865
10866 if (prevPoint)
10867 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10868 newPoint->m_lat, newPoint->m_lon,
10869 prevPoint, newPoint, newTrack);
10870
10871 prevPoint = newPoint;
10872 }
10873
10874 g_TrackList.push_back(newTrack);
10875 // pConfig->AddNewTrack(newTrack);
10876 NavObj_dB::GetInstance().InsertTrack(newTrack);
10877
10878 gFrame->InvalidateAllGL();
10879 gFrame->RefreshAllCanvas(false);
10880}
10881
10882bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
10883 wxJSONValue v;
10884 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
10885 v["CursorPosition_x"] = x;
10886 v["CursorPosition_y"] = y;
10887 // Send a limited set of selection types depending on what is
10888 // found under the mouse point.
10889 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
10890 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
10891 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
10892
10893 wxJSONWriter w;
10894 wxString out;
10895 w.Write(v, out);
10896 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
10897
10898 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
10899
10900#if 0
10901#define SELTYPE_UNKNOWN 0x0001
10902#define SELTYPE_ROUTEPOINT 0x0002
10903#define SELTYPE_ROUTESEGMENT 0x0004
10904#define SELTYPE_TIDEPOINT 0x0008
10905#define SELTYPE_CURRENTPOINT 0x0010
10906#define SELTYPE_ROUTECREATE 0x0020
10907#define SELTYPE_AISTARGET 0x0040
10908#define SELTYPE_MARKPOINT 0x0080
10909#define SELTYPE_TRACKSEGMENT 0x0100
10910#define SELTYPE_DRAGHANDLE 0x0200
10911#endif
10912
10913 if (g_bhide_context_menus) return true;
10914 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
10915 m_pFoundRoutePoint, m_FoundAIS_MMSI,
10916 m_pIDXCandidate, m_nmea_log);
10917
10918 Connect(
10919 wxEVT_COMMAND_MENU_SELECTED,
10920 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10921
10922#ifdef __WXGTK__
10923 // Funny requirement here for gtk, to clear the menu trigger event
10924 // TODO
10925 // Causes a slight "flasH" of the menu,
10926 if (m_inLongPress) {
10927 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10928 m_inLongPress = false;
10929 }
10930#endif
10931
10932 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
10933
10934 Disconnect(
10935 wxEVT_COMMAND_MENU_SELECTED,
10936 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
10937
10938 delete m_canvasMenu;
10939 m_canvasMenu = NULL;
10940
10941#ifdef __WXQT__
10942 // gFrame->SurfaceToolbar();
10943 // g_MainToolbar->Raise();
10944#endif
10945
10946 return true;
10947}
10948
10949void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
10950 // Pass menu events from the canvas to the menu handler
10951 // This is necessarily in ChartCanvas since that is the menu's parent.
10952 if (m_canvasMenu) {
10953 m_canvasMenu->PopupMenuHandler(event);
10954 }
10955 return;
10956}
10957
10958void ChartCanvas::StartRoute() {
10959 // Do not allow more than one canvas to create a route at one time.
10960 if (g_brouteCreating) return;
10961
10962 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
10963
10964 g_brouteCreating = true;
10965 m_routeState = 1;
10966 m_bDrawingRoute = false;
10967 SetCursor(*pCursorPencil);
10968 // SetCanvasToolbarItemState(ID_ROUTE, true);
10969 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
10970
10971 HideGlobalToolbar();
10972
10973#ifdef __ANDROID__
10974 androidSetRouteAnnunciator(true);
10975#endif
10976}
10977
10978wxString ChartCanvas::FinishRoute() {
10979 m_routeState = 0;
10980 m_prev_pMousePoint = NULL;
10981 m_bDrawingRoute = false;
10982 wxString rv = "";
10983 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
10984
10985 // SetCanvasToolbarItemState(ID_ROUTE, false);
10986 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
10987#ifdef __ANDROID__
10988 androidSetRouteAnnunciator(false);
10989#endif
10990
10991 SetCursor(*pCursorArrow);
10992
10993 if (m_pMouseRoute) {
10994 if (m_bAppendingRoute) {
10995 // pConfig->UpdateRoute(m_pMouseRoute);
10996 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
10997 } else {
10998 if (m_pMouseRoute->GetnPoints() > 1) {
10999 // pConfig->AddNewRoute(m_pMouseRoute);
11000 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11001 } else {
11002 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11003 m_pMouseRoute = NULL;
11004 }
11005 }
11006 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11007
11008 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11009 (pRoutePropDialog->IsShown())) {
11010 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11011 }
11012
11013 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11014 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11015 pRouteManagerDialog->UpdateRouteListCtrl();
11016 }
11017 }
11018 m_bAppendingRoute = false;
11019 m_pMouseRoute = NULL;
11020
11021 m_pSelectedRoute = NULL;
11022
11023 undo->InvalidateUndo();
11024 gFrame->RefreshAllCanvas(true);
11025
11026 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11027
11028 ShowGlobalToolbar();
11029
11030 g_brouteCreating = false;
11031
11032 return rv;
11033}
11034
11035void ChartCanvas::HideGlobalToolbar() {
11036 if (m_canvasIndex == 0) {
11037 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11038 }
11039}
11040
11041void ChartCanvas::ShowGlobalToolbar() {
11042 if (m_canvasIndex == 0) {
11043 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11044 }
11045}
11046
11047void ChartCanvas::ShowAISTargetList() {
11048 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11049 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11050 }
11051
11052 g_pAISTargetList->UpdateAISTargetList();
11053}
11054
11055void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11056 if (!m_bShowOutlines) return;
11057
11058 if (!ChartData) return;
11059
11060 int nEntry = ChartData->GetChartTableEntries();
11061
11062 for (int i = 0; i < nEntry; i++) {
11063 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11064
11065 // Check to see if the candidate chart is in the currently active group
11066 bool b_group_draw = false;
11067 if (m_groupIndex > 0) {
11068 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11069 int index = pt->GetGroupArray()[ig];
11070 if (m_groupIndex == index) {
11071 b_group_draw = true;
11072 break;
11073 }
11074 }
11075 } else
11076 b_group_draw = true;
11077
11078 if (b_group_draw) RenderChartOutline(dc, i, vp);
11079 }
11080
11081 // On CM93 Composite Charts, draw the outlines of the next smaller
11082 // scale cell
11083 cm93compchart *pcm93 = NULL;
11084 if (VPoint.b_quilt) {
11085 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11086 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11087 pcm93 = (cm93compchart *)pch;
11088 break;
11089 }
11090 } else if (m_singleChart &&
11091 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11092 pcm93 = (cm93compchart *)m_singleChart;
11093
11094 if (pcm93) {
11095 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11096 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11097
11098 if (zoom_factor > 8.0) {
11099 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11100 dc.SetPen(mPen);
11101 } else {
11102 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11103 dc.SetPen(mPen);
11104 }
11105
11106 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11107 }
11108}
11109
11110void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11111#ifdef ocpnUSE_GL
11112 if (g_bopengl && m_glcc) {
11113 /* opengl version specially optimized */
11114 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11115 return;
11116 }
11117#endif
11118
11119 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11120 if (!ChartData->IsChartAvailable(dbIndex)) return;
11121 }
11122
11123 float plylat, plylon;
11124 float plylat1, plylon1;
11125
11126 int pixx, pixy, pixx1, pixy1;
11127
11128 LLBBox box;
11129 ChartData->GetDBBoundingBox(dbIndex, box);
11130
11131 // Don't draw an outline in the case where the chart covers the entire world
11132 // */
11133 if (box.GetLonRange() == 360) return;
11134
11135 double lon_bias = 0;
11136 // chart is outside of viewport lat/lon bounding box
11137 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11138
11139 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11140
11141 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11142 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11143
11144 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11145 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11146
11147 else
11148 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11149
11150 // Are there any aux ply entries?
11151 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11152 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11153 {
11154 wxPoint r, r1;
11155
11156 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11157 plylon += lon_bias;
11158
11159 GetCanvasPointPix(plylat, plylon, &r);
11160 pixx = r.x;
11161 pixy = r.y;
11162
11163 for (int i = 0; i < nPly - 1; i++) {
11164 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11165 plylon1 += lon_bias;
11166
11167 GetCanvasPointPix(plylat1, plylon1, &r1);
11168 pixx1 = r1.x;
11169 pixy1 = r1.y;
11170
11171 int pixxs1 = pixx1;
11172 int pixys1 = pixy1;
11173
11174 bool b_skip = false;
11175
11176 if (vp.chart_scale > 5e7) {
11177 // calculate projected distance between these two points in meters
11178 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11179 pow((double)(pixy1 - pixy), 2)) /
11180 vp.view_scale_ppm;
11181
11182 if (dist > 0.0) {
11183 // calculate GC distance between these two points in meters
11184 double distgc =
11185 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11186
11187 // If the distances are nonsense, it means that the scale is very
11188 // small and the segment wrapped the world So skip it....
11189 // TODO improve this to draw two segments
11190 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11191 b_skip = true;
11192 } else
11193 b_skip = true;
11194 }
11195
11196 ClipResult res = cohen_sutherland_line_clip_i(
11197 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11198 if (res != Invisible && !b_skip)
11199 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11200
11201 plylat = plylat1;
11202 plylon = plylon1;
11203 pixx = pixxs1;
11204 pixy = pixys1;
11205 }
11206
11207 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11208 plylon1 += lon_bias;
11209
11210 GetCanvasPointPix(plylat1, plylon1, &r1);
11211 pixx1 = r1.x;
11212 pixy1 = r1.y;
11213
11214 ClipResult res = cohen_sutherland_line_clip_i(
11215 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11216 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11217 }
11218
11219 else // Use Aux PlyPoints
11220 {
11221 wxPoint r, r1;
11222
11223 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11224 for (int j = 0; j < nAuxPlyEntries; j++) {
11225 int nAuxPly =
11226 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11227 GetCanvasPointPix(plylat, plylon, &r);
11228 pixx = r.x;
11229 pixy = r.y;
11230
11231 for (int i = 0; i < nAuxPly - 1; i++) {
11232 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11233
11234 GetCanvasPointPix(plylat1, plylon1, &r1);
11235 pixx1 = r1.x;
11236 pixy1 = r1.y;
11237
11238 int pixxs1 = pixx1;
11239 int pixys1 = pixy1;
11240
11241 bool b_skip = false;
11242
11243 if (vp.chart_scale > 5e7) {
11244 // calculate projected distance between these two points in meters
11245 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11246 ((pixy1 - pixy) * (pixy1 - pixy))) /
11247 vp.view_scale_ppm;
11248 if (dist > 0.0) {
11249 // calculate GC distance between these two points in meters
11250 double distgc =
11251 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11252
11253 // If the distances are nonsense, it means that the scale is very
11254 // small and the segment wrapped the world So skip it....
11255 // TODO improve this to draw two segments
11256 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11257 b_skip = true;
11258 } else
11259 b_skip = true;
11260 }
11261
11262 ClipResult res = cohen_sutherland_line_clip_i(
11263 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11264 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11265
11266 plylat = plylat1;
11267 plylon = plylon1;
11268 pixx = pixxs1;
11269 pixy = pixys1;
11270 }
11271
11272 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11273 GetCanvasPointPix(plylat1, plylon1, &r1);
11274 pixx1 = r1.x;
11275 pixy1 = r1.y;
11276
11277 ClipResult res = cohen_sutherland_line_clip_i(
11278 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11279 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11280 }
11281 }
11282}
11283
11284static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point, const wxString &first,
11285 const wxString &second) {
11286 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11287
11288 int pointsize = dFont->GetPointSize();
11289 pointsize /= OCPN_GetWinDIPScaleFactor();
11290
11291 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11292 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11293 false, dFont->GetFaceName());
11294
11295 dc.SetFont(*psRLI_font);
11296
11297 int w1, h1;
11298 int w2 = 0;
11299 int h2 = 0;
11300 int h, w;
11301
11302 int xp, yp;
11303 int hilite_offset = 3;
11304#ifdef __WXMAC__
11305 wxScreenDC sdc;
11306 sdc.GetTextExtent(first, &w1, &h1, NULL, NULL, psRLI_font);
11307 if (second.Len()) sdc.GetTextExtent(second, &w2, &h2, NULL, NULL, psRLI_font);
11308#else
11309 dc.GetTextExtent(first, &w1, &h1);
11310 if (second.Len()) dc.GetTextExtent(second, &w2, &h2);
11311#endif
11312
11313 h1 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11314 h2 *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11315
11316 w = wxMax(w1, w2) + (h1 / 2); // Add a little right pad
11317 w *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11318
11319 h = h1 + h2;
11320
11321 xp = ref_point.x - w;
11322 yp = ref_point.y;
11323 yp += hilite_offset;
11324
11325 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11326
11327 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11328 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11329
11330 dc.DrawText(first, xp, yp);
11331 if (second.Len()) dc.DrawText(second, xp, yp + h1);
11332}
11333
11334void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11335 if (!g_bAllowShipToActive) return;
11336
11337 Route *rt = g_pRouteMan->GetpActiveRoute();
11338 if (!rt) return;
11339
11340 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11341 wxPoint2DDouble pa, pb;
11343 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11344
11345 // set pen
11346 int width =
11347 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11348 if (rt->m_width != wxPENSTYLE_INVALID)
11349 width = rt->m_width; // set route pen style if any
11350 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11351 g_shipToActiveStyle, 5)]; // get setting pen style
11352 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11353 wxColour color =
11354 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11355 : // set setting route pen color
11356 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11357 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11358
11359 dc.SetPen(*mypen);
11360 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11361
11362 if (!Use_Opengl)
11363 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11364 (int)pb.m_y, GetVP(), true);
11365
11366#ifdef ocpnUSE_GL
11367 else {
11368#ifdef USE_ANDROID_GLES2
11369 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11370#else
11371 if (style != wxPENSTYLE_SOLID) {
11372 if (glChartCanvas::dash_map.find(style) !=
11373 glChartCanvas::dash_map.end()) {
11374 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11375 dc.SetPen(*mypen);
11376 }
11377 }
11378 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11379#endif
11380
11381 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11382 (int)pb.m_x, (int)pb.m_y, GetVP());
11383 }
11384#endif
11385 }
11386}
11387
11388void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11389 Route *route = 0;
11390 if (m_routeState >= 2) route = m_pMouseRoute;
11391 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11392 route = m_pMeasureRoute;
11393
11394 if (!route) return;
11395
11396 // Validate route pointer
11397 if (!g_pRouteMan->IsRouteValid(route)) return;
11398
11399 double render_lat = m_cursor_lat;
11400 double render_lon = m_cursor_lon;
11401
11402 int np = route->GetnPoints();
11403 if (np) {
11404 if (g_btouch && (np > 1)) np--;
11405 RoutePoint rp = route->GetPoint(np);
11406 render_lat = rp.m_lat;
11407 render_lon = rp.m_lon;
11408 }
11409
11410 double rhumbBearing, rhumbDist;
11411 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11412 &rhumbBearing, &rhumbDist);
11413 double brg = rhumbBearing;
11414 double dist = rhumbDist;
11415
11416 // Skip GreatCircle rubberbanding on touch devices.
11417 if (!g_btouch) {
11418 double gcBearing, gcBearing2, gcDist;
11419 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11420 m_cursor_lat, &gcDist, &gcBearing,
11421 &gcBearing2);
11422 double gcDistm = gcDist / 1852.0;
11423
11424 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11425 rhumbBearing = 90.;
11426
11427 wxPoint destPoint, lastPoint;
11428
11429 route->m_NextLegGreatCircle = false;
11430 int milesDiff = rhumbDist - gcDistm;
11431 if (milesDiff > 1) {
11432 brg = gcBearing;
11433 dist = gcDistm;
11434 route->m_NextLegGreatCircle = true;
11435 }
11436
11437 // FIXME (MacOS, the first segment is rendered wrong)
11438 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11439 &lastPoint);
11440
11441 if (route->m_NextLegGreatCircle) {
11442 for (int i = 1; i <= milesDiff; i++) {
11443 double p = (double)i * (1.0 / (double)milesDiff);
11444 double pLat, pLon;
11445 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11446 &pLon, &pLat, &gcBearing2);
11447 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11448 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11449 false);
11450 lastPoint = destPoint;
11451 }
11452 } else {
11453 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11454 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11455 false);
11456 if (m_bMeasure_DistCircle) {
11457 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11458 powf((float)(r_rband.y - lastPoint.y), 2));
11459
11460 dc.SetPen(*g_pRouteMan->GetRoutePen());
11461 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11462 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11463 }
11464 }
11465 }
11466 }
11467
11468 wxString routeInfo;
11469 double varBrg = 0;
11470 if (g_bShowTrue)
11471 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11472 0x00B0);
11473
11474 if (g_bShowMag) {
11475 double latAverage = (m_cursor_lat + render_lat) / 2;
11476 double lonAverage = (m_cursor_lon + render_lon) / 2;
11477 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11478
11479 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11480 (int)varBrg, 0x00B0);
11481 }
11482 routeInfo << " " << FormatDistanceAdaptive(dist);
11483
11484 // To make it easier to use a route as a bearing on a charted object add for
11485 // the first leg also the reverse bearing.
11486 if (np == 1) {
11487 routeInfo << "\nReverse: ";
11488 if (g_bShowTrue)
11489 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11490 (int)(brg + 180.) % 360, 0x00B0);
11491 if (g_bShowMag)
11492 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11493 (int)(varBrg + 180.) % 360, 0x00B0);
11494 }
11495
11496 wxString s0;
11497 if (!route->m_bIsInLayer)
11498 s0.Append(_("Route") + ": ");
11499 else
11500 s0.Append(_("Layer Route: "));
11501
11502 double disp_length = route->m_route_length;
11503 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11504 s0 += FormatDistanceAdaptive(disp_length);
11505
11506 RouteLegInfo(dc, r_rband, routeInfo, s0);
11507
11508 m_brepaint_piano = true;
11509}
11510
11511void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11512 if (!m_bShowVisibleSectors) return;
11513
11514 if (g_bDeferredInitDone) {
11515 // need to re-evaluate sectors?
11516 double rhumbBearing, rhumbDist;
11517 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11518 &rhumbBearing, &rhumbDist);
11519
11520 if (rhumbDist > 0.05) // miles
11521 {
11522 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11523 m_sectorlegsVisible);
11524 m_sector_glat = gLat;
11525 m_sector_glon = gLon;
11526 }
11527 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11528 }
11529}
11530
11531void ChartCanvas::WarpPointerDeferred(int x, int y) {
11532 warp_x = x;
11533 warp_y = y;
11534 warp_flag = true;
11535}
11536
11537int s_msg;
11538
11539void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11540 if (!ps52plib) return;
11541
11542 if (VPoint.b_quilt) { // quilted
11543 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11544
11545 if (m_pQuilt->IsQuiltVector()) {
11546 if (ps52plib->GetStateHash() != m_s52StateHash) {
11547 UpdateS52State();
11548 m_s52StateHash = ps52plib->GetStateHash();
11549 }
11550 }
11551 } else {
11552 if (ps52plib->GetStateHash() != m_s52StateHash) {
11553 UpdateS52State();
11554 m_s52StateHash = ps52plib->GetStateHash();
11555 }
11556 }
11557
11558 // Plugin charts
11559 bool bSendPlibState = true;
11560 if (VPoint.b_quilt) { // quilted
11561 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11562 }
11563
11564 if (bSendPlibState) {
11565 wxJSONValue v;
11566 v["OpenCPN Version Major"] = VERSION_MAJOR;
11567 v["OpenCPN Version Minor"] = VERSION_MINOR;
11568 v["OpenCPN Version Patch"] = VERSION_PATCH;
11569 v["OpenCPN Version Date"] = VERSION_DATE;
11570 v["OpenCPN Version Full"] = VERSION_FULL;
11571
11572 // S52PLIB state
11573 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11574 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11575 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11576 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11577 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11578 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11579 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11580
11581 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11582
11583 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11584 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11585
11586 // Global S52 options
11587
11588 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11589 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11590 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11591 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11592 ps52plib->m_bShowS57ImportantTextOnly;
11593 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11594 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11595 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11596 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11597 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11598
11599 // Some global GUI parameters, for completeness
11600 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11601 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11602 v["OpenCPN Scale Factor Exp"] =
11603 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11604 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11605
11606 wxJSONWriter w;
11607 wxString out;
11608 w.Write(v, out);
11609
11610 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11611 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11612 g_lastS52PLIBPluginMessage = out;
11613 }
11614 }
11615}
11616int spaint;
11617int s_in_update;
11618void ChartCanvas::OnPaint(wxPaintEvent &event) {
11619 wxPaintDC dc(this);
11620
11621 // GetToolbar()->Show( m_bToolbarEnable );
11622
11623 // Paint updates may have been externally disabled (temporarily, to avoid
11624 // Yield() recursion performance loss) It is important that the wxPaintDC is
11625 // built, even if we elect to not process this paint message. Otherwise, the
11626 // paint message may not be removed from the message queue, esp on Windows.
11627 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11628
11629 if (!m_b_paint_enable) {
11630 return;
11631 }
11632
11633 // If necessary, reconfigure the S52 PLIB
11634 UpdateCanvasS52PLIBConfig();
11635
11636#ifdef ocpnUSE_GL
11637 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11638
11639 if (m_glcc && g_bopengl) {
11640 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11641 s_in_update++;
11642 m_glcc->Update();
11643 s_in_update--;
11644 }
11645
11646 return;
11647 }
11648#endif
11649
11650 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11651
11652 wxRegion ru = GetUpdateRegion();
11653
11654 int rx, ry, rwidth, rheight;
11655 ru.GetBox(rx, ry, rwidth, rheight);
11656
11657#ifdef ocpnUSE_DIBSECTION
11658 ocpnMemDC temp_dc;
11659#else
11660 wxMemoryDC temp_dc;
11661#endif
11662
11663 long height = GetVP().pix_height;
11664
11665#ifdef __WXMAC__
11666 // On OS X we have to explicitly extend the region for the piano area
11667 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11668 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11669 height += m_Piano->GetHeight();
11670#endif // __WXMAC__
11671 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11672
11673 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11674 if (pthumbwin) {
11675 int thumbx, thumby, thumbsx, thumbsy;
11676 pthumbwin->GetPosition(&thumbx, &thumby);
11677 pthumbwin->GetSize(&thumbsx, &thumbsy);
11678 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11679
11680 if (pthumbwin->IsShown()) {
11681 rgn_chart.Subtract(rgn_thumbwin);
11682 ru.Subtract(rgn_thumbwin);
11683 }
11684 }
11685
11686 // subtract the chart bar if it isn't transparent, and determine if we need to
11687 // paint it
11688 wxRegion rgn_blit = ru;
11689 if (g_bShowChartBar) {
11690 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11691 GetClientSize().x, m_Piano->GetHeight());
11692
11693 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11694 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11695 if (style->chartStatusWindowTransparent)
11696 m_brepaint_piano = true;
11697 else
11698 ru.Subtract(chart_bar_rect);
11699 }
11700 }
11701
11702 if (m_Compass && m_Compass->IsShown()) {
11703 wxRect compassRect = m_Compass->GetRect();
11704 if (ru.Contains(compassRect) != wxOutRegion) {
11705 ru.Subtract(compassRect);
11706 }
11707 }
11708
11709 wxRect noteRect = m_notification_button->GetRect();
11710 if (ru.Contains(noteRect) != wxOutRegion) {
11711 ru.Subtract(noteRect);
11712 }
11713
11714 // Is this viewpoint the same as the previously painted one?
11715 bool b_newview = true;
11716
11717 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11718 (m_cache_vp.rotation == VPoint.rotation) &&
11719 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11720 m_cache_vp.IsValid()) {
11721 b_newview = false;
11722 }
11723
11724 // If the ViewPort is skewed or rotated, we may be able to use the cached
11725 // rotated bitmap.
11726 bool b_rcache_ok = false;
11727 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11728 b_rcache_ok = !b_newview;
11729
11730 // Make a special VP
11731 if (VPoint.b_MercatorProjectionOverride)
11732 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11733 ViewPort svp = VPoint;
11734
11735 svp.pix_width = svp.rv_rect.width;
11736 svp.pix_height = svp.rv_rect.height;
11737
11738 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11739 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11740 // VPoint.rv_rect.height);
11741
11742 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11743
11744 // If we are going to use the cached rotated image, there is no need to fetch
11745 // any chart data and this will do it...
11746 if (b_rcache_ok) chart_get_region.Clear();
11747
11748 // Blit pan acceleration
11749 if (VPoint.b_quilt) // quilted
11750 {
11751 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11752
11753 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11754
11755 bool busy = false;
11756 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11757 m_cache_vp.rotation != VPoint.rotation)) {
11758 AbstractPlatform::ShowBusySpinner();
11759 busy = true;
11760 }
11761
11762 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11763 (m_working_bm.GetHeight() != svp.pix_height))
11764 m_working_bm.Create(svp.pix_width, svp.pix_height,
11765 -1); // make sure the target is big enoug
11766
11767 if (fabs(VPoint.rotation) < 0.01) {
11768 bool b_save = true;
11769
11770 if (g_SencThreadManager) {
11771 if (g_SencThreadManager->GetJobCount()) {
11772 b_save = false;
11773 m_cache_vp.Invalidate();
11774 }
11775 }
11776
11777 // If the saved wxBitmap from last OnPaint is useable
11778 // calculate the blit parameters
11779
11780 // We can only do screen blit painting if subsequent ViewPorts differ by
11781 // whole pixels So, in small scale bFollow mode, force the full screen
11782 // render. This seems a hack....There may be better logic here.....
11783
11784 // if(m_bFollow)
11785 // b_save = false;
11786
11787 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11788 if (b_newview) {
11789 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11790 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11791
11792 int dy = c_new.y - c_old.y;
11793 int dx = c_new.x - c_old.x;
11794
11795 // printf("In OnPaint Trying Blit dx: %d
11796 // dy:%d\n\n", dx, dy);
11797
11798 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11799 if (dx || dy) {
11800 // Blit the reuseable portion of the cached wxBitmap to a working
11801 // bitmap
11802 temp_dc.SelectObject(m_working_bm);
11803
11804 wxMemoryDC cache_dc;
11805 cache_dc.SelectObject(m_cached_chart_bm);
11806
11807 if (dy > 0) {
11808 if (dx > 0) {
11809 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11810 VPoint.pix_height - dy, &cache_dc, dx, dy);
11811 } else {
11812 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11813 VPoint.pix_height - dy, &cache_dc, 0, dy);
11814 }
11815
11816 } else {
11817 if (dx > 0) {
11818 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11819 VPoint.pix_height + dy, &cache_dc, dx, 0);
11820 } else {
11821 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11822 VPoint.pix_height + dy, &cache_dc, 0, 0);
11823 }
11824 }
11825
11826 OCPNRegion update_region;
11827 if (dy) {
11828 if (dy > 0)
11829 update_region.Union(
11830 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11831 else
11832 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11833 }
11834
11835 if (dx) {
11836 if (dx > 0)
11837 update_region.Union(
11838 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11839 else
11840 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11841 }
11842
11843 // Render the new region
11844 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11845 update_region);
11846 cache_dc.SelectObject(wxNullBitmap);
11847 } else {
11848 // No sensible (dx, dy) change in the view, so use the cached
11849 // member bitmap
11850 temp_dc.SelectObject(m_cached_chart_bm);
11851 b_save = false;
11852 }
11853 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11854
11855 } else // not blitable
11856 {
11857 temp_dc.SelectObject(m_working_bm);
11858 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11859 chart_get_region);
11860 }
11861 } else {
11862 // No change in the view, so use the cached member bitmap2
11863 temp_dc.SelectObject(m_cached_chart_bm);
11864 b_save = false;
11865 }
11866 } else // cached bitmap is not yet valid
11867 {
11868 temp_dc.SelectObject(m_working_bm);
11869 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11870 chart_get_region);
11871 }
11872
11873 // Save the fully rendered quilt image as a wxBitmap member of this class
11874 if (b_save) {
11875 // if((m_cached_chart_bm.GetWidth() !=
11876 // svp.pix_width) ||
11877 // (m_cached_chart_bm.GetHeight() !=
11878 // svp.pix_height))
11879 // m_cached_chart_bm.Create(svp.pix_width,
11880 // svp.pix_height, -1); // target wxBitmap
11881 // is big enough
11882 wxMemoryDC scratch_dc_0;
11883 scratch_dc_0.SelectObject(m_cached_chart_bm);
11884 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11885
11886 scratch_dc_0.SelectObject(wxNullBitmap);
11887
11888 m_bm_cache_vp =
11889 VPoint; // save the ViewPort associated with the cached wxBitmap
11890 }
11891 }
11892
11893 else // quilted, rotated
11894 {
11895 temp_dc.SelectObject(m_working_bm);
11896 OCPNRegion chart_get_all_region(
11897 wxRect(0, 0, svp.pix_width, svp.pix_height));
11898 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11899 chart_get_all_region);
11900 }
11901
11902 AbstractPlatform::HideBusySpinner();
11903
11904 }
11905
11906 else // not quilted
11907 {
11908 if (!m_singleChart) {
11909 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
11910 dc.Clear();
11911 return;
11912 }
11913
11914 if (!chart_get_region.IsEmpty()) {
11915 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
11916 }
11917 }
11918
11919 if (temp_dc.IsOk()) {
11920 // Arrange to render the World Chart vector data behind the rendered
11921 // current chart so that uncovered canvas areas show at least the world
11922 // chart.
11923 OCPNRegion chartValidRegion;
11924 if (!VPoint.b_quilt) {
11925 // Make a region covering the current chart on the canvas
11926
11927 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
11928 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
11929 else {
11930 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
11931 // require that the viewport passed here have pix_width and pix_height
11932 // set to the actual display, not the virtual (rv_rect) sizes
11933 // (the vector calculations require the virtual sizes in svp)
11934
11935 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
11936 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
11937 }
11938 } else
11939 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
11940
11941 temp_dc.DestroyClippingRegion();
11942
11943 // Copy current chart region
11944 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
11945
11946 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
11947
11948 if (!backgroundRegion.IsEmpty()) {
11949 // Draw the Background Chart only in the areas NOT covered by the
11950 // current chart view
11951
11952 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
11953 clipping regions with more than 1 rectangle so... */
11954 wxColour water = pWorldBackgroundChart->water;
11955 if (water.IsOk()) {
11956 temp_dc.SetPen(*wxTRANSPARENT_PEN);
11957 temp_dc.SetBrush(wxBrush(water));
11958 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
11959 while (upd.HaveRects()) {
11960 wxRect rect = upd.GetRect();
11961 temp_dc.DrawRectangle(rect);
11962 upd.NextRect();
11963 }
11964 }
11965 // Associate with temp_dc
11966 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
11967 temp_dc.SetDeviceClippingRegion(*clip_region);
11968 delete clip_region;
11969
11970 ocpnDC bgdc(temp_dc);
11971 double r = VPoint.rotation;
11972 SetVPRotation(VPoint.skew);
11973
11974 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
11975 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
11976
11977 SetVPRotation(r);
11978 }
11979 } // temp_dc.IsOk();
11980
11981 wxMemoryDC *pChartDC = &temp_dc;
11982 wxMemoryDC rotd_dc;
11983
11984 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
11985 // Can we use the current rotated image cache?
11986 if (!b_rcache_ok) {
11987#ifdef __WXMSW__
11988 wxMemoryDC tbase_dc;
11989 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
11990 tbase_dc.SelectObject(bm_base);
11991 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
11992 tbase_dc.SelectObject(wxNullBitmap);
11993#else
11994 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
11995#endif
11996
11997 wxImage base_image;
11998 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
11999
12000 // Use a local static image rotator to improve wxWidgets code profile
12001 // Especially, on GTK the wxRound and wxRealPoint functions are very
12002 // expensive.....
12003
12004 double angle = GetVP().skew - GetVP().rotation;
12005 wxImage ri;
12006 bool b_rot_ok = false;
12007 if (base_image.IsOk()) {
12008 ViewPort rot_vp = GetVP();
12009
12010 m_b_rot_hidef = false;
12011
12012 ri = Image_Rotate(
12013 base_image, angle,
12014 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12015 m_b_rot_hidef, &m_roffset);
12016
12017 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12018 (rot_vp.rotation == VPoint.rotation) &&
12019 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12020 rot_vp.IsValid() && (ri.IsOk())) {
12021 b_rot_ok = true;
12022 }
12023 }
12024
12025 if (b_rot_ok) {
12026 delete m_prot_bm;
12027 m_prot_bm = new wxBitmap(ri);
12028 }
12029
12030 m_roffset.x += VPoint.rv_rect.x;
12031 m_roffset.y += VPoint.rv_rect.y;
12032 }
12033
12034 if (m_prot_bm && m_prot_bm->IsOk()) {
12035 rotd_dc.SelectObject(*m_prot_bm);
12036 pChartDC = &rotd_dc;
12037 } else {
12038 pChartDC = &temp_dc;
12039 m_roffset = wxPoint(0, 0);
12040 }
12041 } else { // unrotated
12042 pChartDC = &temp_dc;
12043 m_roffset = wxPoint(0, 0);
12044 }
12045
12046 wxPoint offset = m_roffset;
12047
12048 // Save the PixelCache viewpoint for next time
12049 m_cache_vp = VPoint;
12050
12051 // Set up a scratch DC for overlay objects
12052 wxMemoryDC mscratch_dc;
12053 mscratch_dc.SelectObject(*pscratch_bm);
12054
12055 mscratch_dc.ResetBoundingBox();
12056 mscratch_dc.DestroyClippingRegion();
12057 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12058
12059 // Blit the externally invalidated areas of the chart onto the scratch dc
12060 wxRegionIterator upd(rgn_blit); // get the update rect list
12061 while (upd) {
12062 wxRect rect = upd.GetRect();
12063
12064 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12065 rect.x - offset.x, rect.y - offset.y);
12066 upd++;
12067 }
12068
12069 // If multi-canvas, indicate which canvas has keyboard focus
12070 // by drawing a simple blue bar at the top.
12071 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12072 if (this == wxWindow::FindFocus()) {
12073 g_focusCanvas = this;
12074
12075 wxColour colour = GetGlobalColor("BLUE4");
12076 mscratch_dc.SetPen(wxPen(colour));
12077 mscratch_dc.SetBrush(wxBrush(colour));
12078
12079 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12080 mscratch_dc.DrawRectangle(activeRect);
12081 }
12082 }
12083
12084 // Any MBtiles?
12085 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12086 unsigned int im = stackIndexArray.size();
12087 if (VPoint.b_quilt && im > 0) {
12088 std::vector<int> tiles_to_show;
12089 for (unsigned int is = 0; is < im; is++) {
12090 const ChartTableEntry &cte =
12091 ChartData->GetChartTableEntry(stackIndexArray[is]);
12092 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12093 continue;
12094 }
12095 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12096 tiles_to_show.push_back(stackIndexArray[is]);
12097 }
12098 }
12099
12100 if (tiles_to_show.size())
12101 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12102 }
12103
12104 // May get an unexpected OnPaint call while switching display modes
12105 // Guard for that.
12106 if (!g_bopengl) {
12107 ocpnDC scratch_dc(mscratch_dc);
12108 RenderAlertMessage(mscratch_dc, GetVP());
12109 }
12110
12111#if 0
12112 // quiting?
12113 if (g_bquiting) {
12114#ifdef ocpnUSE_DIBSECTION
12115 ocpnMemDC q_dc;
12116#else
12117 wxMemoryDC q_dc;
12118#endif
12119 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12120 q_dc.SelectObject(qbm);
12121
12122 // Get a copy of the screen
12123 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12124
12125 // Draw a rectangle over the screen with a stipple brush
12126 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12127 q_dc.SetBrush(qbr);
12128 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12129
12130 // Blit back into source
12131 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12132 wxCOPY);
12133
12134 q_dc.SelectObject(wxNullBitmap);
12135 }
12136#endif
12137
12138#if 0
12139 // It is possible that this two-step method may be reuired for some platforms.
12140 // So, retain in the code base to aid recovery if necessary
12141
12142 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12143 if( VPoint.b_quilt ) {
12144 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12145 ChartBase *chart = m_pQuilt->GetRefChart();
12146 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12147
12148 // Clear the text Global declutter list
12149 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12150 if(ChPI)
12151 ChPI->ClearPLIBTextList();
12152 else{
12153 if(ps52plib)
12154 ps52plib->ClearTextList();
12155 }
12156
12157 wxMemoryDC t_dc;
12158 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12159
12160 wxColor maskBackground = wxColour(1,0,0);
12161 t_dc.SelectObject( qbm );
12162 t_dc.SetBackground(wxBrush(maskBackground));
12163 t_dc.Clear();
12164
12165 // Copy the scratch DC into the new bitmap
12166 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12167
12168 // Render the text to the new bitmap
12169 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12170 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12171
12172 // Copy the new bitmap back to the scratch dc
12173 wxRegionIterator upd_final( ru );
12174 while( upd_final ) {
12175 wxRect rect = upd_final.GetRect();
12176 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12177 upd_final++;
12178 }
12179
12180 t_dc.SelectObject( wxNullBitmap );
12181 }
12182 }
12183 }
12184#endif
12185 // Direct rendering model...
12186 if (VPoint.b_quilt) {
12187 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12188 ChartBase *chart = m_pQuilt->GetRefChart();
12189 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12190 // Clear the text Global declutter list
12191 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12192 if (ChPI)
12193 ChPI->ClearPLIBTextList();
12194 else {
12195 if (ps52plib) ps52plib->ClearTextList();
12196 }
12197
12198 // Render the text directly to the scratch bitmap
12199 OCPNRegion chart_all_text_region(
12200 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12201
12202 if (g_bShowChartBar && m_Piano) {
12203 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12204 GetVP().pix_width, m_Piano->GetHeight());
12205
12206 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12207 if (!style->chartStatusWindowTransparent)
12208 chart_all_text_region.Subtract(chart_bar_rect);
12209 }
12210
12211 if (m_Compass && m_Compass->IsShown()) {
12212 wxRect compassRect = m_Compass->GetRect();
12213 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12214 chart_all_text_region.Subtract(compassRect);
12215 }
12216 }
12217
12218 mscratch_dc.DestroyClippingRegion();
12219
12220 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12221 chart_all_text_region);
12222 }
12223 }
12224 }
12225
12226 // Now that charts are fully rendered, apply the overlay objects as decals.
12227 ocpnDC scratch_dc(mscratch_dc);
12228 DrawOverlayObjects(scratch_dc, ru);
12229
12230 // And finally, blit the scratch dc onto the physical dc
12231 wxRegionIterator upd_final(rgn_blit);
12232 while (upd_final) {
12233 wxRect rect = upd_final.GetRect();
12234 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12235 rect.y);
12236 upd_final++;
12237 }
12238
12239 // Deselect the chart bitmap from the temp_dc, so that it will not be
12240 // destroyed in the temp_dc dtor
12241 temp_dc.SelectObject(wxNullBitmap);
12242 // And for the scratch bitmap
12243 mscratch_dc.SelectObject(wxNullBitmap);
12244
12245 dc.DestroyClippingRegion();
12246
12247 PaintCleanup();
12248}
12249
12250void ChartCanvas::PaintCleanup() {
12251 // Handle the current graphic window, if present
12252 if (m_inPinch) return;
12253
12254 if (pCwin) {
12255 pCwin->Show();
12256 if (m_bTCupdate) {
12257 pCwin->Refresh();
12258 pCwin->Update();
12259 }
12260 }
12261
12262 // And set flags for next time
12263 m_bTCupdate = false;
12264
12265 // Handle deferred WarpPointer
12266 if (warp_flag) {
12267 WarpPointer(warp_x, warp_y);
12268 warp_flag = false;
12269 }
12270
12271 // Start movement timers, this runs nearly immediately.
12272 // the reason we cannot simply call it directly is the
12273 // refresh events it emits may be blocked from this paint event
12274 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12275 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12276}
12277
12278#if 0
12279wxColour GetErrorGraphicColor(double val)
12280{
12281 /*
12282 double valm = wxMin(val_max, val);
12283
12284 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12285 unsigned char red = (unsigned char)(255 * (valm/val_max));
12286
12287 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12288
12289 hv.saturation = 1.0;
12290 hv.value = 1.0;
12291
12292 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12293 return wxColour(rv.red, rv.green, rv.blue);
12294 */
12295
12296 // HTML colors taken from NOAA WW3 Web representation
12297 wxColour c;
12298 if((val > 0) && (val < 1)) c.Set("#002ad9");
12299 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12300 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12301 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12302 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12303 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12304 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12305 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12306 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12307 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12308 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12309 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12310 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12311 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12312 else if((val >= 30) && (val < 36)) c.Set("#870000");
12313 else if((val >= 36) && (val < 42)) c.Set("#690000");
12314 else if((val >= 42) && (val < 48)) c.Set("#550000");
12315 else if( val >= 48) c.Set("#410000");
12316
12317 return c;
12318}
12319
12320void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12321{
12322 wxImage gr_image(vp->pix_width, vp->pix_height);
12323 gr_image.InitAlpha();
12324
12325 double maxval = -10000;
12326 double minval = 10000;
12327
12328 double rlat, rlon;
12329 double glat, glon;
12330
12331 GetCanvasPixPoint(0, 0, rlat, rlon);
12332
12333 for(int i=1; i < vp->pix_height-1; i++)
12334 {
12335 for(int j=0; j < vp->pix_width; j++)
12336 {
12337 // Reference mercator value
12338// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12339
12340 // Georef value
12341 GetCanvasPixPoint(j, i, glat, glon);
12342
12343 maxval = wxMax(maxval, (glat - rlat));
12344 minval = wxMin(minval, (glat - rlat));
12345
12346 }
12347 rlat = glat;
12348 }
12349
12350 GetCanvasPixPoint(0, 0, rlat, rlon);
12351 for(int i=1; i < vp->pix_height-1; i++)
12352 {
12353 for(int j=0; j < vp->pix_width; j++)
12354 {
12355 // Reference mercator value
12356// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12357
12358 // Georef value
12359 GetCanvasPixPoint(j, i, glat, glon);
12360
12361 double f = ((glat - rlat)-minval)/(maxval - minval);
12362
12363 double dy = (f * 40);
12364
12365 wxColour c = GetErrorGraphicColor(dy);
12366 unsigned char r = c.Red();
12367 unsigned char g = c.Green();
12368 unsigned char b = c.Blue();
12369
12370 gr_image.SetRGB(j, i, r,g,b);
12371 if((glat - rlat )!= 0)
12372 gr_image.SetAlpha(j, i, 128);
12373 else
12374 gr_image.SetAlpha(j, i, 255);
12375
12376 }
12377 rlat = glat;
12378 }
12379
12380 // Create a Bitmap
12381 wxBitmap *pbm = new wxBitmap(gr_image);
12382 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12383 pbm->SetMask(gr_mask);
12384
12385 pmdc->DrawBitmap(*pbm, 0,0);
12386
12387 delete pbm;
12388
12389}
12390
12391#endif
12392
12393void ChartCanvas::CancelMouseRoute() {
12394 m_routeState = 0;
12395 m_pMouseRoute = NULL;
12396 m_bDrawingRoute = false;
12397}
12398
12399int ChartCanvas::GetNextContextMenuId() {
12400 return CanvasMenuHandler::GetNextContextMenuId();
12401}
12402
12403bool ChartCanvas::SetCursor(const wxCursor &c) {
12404#ifdef ocpnUSE_GL
12405 if (g_bopengl && m_glcc)
12406 return m_glcc->SetCursor(c);
12407 else
12408#endif
12409 return wxWindow::SetCursor(c);
12410}
12411
12412void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12413 if (g_bquiting) return;
12414 // Keep the mouse position members up to date
12415 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12416
12417 // Retrigger the route leg popup timer
12418 // This handles the case when the chart is moving in auto-follow mode,
12419 // but no user mouse input is made. The timer handler may Hide() the
12420 // popup if the chart moved enough n.b. We use slightly longer oneshot
12421 // value to allow this method's Refresh() to complete before potentially
12422 // getting another Refresh() in the popup timer handler.
12423 if (!m_RolloverPopupTimer.IsRunning() &&
12424 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12425 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12426 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12427 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12428
12429#ifdef ocpnUSE_GL
12430 if (m_glcc && g_bopengl) {
12431 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12432 // overlay objects.
12433 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12434
12435 m_glcc->Refresh(eraseBackground,
12436 NULL); // We always are going to render the entire screen
12437 // anyway, so make
12438 // sure that the window managers understand the invalid area
12439 // is actually the entire client area.
12440
12441 // We need to selectively Refresh some child windows, if they are visible.
12442 // Note that some children are refreshed elsewhere on timer ticks, so don't
12443 // need attention here.
12444
12445 // Thumbnail chart
12446 if (pthumbwin && pthumbwin->IsShown()) {
12447 pthumbwin->Raise();
12448 pthumbwin->Refresh(false);
12449 }
12450
12451 // ChartInfo window
12452 if (m_pCIWin && m_pCIWin->IsShown()) {
12453 m_pCIWin->Raise();
12454 m_pCIWin->Refresh(false);
12455 }
12456
12457 // if(g_MainToolbar)
12458 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12459
12460 } else
12461#endif
12462 wxWindow::Refresh(eraseBackground, rect);
12463}
12464
12465void ChartCanvas::Update() {
12466 if (m_glcc && g_bopengl) {
12467#ifdef ocpnUSE_GL
12468 m_glcc->Update();
12469#endif
12470 } else
12471 wxWindow::Update();
12472}
12473
12474void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12475 if (!pemboss) return;
12476 int x = pemboss->x, y = pemboss->y;
12477 const double factor = 200;
12478
12479 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12480 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12481 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12482
12483 // Grab a snipped image out of the chart
12484 wxMemoryDC snip_dc;
12485 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12486 snip_dc.SelectObject(snip_bmp);
12487
12488 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12489 snip_dc.SelectObject(wxNullBitmap);
12490
12491 wxImage snip_img = snip_bmp.ConvertToImage();
12492
12493 // Apply Emboss map to the snip image
12494 unsigned char *pdata = snip_img.GetData();
12495 if (pdata) {
12496 for (int y = 0; y < pemboss->height; y++) {
12497 int map_index = (y * pemboss->width);
12498 for (int x = 0; x < pemboss->width; x++) {
12499 double val = (pemboss->pmap[map_index] * factor) / 256.;
12500
12501 int nred = (int)((*pdata) + val);
12502 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12503 *pdata++ = (unsigned char)nred;
12504
12505 int ngreen = (int)((*pdata) + val);
12506 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12507 *pdata++ = (unsigned char)ngreen;
12508
12509 int nblue = (int)((*pdata) + val);
12510 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12511 *pdata++ = (unsigned char)nblue;
12512
12513 map_index++;
12514 }
12515 }
12516 }
12517
12518 // Convert embossed snip to a bitmap
12519 wxBitmap emb_bmp(snip_img);
12520
12521 // Map to another memoryDC
12522 wxMemoryDC result_dc;
12523 result_dc.SelectObject(emb_bmp);
12524
12525 // Blit to target
12526 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12527
12528 result_dc.SelectObject(wxNullBitmap);
12529}
12530
12531emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12532 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12533
12534 if (GetQuiltMode()) {
12535 // disable Overzoom indicator for MBTiles
12536 int refIndex = GetQuiltRefChartdbIndex();
12537 if (refIndex >= 0) {
12538 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12539 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12540 if (current_type == CHART_TYPE_MBTILES) {
12541 ChartBase *pChart = m_pQuilt->GetRefChart();
12542 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12543 if (ptc) {
12544 zoom_factor = ptc->GetZoomFactor();
12545 }
12546 }
12547 }
12548
12549 if (zoom_factor <= 3.9) return NULL;
12550 } else {
12551 if (m_singleChart) {
12552 if (zoom_factor <= 3.9) return NULL;
12553 } else
12554 return NULL;
12555 }
12556
12557 if (m_pEM_OverZoom) {
12558 m_pEM_OverZoom->x = 4;
12559 m_pEM_OverZoom->y = 0;
12560 if (g_MainToolbar && IsPrimaryCanvas()) {
12561 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12562 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12563 }
12564 }
12565 return m_pEM_OverZoom;
12566}
12567
12568void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12569 GridDraw(dc);
12570
12571 // bool pluginOverlayRender = true;
12572 //
12573 // if(g_canvasConfig > 0){ // Multi canvas
12574 // if(IsPrimaryCanvas())
12575 // pluginOverlayRender = false;
12576 // }
12577
12578 g_overlayCanvas = this;
12579
12580 if (g_pi_manager) {
12581 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12582 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12584 }
12585
12586 AISDrawAreaNotices(dc, GetVP(), this);
12587
12588 wxDC *pdc = dc.GetDC();
12589 if (pdc) {
12590 pdc->DestroyClippingRegion();
12591 wxDCClipper(*pdc, ru);
12592 }
12593
12594 if (m_bShowNavobjects) {
12595 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12596 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12597 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12598 DrawAnchorWatchPoints(dc);
12599 } else {
12600 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12601 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12602 }
12603
12604 AISDraw(dc, GetVP(), this);
12605 ShipDraw(dc);
12606 AlertDraw(dc);
12607
12608 RenderVisibleSectorLights(dc);
12609
12610 RenderAllChartOutlines(dc, GetVP());
12611 RenderRouteLegs(dc);
12612 RenderShipToActive(dc, false);
12613 ScaleBarDraw(dc);
12614 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12615 if (g_pi_manager) {
12616 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12618 }
12619
12620 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12621 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12622
12623 if (g_pi_manager) {
12624 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12626 }
12627
12628 if (m_bShowTide) {
12629 RebuildTideSelectList(GetVP().GetBBox());
12630 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12631 }
12632
12633 if (m_bShowCurrent) {
12634 RebuildCurrentSelectList(GetVP().GetBBox());
12635 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12636 }
12637
12638 if (!g_PrintingInProgress) {
12639 if (IsPrimaryCanvas()) {
12640 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12641 }
12642
12643 if (IsPrimaryCanvas()) {
12644 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12645 }
12646
12647 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12648
12649 if (m_pTrackRolloverWin) {
12650 m_pTrackRolloverWin->Draw(dc);
12651 m_brepaint_piano = true;
12652 }
12653
12654 if (m_pRouteRolloverWin) {
12655 m_pRouteRolloverWin->Draw(dc);
12656 m_brepaint_piano = true;
12657 }
12658
12659 if (m_pAISRolloverWin) {
12660 m_pAISRolloverWin->Draw(dc);
12661 m_brepaint_piano = true;
12662 }
12663 if (m_brepaint_piano && g_bShowChartBar) {
12664 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12665 }
12666
12667 if (m_Compass) m_Compass->Paint(dc);
12668
12669 if (!g_CanvasHideNotificationIcon) {
12670 auto &noteman = NotificationManager::GetInstance();
12671 if (noteman.GetNotificationCount()) {
12672 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12673 if (m_notification_button->UpdateStatus()) Refresh();
12674 m_notification_button->Show(true);
12675 m_notification_button->Paint(dc);
12676 } else {
12677 m_notification_button->Show(false);
12678 }
12679 }
12680 }
12681 if (g_pi_manager) {
12682 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12684 }
12685}
12686
12687emboss_data *ChartCanvas::EmbossDepthScale() {
12688 if (!m_bShowDepthUnits) return NULL;
12689
12690 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12691
12692 if (GetQuiltMode()) {
12693 wxString s = m_pQuilt->GetQuiltDepthUnit();
12694 s.MakeUpper();
12695 if (s == "FEET")
12696 depth_unit_type = DEPTH_UNIT_FEET;
12697 else if (s.StartsWith("FATHOMS"))
12698 depth_unit_type = DEPTH_UNIT_FATHOMS;
12699 else if (s.StartsWith("METERS"))
12700 depth_unit_type = DEPTH_UNIT_METERS;
12701 else if (s.StartsWith("METRES"))
12702 depth_unit_type = DEPTH_UNIT_METERS;
12703 else if (s.StartsWith("METRIC"))
12704 depth_unit_type = DEPTH_UNIT_METERS;
12705 else if (s.StartsWith("METER"))
12706 depth_unit_type = DEPTH_UNIT_METERS;
12707
12708 } else {
12709 if (m_singleChart) {
12710 depth_unit_type = m_singleChart->GetDepthUnitType();
12711 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12712 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12713 }
12714 }
12715
12716 emboss_data *ped = NULL;
12717 switch (depth_unit_type) {
12718 case DEPTH_UNIT_FEET:
12719 ped = m_pEM_Feet;
12720 break;
12721 case DEPTH_UNIT_METERS:
12722 ped = m_pEM_Meters;
12723 break;
12724 case DEPTH_UNIT_FATHOMS:
12725 ped = m_pEM_Fathoms;
12726 break;
12727 default:
12728 return NULL;
12729 }
12730
12731 ped->x = (GetVP().pix_width - ped->width);
12732
12733 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12734 wxRect r = m_Compass->GetRect();
12735 ped->y = r.y + r.height;
12736 } else {
12737 ped->y = 40;
12738 }
12739 return ped;
12740}
12741
12742void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12743 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12744 wxFont font;
12745 if (style->embossFont == wxEmptyString) {
12746 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12747 font = *dFont;
12748 font.SetPointSize(60);
12749 font.SetWeight(wxFONTWEIGHT_BOLD);
12750 } else
12751 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12752 wxFONTWEIGHT_BOLD, false, style->embossFont);
12753
12754 int emboss_width = 500;
12755 int emboss_height = 200;
12756
12757 // Free any existing emboss maps
12758 delete m_pEM_Feet;
12759 delete m_pEM_Meters;
12760 delete m_pEM_Fathoms;
12761
12762 // Create the 3 DepthUnit emboss map structures
12763 m_pEM_Feet =
12764 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12765 m_pEM_Meters =
12766 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12767 m_pEM_Fathoms =
12768 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12769}
12770
12771#define OVERZOOM_TEXT _("OverZoom")
12772
12773void ChartCanvas::SetOverzoomFont() {
12774 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12775 int w, h;
12776
12777 wxFont font;
12778 if (style->embossFont == wxEmptyString) {
12779 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12780 font = *dFont;
12781 font.SetPointSize(40);
12782 font.SetWeight(wxFONTWEIGHT_BOLD);
12783 } else
12784 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12785 wxFONTWEIGHT_BOLD, false, style->embossFont);
12786
12787 wxClientDC dc(this);
12788 dc.SetFont(font);
12789 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12790
12791 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12792 font.SetPointSize(font.GetPointSize() - 1);
12793 dc.SetFont(font);
12794 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12795 }
12796 m_overzoomFont = font;
12797 m_overzoomTextWidth = w;
12798 m_overzoomTextHeight = h;
12799}
12800
12801void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12802 delete m_pEM_OverZoom;
12803
12804 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12805 m_pEM_OverZoom =
12806 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12807 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12808}
12809
12810emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12811 int height, const wxString &str,
12812 ColorScheme cs) {
12813 int *pmap;
12814
12815 // Create a temporary bitmap
12816 wxBitmap bmp(width, height, -1);
12817
12818 // Create a memory DC
12819 wxMemoryDC temp_dc;
12820 temp_dc.SelectObject(bmp);
12821
12822 // Paint on it
12823 temp_dc.SetBackground(*wxWHITE_BRUSH);
12824 temp_dc.SetTextBackground(*wxWHITE);
12825 temp_dc.SetTextForeground(*wxBLACK);
12826
12827 temp_dc.Clear();
12828
12829 temp_dc.SetFont(font);
12830
12831 int str_w, str_h;
12832 temp_dc.GetTextExtent(str, &str_w, &str_h);
12833 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12834 temp_dc.DrawText(str, 1, 1);
12835
12836 // Deselect the bitmap
12837 temp_dc.SelectObject(wxNullBitmap);
12838
12839 // Convert bitmap the wxImage for manipulation
12840 wxImage img = bmp.ConvertToImage();
12841
12842 int image_width = str_w * 105 / 100;
12843 int image_height = str_h * 105 / 100;
12844 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12845 wxMin(image_height, img.GetHeight()));
12846 wxImage imgs = img.GetSubImage(r);
12847
12848 double val_factor;
12849 switch (cs) {
12850 case GLOBAL_COLOR_SCHEME_DAY:
12851 default:
12852 val_factor = 1;
12853 break;
12854 case GLOBAL_COLOR_SCHEME_DUSK:
12855 val_factor = .5;
12856 break;
12857 case GLOBAL_COLOR_SCHEME_NIGHT:
12858 val_factor = .25;
12859 break;
12860 }
12861
12862 int val;
12863 int index;
12864 const int w = imgs.GetWidth();
12865 const int h = imgs.GetHeight();
12866 pmap = (int *)calloc(w * h * sizeof(int), 1);
12867 // Create emboss map by differentiating the emboss image
12868 // and storing integer results in pmap
12869 // n.b. since the image is B/W, it is sufficient to check
12870 // one channel (i.e. red) only
12871 for (int y = 1; y < h - 1; y++) {
12872 for (int x = 1; x < w - 1; x++) {
12873 val =
12874 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
12875 val = (int)(val * val_factor);
12876 index = (y * w) + x;
12877 pmap[index] = val;
12878 }
12879 }
12880
12881 emboss_data *pret = new emboss_data;
12882 pret->pmap = pmap;
12883 pret->width = w;
12884 pret->height = h;
12885
12886 return pret;
12887}
12888
12889void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12890 Track *active_track = NULL;
12891 for (Track *pTrackDraw : g_TrackList) {
12892 if (g_pActiveTrack == pTrackDraw) {
12893 active_track = pTrackDraw;
12894 continue;
12895 }
12896
12897 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
12898 }
12899
12900 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12901}
12902
12903void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12904 Track *active_track = NULL;
12905 for (Track *pTrackDraw : g_TrackList) {
12906 if (g_pActiveTrack == pTrackDraw) {
12907 active_track = pTrackDraw;
12908 break;
12909 }
12910 }
12911 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
12912}
12913
12914void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12915 Route *active_route = NULL;
12916 for (Route *pRouteDraw : *pRouteList) {
12917 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12918 active_route = pRouteDraw;
12919 continue;
12920 }
12921
12922 // if(m_canvasIndex == 1)
12923 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
12924 }
12925
12926 // Draw any active or selected route (or track) last, so that is is always on
12927 // top
12928 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12929}
12930
12931void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12932 Route *active_route = NULL;
12933
12934 for (Route *pRouteDraw : *pRouteList) {
12935 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
12936 active_route = pRouteDraw;
12937 break;
12938 }
12939 }
12940 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
12941}
12942
12943void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
12944 if (!pWayPointMan) return;
12945
12946 auto node = pWayPointMan->GetWaypointList()->begin();
12947
12948 while (node != pWayPointMan->GetWaypointList()->end()) {
12949 RoutePoint *pWP = *node;
12950 if (pWP) {
12951 if (pWP->m_bIsInRoute) {
12952 ++node;
12953 continue;
12954 }
12955
12956 /* technically incorrect... waypoint has bounding box */
12957 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
12958 RoutePointGui(*pWP).Draw(dc, this, NULL);
12959 else {
12960 // Are Range Rings enabled?
12961 if (pWP->GetShowWaypointRangeRings() &&
12962 (pWP->GetWaypointRangeRingsNumber() > 0)) {
12963 double factor = 1.00;
12964 if (pWP->GetWaypointRangeRingsStepUnits() ==
12965 1) // convert kilometers to NMi
12966 factor = 1 / 1.852;
12967
12968 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
12969 pWP->GetWaypointRangeRingsStep() / 60.;
12970 radius *= 2; // Fudge factor
12971
12972 LLBBox radar_box;
12973 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
12974 pWP->m_lat + radius, pWP->m_lon + radius);
12975 if (!BltBBox.IntersectOut(radar_box)) {
12976 RoutePointGui(*pWP).Draw(dc, this, NULL);
12977 }
12978 }
12979 }
12980 }
12981
12982 ++node;
12983 }
12984}
12985
12986void ChartCanvas::DrawBlinkObjects() {
12987 // All RoutePoints
12988 wxRect update_rect;
12989
12990 if (!pWayPointMan) return;
12991
12992 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
12993 if (pWP) {
12994 if (pWP->m_bBlink) {
12995 update_rect.Union(pWP->CurrentRect_in_DC);
12996 }
12997 }
12998 }
12999 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13000}
13001
13002void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13003 // draw anchor watch rings, if activated
13004
13006 wxPoint r1, r2;
13007 wxPoint lAnchorPoint1, lAnchorPoint2;
13008 double lpp1 = 0.0;
13009 double lpp2 = 0.0;
13010 if (pAnchorWatchPoint1) {
13011 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13013 &lAnchorPoint1);
13014 }
13015 if (pAnchorWatchPoint2) {
13016 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13018 &lAnchorPoint2);
13019 }
13020
13021 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13022 wxPen ppPenr(GetGlobalColor("URED"), 2);
13023
13024 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13025 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13026 dc.SetBrush(*ppBrush);
13027
13028 if (lpp1 > 0) {
13029 dc.SetPen(ppPeng);
13030 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13031 }
13032
13033 if (lpp2 > 0) {
13034 dc.SetPen(ppPeng);
13035 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13036 }
13037
13038 if (lpp1 < 0) {
13039 dc.SetPen(ppPenr);
13040 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13041 }
13042
13043 if (lpp2 < 0) {
13044 dc.SetPen(ppPenr);
13045 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13046 }
13047 }
13048}
13049
13050double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13051 double lpp = 0.;
13052 wxPoint r1;
13053 wxPoint lAnchorPoint;
13054 double d1 = 0.0;
13055 double dabs;
13056 double tlat1, tlon1;
13057
13058 if (pAnchorWatchPoint) {
13059 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13060 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13061 dabs = fabs(d1 / 1852.);
13062 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13063 &tlat1, &tlon1);
13064 GetCanvasPointPix(tlat1, tlon1, &r1);
13065 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13066 &lAnchorPoint);
13067 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13068 pow((double)(lAnchorPoint.y - r1.y), 2));
13069
13070 // This is an entry watch
13071 if (d1 < 0) lpp = -lpp;
13072 }
13073 return lpp;
13074}
13075
13076//------------------------------------------------------------------------------------------
13077// Tides Support
13078//------------------------------------------------------------------------------------------
13079void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13080 if (!ptcmgr) return;
13081
13082 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13083
13084 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13085 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13086 double lon = pIDX->IDX_lon;
13087 double lat = pIDX->IDX_lat;
13088
13089 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13090 if ((type == 't') || (type == 'T')) {
13091 if (BBox.Contains(lat, lon)) {
13092 // Manage the point selection list
13093 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13094 }
13095 }
13096 }
13097}
13098
13099void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13100 if (!ptcmgr) return;
13101
13102 wxDateTime this_now = gTimeSource;
13103 bool cur_time = !gTimeSource.IsValid();
13104 if (cur_time) this_now = wxDateTime::Now();
13105 time_t t_this_now = this_now.GetTicks();
13106
13107 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13108 wxPENSTYLE_SOLID);
13109 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13110 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13111 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13112 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13113
13114 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13115 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13116 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13117 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13118 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13119 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13120
13121 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13122 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13123 int font_size = wxMax(10, dFont->GetPointSize());
13124 font_size /= g_Platform->GetDisplayDIPMult(this);
13125 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13126 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13127 false, dFont->GetFaceName());
13128
13129 dc.SetPen(*pblack_pen);
13130 dc.SetBrush(*pgreen_brush);
13131
13132 wxBitmap bm;
13133 switch (m_cs) {
13134 case GLOBAL_COLOR_SCHEME_DAY:
13135 bm = m_bmTideDay;
13136 break;
13137 case GLOBAL_COLOR_SCHEME_DUSK:
13138 bm = m_bmTideDusk;
13139 break;
13140 case GLOBAL_COLOR_SCHEME_NIGHT:
13141 bm = m_bmTideNight;
13142 break;
13143 default:
13144 bm = m_bmTideDay;
13145 break;
13146 }
13147
13148 int bmw = bm.GetWidth();
13149 int bmh = bm.GetHeight();
13150
13151 float scale_factor = 1.0;
13152
13153 // Set the onscreen size of the symbol
13154 // Compensate for various display resolutions
13155 float icon_pixelRefDim = 45;
13156
13157 // Tidal report graphic is scaled by the text size of the label in use
13158 wxScreenDC sdc;
13159 int height;
13160 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13161 height *= g_Platform->GetDisplayDIPMult(this);
13162 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13163
13164 scale_factor *= pix_factor;
13165
13166 float user_scale_factor = g_ChartScaleFactorExp;
13167 if (g_ChartScaleFactorExp > 1.0)
13168 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13169 1.2; // soften the scale factor a bit
13170
13171 scale_factor *= user_scale_factor;
13172 scale_factor *= GetContentScaleFactor();
13173
13174 {
13175 double marge = 0.05;
13176 std::vector<LLBBox> drawn_boxes;
13177 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13178 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13179
13180 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13181 if ((type == 't') || (type == 'T')) // only Tides
13182 {
13183 double lon = pIDX->IDX_lon;
13184 double lat = pIDX->IDX_lat;
13185
13186 if (BBox.ContainsMarge(lat, lon, marge)) {
13187 // Avoid drawing detailed graphic for duplicate tide stations
13188 if (GetVP().chart_scale < 500000) {
13189 bool bdrawn = false;
13190 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13191 if (drawn_boxes[i].Contains(lat, lon)) {
13192 bdrawn = true;
13193 break;
13194 }
13195 }
13196 if (bdrawn) continue; // the station loop
13197
13198 LLBBox this_box;
13199 this_box.Set(lat, lon, lat, lon);
13200 this_box.EnLarge(.005);
13201 drawn_boxes.push_back(this_box);
13202 }
13203
13204 wxPoint r;
13205 GetCanvasPointPix(lat, lon, &r);
13206 // draw standard icons
13207 if (GetVP().chart_scale > 500000) {
13208 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13209 }
13210 // draw "extended" icons
13211 else {
13212 dc.SetFont(*plabelFont);
13213 {
13214 {
13215 float val, nowlev;
13216 float ltleve = 0.;
13217 float htleve = 0.;
13218 time_t tctime;
13219 time_t lttime = 0;
13220 time_t httime = 0;
13221 bool wt;
13222 // define if flood or ebb in the last ten minutes and verify if
13223 // data are useable
13224 if (ptcmgr->GetTideFlowSens(
13225 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13226 pIDX->IDX_rec_num, nowlev, val, wt)) {
13227 // search forward the first HW or LW near "now" ( starting at
13228 // "now" - ten minutes )
13229 ptcmgr->GetHightOrLowTide(
13230 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13231 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13232 wt, pIDX->IDX_rec_num, val, tctime);
13233 if (wt) {
13234 httime = tctime;
13235 htleve = val;
13236 } else {
13237 lttime = tctime;
13238 ltleve = val;
13239 }
13240 wt = !wt;
13241
13242 // then search opposite tide near "now"
13243 if (tctime > t_this_now) // search backward
13244 ptcmgr->GetHightOrLowTide(
13245 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13246 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13247 pIDX->IDX_rec_num, val, tctime);
13248 else
13249 // or search forward
13250 ptcmgr->GetHightOrLowTide(
13251 t_this_now, FORWARD_TEN_MINUTES_STEP,
13252 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13253 val, tctime);
13254 if (wt) {
13255 httime = tctime;
13256 htleve = val;
13257 } else {
13258 lttime = tctime;
13259 ltleve = val;
13260 }
13261
13262 // draw the tide rectangle:
13263
13264 // tide icon rectangle has default pre-scaled width = 12 ,
13265 // height = 45
13266 int width = (int)(12 * scale_factor + 0.5);
13267 int height = (int)(45 * scale_factor + 0.5);
13268 int linew = wxMax(1, (int)(scale_factor));
13269 int xDraw = r.x - (width / 2);
13270 int yDraw = r.y - (height / 2);
13271
13272 // process tide state ( %height and flow sens )
13273 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13274 int hs = (httime > lttime) ? -4 : 4;
13275 hs *= (int)(scale_factor + 0.5);
13276 if (ts > 0.995 || ts < 0.005) hs = 0;
13277 int ht_y = (int)(height * ts);
13278
13279 // draw yellow tide rectangle outlined in black
13280 pblack_pen->SetWidth(linew);
13281 dc.SetPen(*pblack_pen);
13282 dc.SetBrush(*pyelo_brush);
13283 dc.DrawRectangle(xDraw, yDraw, width, height);
13284
13285 // draw blue rectangle as water height, smaller in width than
13286 // yellow rectangle
13287 dc.SetPen(*pblue_pen);
13288 dc.SetBrush(*pblue_brush);
13289 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13290 (width - (4 * linew)), height - ht_y);
13291
13292 // draw sens arrows (ensure they are not "under-drawn" by top
13293 // line of blue rectangle )
13294 int hl;
13295 wxPoint arrow[3];
13296 arrow[0].x = xDraw + 2 * linew;
13297 arrow[1].x = xDraw + width / 2;
13298 arrow[2].x = xDraw + width - 2 * linew;
13299 pyelo_pen->SetWidth(linew);
13300 pblue_pen->SetWidth(linew);
13301 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13302 {
13303 hl = (int)(height * 0.25) + yDraw;
13304 arrow[0].y = hl;
13305 arrow[1].y = hl + hs;
13306 arrow[2].y = hl;
13307 if (ts < 0.15)
13308 dc.SetPen(*pyelo_pen);
13309 else
13310 dc.SetPen(*pblue_pen);
13311 dc.DrawLines(3, arrow);
13312 }
13313 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13314 {
13315 hl = (int)(height * 0.5) + yDraw;
13316 arrow[0].y = hl;
13317 arrow[1].y = hl + hs;
13318 arrow[2].y = hl;
13319 if (ts < 0.40)
13320 dc.SetPen(*pyelo_pen);
13321 else
13322 dc.SetPen(*pblue_pen);
13323 dc.DrawLines(3, arrow);
13324 }
13325 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13326 {
13327 hl = (int)(height * 0.75) + yDraw;
13328 arrow[0].y = hl;
13329 arrow[1].y = hl + hs;
13330 arrow[2].y = hl;
13331 if (ts < 0.65)
13332 dc.SetPen(*pyelo_pen);
13333 else
13334 dc.SetPen(*pblue_pen);
13335 dc.DrawLines(3, arrow);
13336 }
13337 // draw tide level text
13338 wxString s;
13339 s.Printf("%3.1f", nowlev);
13340 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13341 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13342 int wx1;
13343 dc.GetTextExtent(s, &wx1, NULL);
13344 wx1 *= g_Platform->GetDisplayDIPMult(this);
13345 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13346 }
13347 }
13348 }
13349 }
13350 }
13351 }
13352 }
13353 }
13354}
13355
13356//------------------------------------------------------------------------------------------
13357// Currents Support
13358//------------------------------------------------------------------------------------------
13359
13360void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13361 if (!ptcmgr) return;
13362
13363 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13364
13365 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13366 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13367 double lon = pIDX->IDX_lon;
13368 double lat = pIDX->IDX_lat;
13369
13370 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13371 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13372 if ((BBox.Contains(lat, lon))) {
13373 // Manage the point selection list
13374 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13375 }
13376 }
13377 }
13378}
13379
13380void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13381 if (!ptcmgr) return;
13382
13383 float tcvalue, dir;
13384 bool bnew_val;
13385 char sbuf[20];
13386 wxFont *pTCFont;
13387 double lon_last = 0.;
13388 double lat_last = 0.;
13389 // arrow size for Raz Blanchard : 12 knots north
13390 double marge = 0.2;
13391 bool cur_time = !gTimeSource.IsValid();
13392
13393 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13394 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13395
13396 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13397 wxPENSTYLE_SOLID);
13398 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13399 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13400 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13401 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13402 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13403 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13404 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13405 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13406
13407 double skew_angle = GetVPRotation();
13408
13409 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13410 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13411 int font_size = wxMax(10, dFont->GetPointSize());
13412 font_size /= g_Platform->GetDisplayDIPMult(this);
13413 pTCFont = FontMgr::Get().FindOrCreateFont(
13414 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13415 false, dFont->GetFaceName());
13416
13417 float scale_factor = 1.0;
13418
13419 // Set the onscreen size of the symbol
13420 // Current report graphic is scaled by the text size of the label in use
13421 wxScreenDC sdc;
13422 int height;
13423 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13424 height *= g_Platform->GetDisplayDIPMult(this);
13425 float nominal_icon_size_pixels = 15;
13426 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13427
13428 scale_factor *= pix_factor;
13429
13430 float user_scale_factor = g_ChartScaleFactorExp;
13431 if (g_ChartScaleFactorExp > 1.0)
13432 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13433 1.2; // soften the scale factor a bit
13434
13435 scale_factor *= user_scale_factor;
13436
13437 scale_factor *= GetContentScaleFactor();
13438
13439 {
13440 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13441 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13442 double lon = pIDX->IDX_lon;
13443 double lat = pIDX->IDX_lat;
13444
13445 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13446 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13447 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13448 wxPoint r;
13449 GetCanvasPointPix(lat, lon, &r);
13450
13451 wxPoint d[4]; // points of a diamond at the current station location
13452 int dd = (int)(5.0 * scale_factor + 0.5);
13453 d[0].x = r.x;
13454 d[0].y = r.y + dd;
13455 d[1].x = r.x + dd;
13456 d[1].y = r.y;
13457 d[2].x = r.x;
13458 d[2].y = r.y - dd;
13459 d[3].x = r.x - dd;
13460 d[3].y = r.y;
13461
13462 if (1) {
13463 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13464 dc.SetPen(*pblack_pen);
13465 dc.SetBrush(*porange_brush);
13466 dc.DrawPolygon(4, d);
13467
13468 if (type == 'C') {
13469 dc.SetBrush(*pblack_brush);
13470 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13471 }
13472
13473 if (GetVP().chart_scale < 1000000) {
13474 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13475 continue;
13476 } else
13477 continue;
13478
13479 if (1 /*type == 'c'*/) {
13480 {
13481 // Get the display pixel location of the current station
13482 int pixxc, pixyc;
13483 pixxc = r.x;
13484 pixyc = r.y;
13485
13486 // Adjust drawing size using logarithmic scale. tcvalue is
13487 // current in knots
13488 double a1 = fabs(tcvalue) * 10.;
13489 // Current values <= 0.1 knot will have no arrow
13490 a1 = wxMax(1.0, a1);
13491 double a2 = log10(a1);
13492
13493 float cscale = scale_factor * a2 * 0.3;
13494
13495 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13496 dc.SetPen(*porange_pen);
13497 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13498 cscale);
13499 // Draw text, if enabled
13500
13501 if (bDrawCurrentValues) {
13502 dc.SetFont(*pTCFont);
13503 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13504 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13505 }
13506 }
13507 } // scale
13508 }
13509 /* This is useful for debugging the TC database
13510 else
13511 {
13512 dc.SetPen ( *porange_pen );
13513 dc.SetBrush ( *pgray_brush );
13514 dc.DrawPolygon ( 4, d );
13515 }
13516 */
13517 }
13518 lon_last = lon;
13519 lat_last = lat;
13520 }
13521 }
13522 }
13523}
13524
13525void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13526 ShowSingleTideDialog(x, y, pvIDX);
13527}
13528
13529void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13530 if (!pvIDX) return; // Validate input
13531
13532 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13533
13534 // Check if a tide dialog is already open and visible
13535 if (pCwin && pCwin->IsShown()) {
13536 // Same tide station: bring existing dialog to front (preserves user
13537 // context)
13538 if (pCwin->GetCurrentIDX() == pNewIDX) {
13539 pCwin->Raise();
13540 pCwin->SetFocus();
13541
13542 // Provide subtle visual feedback that dialog is already open
13543 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13544 return;
13545 }
13546
13547 // Different tide station: close current dialog before opening new one
13548 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13549 }
13550
13551 if (pCwin) {
13552 // This shouldn't happen but ensures clean state
13553 pCwin->Destroy();
13554 pCwin = NULL;
13555 }
13556
13557 // Create and display new tide dialog
13558 pCwin = new TCWin(this, x, y, pvIDX);
13559
13560 // Ensure the dialog is properly shown and focused
13561 if (pCwin) {
13562 pCwin->Show();
13563 pCwin->Raise();
13564 pCwin->SetFocus();
13565 }
13566}
13567
13568bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13569
13571 if (pCwin) {
13572 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13573 }
13574}
13575
13576#define NUM_CURRENT_ARROW_POINTS 9
13577static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13578 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13579 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13580 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13581
13582void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13583 double scale) {
13584 if (scale > 1e-2) {
13585 float sin_rot = sin(rot_angle * PI / 180.);
13586 float cos_rot = cos(rot_angle * PI / 180.);
13587
13588 // Move to the first point
13589
13590 float xt = CurrentArrowArray[0].x;
13591 float yt = CurrentArrowArray[0].y;
13592
13593 float xp = (xt * cos_rot) - (yt * sin_rot);
13594 float yp = (xt * sin_rot) + (yt * cos_rot);
13595 int x1 = (int)(xp * scale);
13596 int y1 = (int)(yp * scale);
13597
13598 // Walk thru the point list
13599 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13600 xt = CurrentArrowArray[ip].x;
13601 yt = CurrentArrowArray[ip].y;
13602
13603 float xp = (xt * cos_rot) - (yt * sin_rot);
13604 float yp = (xt * sin_rot) + (yt * cos_rot);
13605 int x2 = (int)(xp * scale);
13606 int y2 = (int)(yp * scale);
13607
13608 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13609
13610 x1 = x2;
13611 y1 = y2;
13612 }
13613 }
13614}
13615
13616wxString ChartCanvas::FindValidUploadPort() {
13617 wxString port;
13618 // Try to use the saved persistent upload port first
13619 if (!g_uploadConnection.IsEmpty() &&
13620 g_uploadConnection.StartsWith("Serial")) {
13621 port = g_uploadConnection;
13622 }
13623
13624 else {
13625 // If there is no persistent upload port recorded (yet)
13626 // then use the first available serial connection which has output defined.
13627 for (auto *cp : TheConnectionParams()) {
13628 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13629 port << "Serial:" << cp->Port;
13630 }
13631 }
13632 return port;
13633}
13634
13635void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13636 if (!win) return;
13637
13638 if (NULL == g_pais_query_dialog_active) {
13639 int pos_x = g_ais_query_dialog_x;
13640 int pos_y = g_ais_query_dialog_y;
13641
13642 if (g_pais_query_dialog_active) {
13643 g_pais_query_dialog_active->Destroy();
13644 g_pais_query_dialog_active = new AISTargetQueryDialog();
13645 } else {
13646 g_pais_query_dialog_active = new AISTargetQueryDialog();
13647 }
13648
13649 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13650 wxPoint(pos_x, pos_y));
13651
13652 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13653 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13654 g_pais_query_dialog_active->SetMMSI(mmsi);
13655 g_pais_query_dialog_active->UpdateText();
13656 wxSize sz = g_pais_query_dialog_active->GetSize();
13657
13658 bool b_reset_pos = false;
13659#ifdef __WXMSW__
13660 // Support MultiMonitor setups which an allow negative window positions.
13661 // If the requested window title bar does not intersect any installed
13662 // monitor, then default to simple primary monitor positioning.
13663 RECT frame_title_rect;
13664 frame_title_rect.left = pos_x;
13665 frame_title_rect.top = pos_y;
13666 frame_title_rect.right = pos_x + sz.x;
13667 frame_title_rect.bottom = pos_y + 30;
13668
13669 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13670 b_reset_pos = true;
13671#else
13672
13673 // Make sure drag bar (title bar) of window intersects wxClient Area of
13674 // screen, with a little slop...
13675 wxRect window_title_rect; // conservative estimate
13676 window_title_rect.x = pos_x;
13677 window_title_rect.y = pos_y;
13678 window_title_rect.width = sz.x;
13679 window_title_rect.height = 30;
13680
13681 wxRect ClientRect = wxGetClientDisplayRect();
13682 ClientRect.Deflate(
13683 60, 60); // Prevent the new window from being too close to the edge
13684 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13685
13686#endif
13687
13688 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13689
13690 } else {
13691 g_pais_query_dialog_active->SetMMSI(mmsi);
13692 g_pais_query_dialog_active->UpdateText();
13693 }
13694
13695 g_pais_query_dialog_active->Show();
13696}
13697
13698void ChartCanvas::ToggleCanvasQuiltMode() {
13699 bool cur_mode = GetQuiltMode();
13700
13701 if (!GetQuiltMode())
13702 SetQuiltMode(true);
13703 else if (GetQuiltMode()) {
13704 SetQuiltMode(false);
13705 g_sticky_chart = GetQuiltReferenceChartIndex();
13706 }
13707
13708 if (cur_mode != GetQuiltMode()) {
13709 SetupCanvasQuiltMode();
13710 DoCanvasUpdate();
13711 InvalidateGL();
13712 Refresh();
13713 }
13714 // TODO What to do about this?
13715 // g_bQuiltEnable = GetQuiltMode();
13716
13717 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13718 if (ps52plib) ps52plib->GenerateStateHash();
13719
13720 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13721 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13722}
13723
13724void ChartCanvas::DoCanvasStackDelta(int direction) {
13725 if (!GetQuiltMode()) {
13726 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13727 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13728 if ((current_stack_index + direction) < 0) return;
13729
13730 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13731 int new_dbIndex =
13732 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13733
13734 if (IsChartQuiltableRef(new_dbIndex)) {
13735 ToggleCanvasQuiltMode();
13736 SelectQuiltRefdbChart(new_dbIndex);
13737 m_bpersistent_quilt = false;
13738 }
13739 } else {
13740 SelectChartFromStack(current_stack_index + direction);
13741 }
13742 } else {
13743 std::vector<int> piano_chart_index_array =
13744 GetQuiltExtendedStackdbIndexArray();
13745 int refdb = GetQuiltRefChartdbIndex();
13746
13747 // Find the ref chart in the stack
13748 int current_index = -1;
13749 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13750 if (refdb == piano_chart_index_array[i]) {
13751 current_index = i;
13752 break;
13753 }
13754 }
13755 if (current_index == -1) return;
13756
13757 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13758 int target_family = ctet.GetChartFamily();
13759
13760 int new_index = -1;
13761 int check_index = current_index + direction;
13762 bool found = false;
13763 int check_dbIndex = -1;
13764 int new_dbIndex = -1;
13765
13766 // When quilted. switch within the same chart family
13767 while (!found &&
13768 (unsigned int)check_index < piano_chart_index_array.size() &&
13769 (check_index >= 0)) {
13770 check_dbIndex = piano_chart_index_array[check_index];
13771 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13772 if (target_family == cte.GetChartFamily()) {
13773 found = true;
13774 new_index = check_index;
13775 new_dbIndex = check_dbIndex;
13776 break;
13777 }
13778
13779 check_index += direction;
13780 }
13781
13782 if (!found) return;
13783
13784 if (!IsChartQuiltableRef(new_dbIndex)) {
13785 ToggleCanvasQuiltMode();
13786 SelectdbChart(new_dbIndex);
13787 m_bpersistent_quilt = true;
13788 } else {
13789 SelectQuiltRefChart(new_index);
13790 }
13791 }
13792
13793 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13794 // (checkmarks etc)
13795 SetQuiltChartHiLiteIndex(-1);
13796
13797 ReloadVP();
13798}
13799
13800//--------------------------------------------------------------------------------------------------------
13801//
13802// Toolbar support
13803//
13804//--------------------------------------------------------------------------------------------------------
13805
13806void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13807 // Handle the per-canvas toolbar clicks here
13808
13809 switch (event.GetId()) {
13810 case ID_ZOOMIN: {
13811 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13812 break;
13813 }
13814
13815 case ID_ZOOMOUT: {
13816 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13817 break;
13818 }
13819
13820 case ID_STKUP:
13821 DoCanvasStackDelta(1);
13822 DoCanvasUpdate();
13823 break;
13824
13825 case ID_STKDN:
13826 DoCanvasStackDelta(-1);
13827 DoCanvasUpdate();
13828 break;
13829
13830 case ID_FOLLOW: {
13831 TogglebFollow();
13832 break;
13833 }
13834
13835 case ID_CURRENT: {
13836 ShowCurrents(!GetbShowCurrent());
13837 ReloadVP();
13838 Refresh(false);
13839 break;
13840 }
13841
13842 case ID_TIDE: {
13843 ShowTides(!GetbShowTide());
13844 ReloadVP();
13845 Refresh(false);
13846 break;
13847 }
13848
13849 case ID_ROUTE: {
13850 if (0 == m_routeState) {
13851 StartRoute();
13852 } else {
13853 FinishRoute();
13854 }
13855
13856#ifdef __ANDROID__
13857 androidSetRouteAnnunciator(m_routeState == 1);
13858#endif
13859 break;
13860 }
13861
13862 case ID_AIS: {
13863 SetAISCanvasDisplayStyle(-1);
13864 break;
13865 }
13866
13867 default:
13868 break;
13869 }
13870
13871 // And then let gFrame handle the rest....
13872 event.Skip();
13873}
13874
13875void ChartCanvas::SetShowAIS(bool show) {
13876 m_bShowAIS = show;
13877 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13878 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13879}
13880
13881void ChartCanvas::SetAttenAIS(bool show) {
13882 m_bShowAISScaled = show;
13883 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13884 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13885}
13886
13887void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
13888 // make some arrays to hold the dfferences between cycle steps
13889 // show all, scaled, hide all
13890 bool bShowAIS_Array[3] = {true, true, false};
13891 bool bShowScaled_Array[3] = {false, true, true};
13892 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
13893 _("Attenuate less critical AIS targets"),
13894 _("Hide AIS Targets")};
13895 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
13896 int ArraySize = 3;
13897 int AIS_Toolbar_Switch = 0;
13898 if (StyleIndx == -1) { // -1 means coming from toolbar button
13899 // find current state of switch
13900 for (int i = 1; i < ArraySize; i++) {
13901 if ((bShowAIS_Array[i] == m_bShowAIS) &&
13902 (bShowScaled_Array[i] == m_bShowAISScaled))
13903 AIS_Toolbar_Switch = i;
13904 }
13905 AIS_Toolbar_Switch++; // we did click so continu with next item
13906 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
13907 AIS_Toolbar_Switch++;
13908
13909 } else { // coming from menu bar.
13910 AIS_Toolbar_Switch = StyleIndx;
13911 }
13912 // make sure we are not above array
13913 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
13914
13915 int AIS_Toolbar_Switch_Next =
13916 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
13917 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
13918 AIS_Toolbar_Switch_Next++;
13919 if (AIS_Toolbar_Switch_Next >= ArraySize)
13920 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
13921
13922 // Set found values to global and member variables
13923 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
13924 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
13925}
13926
13927void ChartCanvas::TouchAISToolActive() {}
13928
13929void ChartCanvas::UpdateAISTBTool() {}
13930
13931//---------------------------------------------------------------------------------
13932//
13933// Compass/GPS status icon support
13934//
13935//---------------------------------------------------------------------------------
13936
13937void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
13938 // Look for change in overlap or positions
13939 bool b_update = false;
13940 int cc1_edge_comp = 2;
13941 wxRect rect = m_Compass->GetRect();
13942 wxSize parent_size = GetSize();
13943
13944 parent_size *= m_displayScale;
13945
13946 // check to see if it would overlap if it was in its home position (upper
13947 // right)
13948 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
13949 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
13950 wxRect compass_rect(compass_pt, rect.GetSize());
13951
13952 m_Compass->Move(compass_pt);
13953
13954 if (m_Compass && m_Compass->IsShown())
13955 m_Compass->UpdateStatus(b_force_new | b_update);
13956
13957 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
13958 scaler = wxMax(scaler, 1.0);
13959 wxPoint note_point = wxPoint(
13960 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
13961 m_notification_button->Move(note_point);
13962 m_notification_button->UpdateStatus();
13963
13964 if (b_force_new | b_update) Refresh();
13965}
13966
13967void ChartCanvas::SelectChartFromStack(int index, bool bDir,
13968 ChartTypeEnum New_Type,
13969 ChartFamilyEnum New_Family) {
13970 if (!GetpCurrentStack()) return;
13971 if (!ChartData) return;
13972
13973 if (index < GetpCurrentStack()->nEntry) {
13974 // Open the new chart
13975 ChartBase *pTentative_Chart;
13976 pTentative_Chart = ChartData->OpenStackChartConditional(
13977 GetpCurrentStack(), index, bDir, New_Type, New_Family);
13978
13979 if (pTentative_Chart) {
13980 if (m_singleChart) m_singleChart->Deactivate();
13981
13982 m_singleChart = pTentative_Chart;
13983 m_singleChart->Activate();
13984
13985 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
13986 GetpCurrentStack(), m_singleChart->GetFullPath());
13987 }
13988
13989 // Setup the view
13990 double zLat, zLon;
13991 if (m_bFollow) {
13992 zLat = gLat;
13993 zLon = gLon;
13994 } else {
13995 zLat = m_vLat;
13996 zLon = m_vLon;
13997 }
13998
13999 double best_scale_ppm = GetBestVPScale(m_singleChart);
14000 double rotation = GetVPRotation();
14001 double oldskew = GetVPSkew();
14002 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14003
14004 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14005 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14006 if (fabs(newskew) > 0.0001) rotation = newskew;
14007 }
14008
14009 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14010
14011 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14012 }
14013
14014 // refresh Piano
14015 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14016 if (idx < 0) return;
14017
14018 std::vector<int> piano_active_chart_index_array;
14019 piano_active_chart_index_array.push_back(
14020 GetpCurrentStack()->GetCurrentEntrydbIndex());
14021 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14022}
14023
14024void ChartCanvas::SelectdbChart(int dbindex) {
14025 if (!GetpCurrentStack()) return;
14026 if (!ChartData) return;
14027
14028 if (dbindex >= 0) {
14029 // Open the new chart
14030 ChartBase *pTentative_Chart;
14031 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14032
14033 if (pTentative_Chart) {
14034 if (m_singleChart) m_singleChart->Deactivate();
14035
14036 m_singleChart = pTentative_Chart;
14037 m_singleChart->Activate();
14038
14039 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14040 GetpCurrentStack(), m_singleChart->GetFullPath());
14041 }
14042
14043 // Setup the view
14044 double zLat, zLon;
14045 if (m_bFollow) {
14046 zLat = gLat;
14047 zLon = gLon;
14048 } else {
14049 zLat = m_vLat;
14050 zLon = m_vLon;
14051 }
14052
14053 double best_scale_ppm = GetBestVPScale(m_singleChart);
14054
14055 if (m_singleChart)
14056 SetViewPoint(zLat, zLon, best_scale_ppm,
14057 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14058
14059 // SetChartUpdatePeriod( );
14060
14061 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14062 }
14063
14064 // TODO refresh_Piano();
14065}
14066
14067void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14068 double target_scale = GetVP().view_scale_ppm;
14069
14070 if (!GetQuiltMode()) {
14071 if (GetpCurrentStack()) {
14072 int stack_index = -1;
14073 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14074 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14075 if (check_dbIndex < 0) continue;
14076 const ChartTableEntry &cte =
14077 ChartData->GetChartTableEntry(check_dbIndex);
14078 if (type == cte.GetChartType()) {
14079 stack_index = i;
14080 break;
14081 } else if (family == cte.GetChartFamily()) {
14082 stack_index = i;
14083 break;
14084 }
14085 }
14086
14087 if (stack_index >= 0) {
14088 SelectChartFromStack(stack_index);
14089 }
14090 }
14091 } else {
14092 int sel_dbIndex = -1;
14093 std::vector<int> piano_chart_index_array =
14094 GetQuiltExtendedStackdbIndexArray();
14095 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14096 int check_dbIndex = piano_chart_index_array[i];
14097 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14098 if (type == cte.GetChartType()) {
14099 if (IsChartQuiltableRef(check_dbIndex)) {
14100 sel_dbIndex = check_dbIndex;
14101 break;
14102 }
14103 } else if (family == cte.GetChartFamily()) {
14104 if (IsChartQuiltableRef(check_dbIndex)) {
14105 sel_dbIndex = check_dbIndex;
14106 break;
14107 }
14108 }
14109 }
14110
14111 if (sel_dbIndex >= 0) {
14112 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14113 // Re-qualify the quilt reference chart selection
14114 AdjustQuiltRefChart();
14115 }
14116
14117 // Now reset the scale to the target...
14118 SetVPScale(target_scale);
14119 }
14120
14121 SetQuiltChartHiLiteIndex(-1);
14122
14123 ReloadVP();
14124}
14125
14126bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14127 return std::find(m_tile_yesshow_index_array.begin(),
14128 m_tile_yesshow_index_array.end(),
14129 index) != m_tile_yesshow_index_array.end();
14130}
14131
14132bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14133 return std::find(m_tile_noshow_index_array.begin(),
14134 m_tile_noshow_index_array.end(),
14135 index) != m_tile_noshow_index_array.end();
14136}
14137
14138void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14139 if (std::find(m_tile_noshow_index_array.begin(),
14140 m_tile_noshow_index_array.end(),
14141 index) == m_tile_noshow_index_array.end()) {
14142 m_tile_noshow_index_array.push_back(index);
14143 }
14144}
14145
14146//-------------------------------------------------------------------------------------------------------
14147//
14148// Piano support
14149//
14150//-------------------------------------------------------------------------------------------------------
14151
14152void ChartCanvas::HandlePianoClick(
14153 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14154 if (g_options && g_options->IsShown())
14155 return; // Piano might be invalid due to chartset updates.
14156 if (!m_pCurrentStack) return;
14157 if (!ChartData) return;
14158
14159 // stop movement or on slow computer we may get something like :
14160 // zoom out with the wheel (timer is set)
14161 // quickly click and display a chart, which may zoom in
14162 // but the delayed timer fires first and it zooms out again!
14163 StopMovement();
14164
14165 // When switching by piano key click, we may appoint the new target chart to
14166 // be any chart in the composite array.
14167 // As an improvement to UX, find the chart that is "closest" to the current
14168 // vp,
14169 // and select that chart. This will cause a jump to the centroid of that
14170 // chart
14171
14172 double distance = 25000; // RTW
14173 int closest_index = -1;
14174 for (int chart_index : selected_dbIndex_array) {
14175 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14176 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14177 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14178
14179 // measure distance as Manhattan style
14180 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14181 if (test_distance < distance) {
14182 distance = test_distance;
14183 closest_index = chart_index;
14184 }
14185 }
14186
14187 int selected_dbIndex = selected_dbIndex_array[0];
14188 if (closest_index >= 0) selected_dbIndex = closest_index;
14189
14190 if (!GetQuiltMode()) {
14191 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14192 if (IsChartQuiltableRef(selected_dbIndex)) {
14193 ToggleCanvasQuiltMode();
14194 SelectQuiltRefdbChart(selected_dbIndex);
14195 m_bpersistent_quilt = false;
14196 } else {
14197 SelectChartFromStack(selected_index);
14198 }
14199 } else {
14200 SelectChartFromStack(selected_index);
14201 g_sticky_chart = selected_dbIndex;
14202 }
14203
14204 if (m_singleChart)
14205 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14206 } else {
14207 // Handle MBTiles overlays first
14208 // Left click simply toggles the noshow array index entry
14209 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14210 bool bfound = false;
14211 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14212 if (m_tile_noshow_index_array[i] ==
14213 selected_dbIndex) { // chart is in the noshow list
14214 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14215 i); // erase it
14216 bfound = true;
14217 break;
14218 }
14219 }
14220 if (!bfound) {
14221 m_tile_noshow_index_array.push_back(selected_dbIndex);
14222 }
14223
14224 // If not already present, add this tileset to the "yes_show" array.
14225 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14226 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14227 }
14228
14229 else {
14230 if (IsChartQuiltableRef(selected_dbIndex)) {
14231 // if( ChartData ) ChartData->PurgeCache();
14232
14233 // If the chart is a vector chart, and of very large scale,
14234 // then we had better set the new scale directly to avoid excessive
14235 // underzoom on, eg, Inland ENCs
14236 bool set_scale = false;
14237 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14238 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14239 set_scale = true;
14240 }
14241 }
14242
14243 if (!set_scale) {
14244 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14245 } else {
14246 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14247
14248 // Adjust scale so that the selected chart is underzoomed/overzoomed
14249 // by a controlled amount
14250 ChartBase *pc =
14251 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14252 if (pc) {
14253 double proposed_scale_onscreen =
14255
14256 if (g_bPreserveScaleOnX) {
14257 proposed_scale_onscreen =
14258 wxMin(proposed_scale_onscreen,
14259 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14260 GetCanvasWidth()));
14261 } else {
14262 proposed_scale_onscreen =
14263 wxMin(proposed_scale_onscreen,
14264 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14265 GetCanvasWidth()));
14266
14267 proposed_scale_onscreen =
14268 wxMax(proposed_scale_onscreen,
14269 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14271 }
14272
14273 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14274 }
14275 }
14276 } else {
14277 ToggleCanvasQuiltMode();
14278 SelectdbChart(selected_dbIndex);
14279 m_bpersistent_quilt = true;
14280 }
14281 }
14282 }
14283
14284 SetQuiltChartHiLiteIndex(-1);
14285 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14286 // (checkmarks etc)
14287 HideChartInfoWindow();
14288 DoCanvasUpdate();
14289 ReloadVP(); // Pick up the new selections
14290}
14291
14292void ChartCanvas::HandlePianoRClick(
14293 int x, int y, int selected_index,
14294 const std::vector<int> &selected_dbIndex_array) {
14295 if (g_options && g_options->IsShown())
14296 return; // Piano might be invalid due to chartset updates.
14297 if (!GetpCurrentStack()) return;
14298
14299 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14300 UpdateCanvasControlBar();
14301
14302 SetQuiltChartHiLiteIndex(-1);
14303}
14304
14305void ChartCanvas::HandlePianoRollover(
14306 int selected_index, const std::vector<int> &selected_dbIndex_array,
14307 int n_charts, int scale) {
14308 if (g_options && g_options->IsShown())
14309 return; // Piano might be invalid due to chartset updates.
14310 if (!GetpCurrentStack()) return;
14311 if (!ChartData) return;
14312
14313 if (ChartData->IsBusy()) return;
14314
14315 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14316
14317 if (!GetQuiltMode()) {
14318 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14319 } else {
14320 // Select the correct vector
14321 std::vector<int> piano_chart_index_array;
14322 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14323 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14324 if ((GetpCurrentStack()->nEntry > 1) ||
14325 (piano_chart_index_array.size() >= 1)) {
14326 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14327
14328 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14329 ReloadVP(false); // no VP adjustment allowed
14330 } else if (GetpCurrentStack()->nEntry == 1) {
14331 const ChartTableEntry &cte =
14332 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14333 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14334 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14335 ReloadVP(false);
14336 } else if ((-1 == selected_index) &&
14337 (0 == selected_dbIndex_array.size())) {
14338 ShowChartInfoWindow(key_location.x, -1);
14339 }
14340 }
14341 } else {
14342 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14343
14344 if ((GetpCurrentStack()->nEntry > 1) ||
14345 (piano_chart_index_array.size() >= 1)) {
14346 if (n_charts > 1)
14347 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14348 selected_dbIndex_array);
14349 else if (n_charts == 1)
14350 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14351
14352 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14353 ReloadVP(false); // no VP adjustment allowed
14354 }
14355 }
14356 }
14357}
14358
14359void ChartCanvas::ClearPianoRollover() {
14360 ClearQuiltChartHiLiteIndexArray();
14361 ShowChartInfoWindow(0, -1);
14362 std::vector<int> vec;
14363 ShowCompositeInfoWindow(0, 0, 0, vec);
14364 ReloadVP(false);
14365}
14366
14367void ChartCanvas::UpdateCanvasControlBar() {
14368 if (m_pianoFrozen) return;
14369
14370 if (!GetpCurrentStack()) return;
14371 if (!ChartData) return;
14372 if (!g_bShowChartBar) return;
14373
14374 int sel_type = -1;
14375 int sel_family = -1;
14376
14377 std::vector<int> piano_chart_index_array;
14378 std::vector<int> empty_piano_chart_index_array;
14379
14380 wxString old_hash = m_Piano->GetStoredHash();
14381
14382 if (GetQuiltMode()) {
14383 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14384 GetQuiltFullScreendbIndexArray());
14385
14386 std::vector<int> piano_active_chart_index_array =
14387 GetQuiltCandidatedbIndexArray();
14388 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14389
14390 std::vector<int> piano_eclipsed_chart_index_array =
14391 GetQuiltEclipsedStackdbIndexArray();
14392 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14393
14394 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14395 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14396
14397 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14398 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14399 } else {
14400 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14401 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14402 // TODO refresh_Piano();
14403
14404 if (m_singleChart) {
14405 sel_type = m_singleChart->GetChartType();
14406 sel_family = m_singleChart->GetChartFamily();
14407 }
14408 }
14409
14410 // Set up the TMerc and Skew arrays
14411 std::vector<int> piano_skew_chart_index_array;
14412 std::vector<int> piano_tmerc_chart_index_array;
14413 std::vector<int> piano_poly_chart_index_array;
14414
14415 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14416 const ChartTableEntry &ctei =
14417 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14418 double skew_norm = ctei.GetChartSkew();
14419 if (skew_norm > 180.) skew_norm -= 360.;
14420
14421 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14422 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14423
14424 // Polyconic skewed charts should show as skewed
14425 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14426 if (fabs(skew_norm) > 1.)
14427 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14428 else
14429 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14430 } else if (fabs(skew_norm) > 1.)
14431 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14432 }
14433 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14434 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14435 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14436
14437 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14438 if (new_hash != old_hash) {
14439 m_Piano->FormatKeys();
14440 HideChartInfoWindow();
14441 m_Piano->ResetRollover();
14442 SetQuiltChartHiLiteIndex(-1);
14443 m_brepaint_piano = true;
14444 }
14445
14446 // Create a bitmask int that describes what Family/Type of charts are shown in
14447 // the bar, and notify the platform.
14448 int mask = 0;
14449 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14450 const ChartTableEntry &ctei =
14451 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14452 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14453 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14454 if (e == CHART_FAMILY_RASTER) mask |= 1;
14455 if (e == CHART_FAMILY_VECTOR) {
14456 if (t == CHART_TYPE_CM93COMP)
14457 mask |= 4;
14458 else
14459 mask |= 2;
14460 }
14461 }
14462
14463 wxString s_indicated;
14464 if (sel_type == CHART_TYPE_CM93COMP)
14465 s_indicated = "cm93";
14466 else {
14467 if (sel_family == CHART_FAMILY_RASTER)
14468 s_indicated = "raster";
14469 else if (sel_family == CHART_FAMILY_VECTOR)
14470 s_indicated = "vector";
14471 }
14472
14473 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14474}
14475
14476void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14477
14478void ChartCanvas::PianoPopupMenu(
14479 int x, int y, int selected_index,
14480 const std::vector<int> &selected_dbIndex_array) {
14481 if (!GetpCurrentStack()) return;
14482
14483 // No context menu if quilting is disabled
14484 if (!GetQuiltMode()) return;
14485
14486 m_piano_ctx_menu = new wxMenu();
14487
14488 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14489 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14490 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14491 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14492 } else {
14493 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14494 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14495 // wxEVT_COMMAND_MENU_SELECTED,
14496 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14497
14498 menu_selected_dbIndex = selected_dbIndex_array[0];
14499 menu_selected_index = selected_index;
14500
14501 // Search the no-show array
14502 bool b_is_in_noshow = false;
14503 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14504 if (m_quilt_noshow_index_array[i] ==
14505 menu_selected_dbIndex) // chart is in the noshow list
14506 {
14507 b_is_in_noshow = true;
14508 break;
14509 }
14510 }
14511
14512 if (b_is_in_noshow) {
14513 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14514 _("Show This Chart"));
14515 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14516 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14517 } else if (GetpCurrentStack()->nEntry > 1) {
14518 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14519 _("Hide This Chart"));
14520 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14521 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14522 }
14523 }
14524
14525 wxPoint pos = wxPoint(x, y - 30);
14526
14527 // Invoke the drop-down menu
14528 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14529 PopupMenu(m_piano_ctx_menu, pos);
14530
14531 delete m_piano_ctx_menu;
14532 m_piano_ctx_menu = NULL;
14533
14534 HideChartInfoWindow();
14535 m_Piano->ResetRollover();
14536
14537 SetQuiltChartHiLiteIndex(-1);
14538 ClearQuiltChartHiLiteIndexArray();
14539
14540 ReloadVP();
14541}
14542
14543void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14544 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14545 if (m_quilt_noshow_index_array[i] ==
14546 menu_selected_dbIndex) // chart is in the noshow list
14547 {
14548 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14549 break;
14550 }
14551 }
14552}
14553
14554void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14555 if (!GetpCurrentStack()) return;
14556 if (!ChartData) return;
14557
14558 RemoveChartFromQuilt(menu_selected_dbIndex);
14559
14560 // It could happen that the chart being disabled is the reference
14561 // chart....
14562 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14563 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14564
14565 int i = menu_selected_index + 1; // select next smaller scale chart
14566 bool b_success = false;
14567 while (i < GetpCurrentStack()->nEntry - 1) {
14568 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14569 if (type == ChartData->GetDBChartType(dbIndex)) {
14570 SelectQuiltRefChart(i);
14571 b_success = true;
14572 break;
14573 }
14574 i++;
14575 }
14576
14577 // If that did not work, try to select the next larger scale compatible
14578 // chart
14579 if (!b_success) {
14580 i = menu_selected_index - 1;
14581 while (i > 0) {
14582 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14583 if (type == ChartData->GetDBChartType(dbIndex)) {
14584 SelectQuiltRefChart(i);
14585 b_success = true;
14586 break;
14587 }
14588 i--;
14589 }
14590 }
14591 }
14592}
14593
14594void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14595 // Remove the item from the list (if it appears) to avoid multiple addition
14596 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14597 if (m_quilt_noshow_index_array[i] ==
14598 dbIndex) // chart is already in the noshow list
14599 {
14600 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14601 break;
14602 }
14603 }
14604
14605 m_quilt_noshow_index_array.push_back(dbIndex);
14606}
14607
14608bool ChartCanvas::UpdateS52State() {
14609 bool retval = false;
14610
14611 if (ps52plib) {
14612 ps52plib->SetShowS57Text(m_encShowText);
14613 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14614 ps52plib->m_bShowSoundg = m_encShowDepth;
14615 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14616 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14617
14618 // Lights
14619 if (!m_encShowLights) // On, going off
14620 ps52plib->AddObjNoshow("LIGHTS");
14621 else // Off, going on
14622 ps52plib->RemoveObjNoshow("LIGHTS");
14623 ps52plib->SetLightsOff(!m_encShowLights);
14624 ps52plib->m_bExtendLightSectors = true;
14625
14626 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14627 ps52plib->SetAnchorOn(m_encShowAnchor);
14628 ps52plib->SetQualityOfData(m_encShowDataQual);
14629 }
14630
14631 return retval;
14632}
14633
14634void ChartCanvas::SetShowENCDataQual(bool show) {
14635 m_encShowDataQual = show;
14636 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14637 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14638
14639 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14640}
14641
14642void ChartCanvas::SetShowENCText(bool show) {
14643 m_encShowText = show;
14644 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14645 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14646
14647 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14648}
14649
14650void ChartCanvas::SetENCDisplayCategory(int category) {
14651 m_encDisplayCategory = category;
14652 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14653}
14654
14655void ChartCanvas::SetShowENCDepth(bool show) {
14656 m_encShowDepth = show;
14657 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14658 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14659
14660 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14661}
14662
14663void ChartCanvas::SetShowENCLightDesc(bool show) {
14664 m_encShowLightDesc = show;
14665 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14666 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14667
14668 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14669}
14670
14671void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14672 m_encShowBuoyLabels = show;
14673 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14674}
14675
14676void ChartCanvas::SetShowENCLights(bool show) {
14677 m_encShowLights = show;
14678 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14679 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14680
14681 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14682}
14683
14684void ChartCanvas::SetShowENCAnchor(bool show) {
14685 m_encShowAnchor = show;
14686 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14687 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14688
14689 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14690}
14691
14692wxRect ChartCanvas::GetMUIBarRect() {
14693 wxRect rv;
14694 if (m_muiBar) {
14695 rv = m_muiBar->GetRect();
14696 }
14697
14698 return rv;
14699}
14700
14701void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14702 if (!GetAlertString().IsEmpty()) {
14703 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14704 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14705
14706 dc.SetFont(*pfont);
14707 dc.SetPen(*wxTRANSPARENT_PEN);
14708
14709 dc.SetBrush(wxColour(243, 229, 47));
14710 int w, h;
14711 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14712 h += 2;
14713 // int yp = vp.pix_height - 20 - h;
14714
14715 wxRect sbr = GetScaleBarRect();
14716 int xp = sbr.x + sbr.width + 10;
14717 int yp = (sbr.y + sbr.height) - h;
14718
14719 int wdraw = w + 10;
14720 dc.DrawRectangle(xp, yp, wdraw, h);
14721 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14722 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14723 }
14724}
14725
14726//--------------------------------------------------------------------------------------------------------
14727// Screen Brightness Control Support Routines
14728//
14729//--------------------------------------------------------------------------------------------------------
14730
14731#ifdef __UNIX__
14732#define BRIGHT_XCALIB
14733#define __OPCPN_USEICC__
14734#endif
14735
14736#ifdef __OPCPN_USEICC__
14737int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14738 double co_green, double co_blue);
14739
14740wxString temp_file_name;
14741#endif
14742
14743#if 0
14744class ocpnCurtain: public wxDialog
14745{
14746 DECLARE_CLASS( ocpnCurtain )
14747 DECLARE_EVENT_TABLE()
14748
14749public:
14750 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14751 ~ocpnCurtain( );
14752 bool ProcessEvent(wxEvent& event);
14753
14754};
14755
14756IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14757
14758BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14759END_EVENT_TABLE()
14760
14761ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14762{
14763 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14764}
14765
14766ocpnCurtain::~ocpnCurtain()
14767{
14768}
14769
14770bool ocpnCurtain::ProcessEvent(wxEvent& event)
14771{
14772 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14773 return GetParent()->GetEventHandler()->ProcessEvent(event);
14774}
14775#endif
14776
14777#ifdef _WIN32
14778#include <windows.h>
14779
14780HMODULE hGDI32DLL;
14781typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14782typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14783SetDeviceGammaRamp_ptr_type
14784 g_pSetDeviceGammaRamp; // the API entry points in the dll
14785GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14786
14787WORD *g_pSavedGammaMap;
14788
14789#endif
14790
14791int InitScreenBrightness() {
14792#ifdef _WIN32
14793#ifdef ocpnUSE_GL
14794 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14795 HDC hDC;
14796 BOOL bbr;
14797
14798 if (NULL == hGDI32DLL) {
14799 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14800
14801 if (NULL != hGDI32DLL) {
14802 // Get the entry points of the required functions
14803 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14804 hGDI32DLL, "SetDeviceGammaRamp");
14805 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14806 hGDI32DLL, "GetDeviceGammaRamp");
14807
14808 // If the functions are not found, unload the DLL and return false
14809 if ((NULL == g_pSetDeviceGammaRamp) ||
14810 (NULL == g_pGetDeviceGammaRamp)) {
14811 FreeLibrary(hGDI32DLL);
14812 hGDI32DLL = NULL;
14813 return 0;
14814 }
14815 }
14816 }
14817
14818 // Interface is ready, so....
14819 // Get some storage
14820 if (!g_pSavedGammaMap) {
14821 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14822
14823 hDC = GetDC(NULL); // Get the full screen DC
14824 bbr = g_pGetDeviceGammaRamp(
14825 hDC, g_pSavedGammaMap); // Get the existing ramp table
14826 ReleaseDC(NULL, hDC); // Release the DC
14827 }
14828
14829 // On Windows hosts, try to adjust the registry to allow full range
14830 // setting of Gamma table This is an undocumented Windows hack.....
14831 wxRegKey *pRegKey = new wxRegKey(
14832 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14833 "NT\\CurrentVersion\\ICM");
14834 if (!pRegKey->Exists()) pRegKey->Create();
14835 pRegKey->SetValue("GdiIcmGammaRange", 256);
14836
14837 g_brightness_init = true;
14838 return 1;
14839 }
14840#endif
14841
14842 {
14843 if (NULL == g_pcurtain) {
14844 if (gFrame->CanSetTransparent()) {
14845 // Build the curtain window
14846 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14847 wxPoint(0, 0), ::wxGetDisplaySize(),
14848 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14849 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14850
14851 // g_pcurtain = new ocpnCurtain(gFrame,
14852 // wxPoint(0,0),::wxGetDisplaySize(),
14853 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14854 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14855
14856 g_pcurtain->Hide();
14857
14858 HWND hWnd = GetHwndOf(g_pcurtain);
14859 SetWindowLong(hWnd, GWL_EXSTYLE,
14860 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
14861 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
14862 g_pcurtain->SetTransparent(0);
14863
14864 g_pcurtain->Maximize();
14865 g_pcurtain->Show();
14866
14867 // All of this is obtuse, but necessary for Windows...
14868 g_pcurtain->Enable();
14869 g_pcurtain->Disable();
14870
14871 gFrame->Disable();
14872 gFrame->Enable();
14873 // SetFocus();
14874 }
14875 }
14876 g_brightness_init = true;
14877
14878 return 1;
14879 }
14880#else
14881 // Look for "xcalib" application
14882 wxString cmd("xcalib -version");
14883
14884 wxArrayString output;
14885 long r = wxExecute(cmd, output);
14886 if (0 != r)
14887 wxLogMessage(
14888 " External application \"xcalib\" not found. Screen brightness "
14889 "not changed.");
14890
14891 g_brightness_init = true;
14892 return 0;
14893#endif
14894}
14895
14896int RestoreScreenBrightness() {
14897#ifdef _WIN32
14898
14899 if (g_pSavedGammaMap) {
14900 HDC hDC = GetDC(NULL); // Get the full screen DC
14901 g_pSetDeviceGammaRamp(hDC,
14902 g_pSavedGammaMap); // Restore the saved ramp table
14903 ReleaseDC(NULL, hDC); // Release the DC
14904
14905 free(g_pSavedGammaMap);
14906 g_pSavedGammaMap = NULL;
14907 }
14908
14909 if (g_pcurtain) {
14910 g_pcurtain->Close();
14911 g_pcurtain->Destroy();
14912 g_pcurtain = NULL;
14913 }
14914
14915 g_brightness_init = false;
14916 return 1;
14917
14918#endif
14919
14920#ifdef BRIGHT_XCALIB
14921 if (g_brightness_init) {
14922 wxString cmd;
14923 cmd = "xcalib -clear";
14924 wxExecute(cmd, wxEXEC_ASYNC);
14925 g_brightness_init = false;
14926 }
14927
14928 return 1;
14929#endif
14930
14931 return 0;
14932}
14933
14934// Set brightness. [0..100]
14935int SetScreenBrightness(int brightness) {
14936#ifdef _WIN32
14937
14938 // Under Windows, we use the SetDeviceGammaRamp function which exists in
14939 // some (most modern?) versions of gdi32.dll Load the required library dll,
14940 // if not already in place
14941#ifdef ocpnUSE_GL
14942 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14943 if (g_pcurtain) {
14944 g_pcurtain->Close();
14945 g_pcurtain->Destroy();
14946 g_pcurtain = NULL;
14947 }
14948
14949 InitScreenBrightness();
14950
14951 if (NULL == hGDI32DLL) {
14952 // Unicode stuff.....
14953 wchar_t wdll_name[80];
14954 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
14955 LPCWSTR cstr = wdll_name;
14956
14957 hGDI32DLL = LoadLibrary(cstr);
14958
14959 if (NULL != hGDI32DLL) {
14960 // Get the entry points of the required functions
14961 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14962 hGDI32DLL, "SetDeviceGammaRamp");
14963 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14964 hGDI32DLL, "GetDeviceGammaRamp");
14965
14966 // If the functions are not found, unload the DLL and return false
14967 if ((NULL == g_pSetDeviceGammaRamp) ||
14968 (NULL == g_pGetDeviceGammaRamp)) {
14969 FreeLibrary(hGDI32DLL);
14970 hGDI32DLL = NULL;
14971 return 0;
14972 }
14973 }
14974 }
14975
14976 HDC hDC = GetDC(NULL); // Get the full screen DC
14977
14978 /*
14979 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
14980 if (cmcap != CM_GAMMA_RAMP)
14981 {
14982 wxLogMessage(" Video hardware does not support brightness control by
14983 gamma ramp adjustment."); return false;
14984 }
14985 */
14986
14987 int increment = brightness * 256 / 100;
14988
14989 // Build the Gamma Ramp table
14990 WORD GammaTable[3][256];
14991
14992 int table_val = 0;
14993 for (int i = 0; i < 256; i++) {
14994 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
14995 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
14996 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
14997
14998 table_val += increment;
14999
15000 if (table_val > 65535) table_val = 65535;
15001 }
15002
15003 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15004 ReleaseDC(NULL, hDC); // Release the DC
15005
15006 return 1;
15007 }
15008#endif
15009
15010 {
15011 if (g_pSavedGammaMap) {
15012 HDC hDC = GetDC(NULL); // Get the full screen DC
15013 g_pSetDeviceGammaRamp(hDC,
15014 g_pSavedGammaMap); // Restore the saved ramp table
15015 ReleaseDC(NULL, hDC); // Release the DC
15016 }
15017
15018 if (brightness < 100) {
15019 if (NULL == g_pcurtain) InitScreenBrightness();
15020
15021 if (g_pcurtain) {
15022 int sbrite = wxMax(1, brightness);
15023 sbrite = wxMin(100, sbrite);
15024
15025 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15026 }
15027 } else {
15028 if (g_pcurtain) {
15029 g_pcurtain->Close();
15030 g_pcurtain->Destroy();
15031 g_pcurtain = NULL;
15032 }
15033 }
15034
15035 return 1;
15036 }
15037
15038#endif
15039
15040#ifdef BRIGHT_XCALIB
15041
15042 if (!g_brightness_init) {
15043 last_brightness = 100;
15044 g_brightness_init = true;
15045 temp_file_name = wxFileName::CreateTempFileName("");
15046 InitScreenBrightness();
15047 }
15048
15049#ifdef __OPCPN_USEICC__
15050 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15051 // desired, and then activate this temporary profile using xcalib <filename>
15052 if (!CreateSimpleICCProfileFile(
15053 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15054 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15055 wxString cmd("xcalib ");
15056 cmd += temp_file_name;
15057
15058 wxExecute(cmd, wxEXEC_ASYNC);
15059 }
15060
15061#else
15062 // Or, use "xcalib -co" to set overall contrast value
15063 // This is not as nice, since the -co parameter wants to be a fraction of
15064 // the current contrast, and values greater than 100 are not allowed. As a
15065 // result, increases of contrast must do a "-clear" step first, which
15066 // produces objectionable flashing.
15067 if (brightness > last_brightness) {
15068 wxString cmd;
15069 cmd = "xcalib -clear";
15070 wxExecute(cmd, wxEXEC_ASYNC);
15071
15072 ::wxMilliSleep(10);
15073
15074 int brite_adj = wxMax(1, brightness);
15075 cmd.Printf("xcalib -co %2d -a", brite_adj);
15076 wxExecute(cmd, wxEXEC_ASYNC);
15077 } else {
15078 int brite_adj = wxMax(1, brightness);
15079 int factor = (brite_adj * 100) / last_brightness;
15080 factor = wxMax(1, factor);
15081 wxString cmd;
15082 cmd.Printf("xcalib -co %2d -a", factor);
15083 wxExecute(cmd, wxEXEC_ASYNC);
15084 }
15085
15086#endif
15087
15088 last_brightness = brightness;
15089
15090#endif
15091
15092 return 0;
15093}
15094
15095#ifdef __OPCPN_USEICC__
15096
15097#define MLUT_TAG 0x6d4c5554L
15098#define VCGT_TAG 0x76636774L
15099
15100int GetIntEndian(unsigned char *s) {
15101 int ret;
15102 unsigned char *p;
15103 int i;
15104
15105 p = (unsigned char *)&ret;
15106
15107 if (1)
15108 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15109 else
15110 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15111
15112 return ret;
15113}
15114
15115unsigned short GetShortEndian(unsigned char *s) {
15116 unsigned short ret;
15117 unsigned char *p;
15118 int i;
15119
15120 p = (unsigned char *)&ret;
15121
15122 if (1)
15123 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15124 else
15125 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15126
15127 return ret;
15128}
15129
15130// Create a very simple Gamma correction file readable by xcalib
15131int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15132 double co_green, double co_blue) {
15133 FILE *fp;
15134
15135 if (file_name) {
15136 fp = fopen(file_name, "wb");
15137 if (!fp) return -1; /* file can not be created */
15138 } else
15139 return -1; /* filename char pointer not valid */
15140
15141 // Write header
15142 char header[128];
15143 for (int i = 0; i < 128; i++) header[i] = 0;
15144
15145 fwrite(header, 128, 1, fp);
15146
15147 // Num tags
15148 int numTags0 = 1;
15149 int numTags = GetIntEndian((unsigned char *)&numTags0);
15150 fwrite(&numTags, 1, 4, fp);
15151
15152 int tagName0 = VCGT_TAG;
15153 int tagName = GetIntEndian((unsigned char *)&tagName0);
15154 fwrite(&tagName, 1, 4, fp);
15155
15156 int tagOffset0 = 128 + 4 * sizeof(int);
15157 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15158 fwrite(&tagOffset, 1, 4, fp);
15159
15160 int tagSize0 = 1;
15161 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15162 fwrite(&tagSize, 1, 4, fp);
15163
15164 fwrite(&tagName, 1, 4, fp); // another copy of tag
15165
15166 fwrite(&tagName, 1, 4, fp); // dummy
15167
15168 // Table type
15169
15170 /* VideoCardGammaTable (The simplest type) */
15171 int gammatype0 = 0;
15172 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15173 fwrite(&gammatype, 1, 4, fp);
15174
15175 int numChannels0 = 3;
15176 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15177 fwrite(&numChannels, 1, 2, fp);
15178
15179 int numEntries0 = 256;
15180 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15181 fwrite(&numEntries, 1, 2, fp);
15182
15183 int entrySize0 = 1;
15184 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15185 fwrite(&entrySize, 1, 2, fp);
15186
15187 unsigned char ramp[256];
15188
15189 // Red ramp
15190 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15191 fwrite(ramp, 256, 1, fp);
15192
15193 // Green ramp
15194 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15195 fwrite(ramp, 256, 1, fp);
15196
15197 // Blue ramp
15198 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15199 fwrite(ramp, 256, 1, fp);
15200
15201 fclose(fp);
15202
15203 return 0;
15204}
15205#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.
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:70
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:56
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1219
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1218
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1219
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1218
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:49
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:125
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:157
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13570
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:4420
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4416
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3512
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13529
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:4366
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:781
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:484
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:515
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2247
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7916
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7726
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:4948
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:472
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:872
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4497
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5228
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:765
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4441
bool IsTideDialogOpen() const
Definition chcanv.cpp:13568
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:4503
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13525
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4361
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5247
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10046
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:468
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:440
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:108
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:191
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:61
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:203
Main application frame.
Definition ocpn_frame.h:139
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:60
PluginLoader is a backend module without any direct GUI functionality.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1718
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h:44
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:72
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:58
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:811
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
Definition routeman.cpp:998
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:474
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:52
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Font list manager.
OpenGL chart rendering canvas.
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.
MySQL based storage for routes, tracks, etc.
Utility functions.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
OpenCPN top window.
Optimized wxBitmap Object.
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:179
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:183
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:65
OpenCPN Toolbar.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.