OpenCPN Partial API docs
Loading...
Searching...
No Matches
chcanv.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2018 by David S. Register *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <vector>
25
26#include "gl_headers.h" // Must be included before anything using GL stuff
27
28// For compilers that support precompilation, includes "wx.h".
29#include <wx/wxprec.h>
30
31#ifndef WX_PRECOMP
32#include <wx/wx.h>
33#endif // precompiled headers
34#include <wx/image.h>
35#include <wx/graphics.h>
36#include <wx/clipbrd.h>
37#include <wx/aui/aui.h>
38
39#include "config.h"
40
41#include "o_sound/o_sound.h"
42
43#include "model/ais_decoder.h"
46#include "model/cmdline.h"
47#include "model/conn_params.h"
48#include "model/geodesic.h"
49#include "model/gui.h"
50#include "model/gui_vars.h"
51#include "model/idents.h"
52#include "model/multiplexer.h"
54#include "model/nav_object_database.h"
55#include "model/navobj_db.h"
56#include "model/navutil_base.h"
57#include "model/ocpn_utils.h"
58#include "model/own_ship.h"
59#include "model/plugin_comm.h"
60#include "model/route.h"
61#include "model/routeman.h"
62#include "model/select.h"
63#include "model/select_item.h"
64#include "model/track.h"
65#include "model/ocpn_utils.h"
66
67#include "ais.h"
70#include "canvas_config.h"
71#include "canvas_menu.h"
72#include "canvas_options.h"
73#include "chartdb.h"
74#include "chartimg.h"
75#include "chcanv.h"
76#include "ch_info_win.h"
77#include "cm93.h" // for chart outline draw
78#include "compass.h"
79#include "concanv.h"
80#include "detail_slider.h"
81#include "displays.h"
82#include "hotkeys_dlg.h"
83#include "font_mgr.h"
84#include "gl_texture_descr.h"
85#include "go_to_position_dlg.h"
86#include "gshhs.h"
87#include "ienc_toolbar.h"
88#include "kml.h"
89#include "line_clip.h"
90#include "mark_info.h"
91#include "mbtiles.h"
92#include "mui_bar.h"
93#include "navutil.h"
94#include "ocpn_aui_manager.h"
95#include "ocpndc.h"
96#include "ocpn_pixel.h"
97#include "ocpn_region.h"
98#include "options.h"
99#include "piano.h"
100#include "pluginmanager.h"
101#include "quilt.h"
102#include "route_gui.h"
103#include "routemanagerdialog.h"
104#include "route_point_gui.h"
105#include "route_prop_dlg_impl.h"
106#include "s52plib.h"
107#include "s52utils.h"
108#include "s57_query_dlg.h"
109#include "s57chart.h" // for ArrayOfS57Obj
110#include "senc_manager.h"
111#include "shapefile_basemap.h"
112#include "styles.h"
113#include "tcmgr.h"
114#include "tc_win.h"
115#include "thumbwin.h"
116#include "tide_time.h"
117#include "timers.h"
118#include "toolbar.h"
119#include "top_frame.h"
120#include "track_gui.h"
121#include "track_prop_dlg.h"
122#include "undo.h"
123#include "user_colors.h"
124
125#include "s57_ocpn_utils.h"
126
127#ifdef __ANDROID__
128#include "androidUTIL.h"
129#endif
130
131#ifdef ocpnUSE_GL
132#include "gl_chart_canvas.h"
135#endif
136
137#ifdef __VISUALC__
138#include <wx/msw/msvcrt.h>
139#endif
140
141#ifndef __WXMSW__
142#include <signal.h>
143#include <setjmp.h>
144#endif
145
146#ifdef __WXMSW__
147#define printf printf2
148
149int __cdecl printf2(const char *format, ...) {
150 char str[1024];
151
152 va_list argptr;
153 va_start(argptr, format);
154 int ret = vsnprintf(str, sizeof(str), format, argptr);
155 va_end(argptr);
156 OutputDebugStringA(str);
157 return ret;
158}
159#endif
160
161#if defined(__MSVC__) && (_MSC_VER < 1700)
162#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
163#endif
164
165// Define to enable the invocation of a temporary menubar by pressing the Alt
166// key. Not implemented for Windows XP, as it interferes with Alt-Tab
167// processing.
168#define OCPN_ALT_MENUBAR 1
169
170// Profiling support
171// #include "/usr/include/valgrind/callgrind.h"
172
173arrayofCanvasPtr g_canvasArray;
175static bool g_bSmoothRecenter = true;
176static bool bDrawCurrentValues;
186static int mouse_x;
196static int mouse_y;
197static bool mouse_leftisdown;
198static bool g_brouteCreating;
199static int r_gamma_mult;
200static int g_gamma_mult;
201static int b_gamma_mult;
202static int gamma_state;
203static bool g_brightness_init;
204static int last_brightness;
205static wxGLContext *g_pGLcontext; // shared common context
206
207// "Curtain" mode parameters
208static wxDialog *g_pcurtain;
209
210static wxString g_lastS52PLIBPluginMessage;
211
212#define MIN_BRIGHT 10
213#define MAX_BRIGHT 100
214
215//------------------------------------------------------------------------------
216// ChartCanvas Implementation
217//------------------------------------------------------------------------------
218BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
219EVT_PAINT(ChartCanvas::OnPaint)
220EVT_ACTIVATE(ChartCanvas::OnActivate)
221EVT_SIZE(ChartCanvas::OnSize)
222#ifndef HAVE_WX_GESTURE_EVENTS
223EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
224#endif
225EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
226EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
227EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
228EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
229EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
230EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
231EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
232EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
233EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
234EVT_KEY_UP(ChartCanvas::OnKeyUp)
235EVT_CHAR(ChartCanvas::OnKeyChar)
236EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
237EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
238EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
239EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
240EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
241EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
242EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
243EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
244EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
245EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
246
247END_EVENT_TABLE()
248
249// Define a constructor for my canvas
250ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
251 : AbstractChartCanvas(frame, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
252 m_nmea_log(nmea_log) {
253 parent_frame = frame; // save a pointer to parent
254 m_canvasIndex = canvasIndex;
255
256 pscratch_bm = NULL;
257
258 SetBackgroundColour(wxColour(0, 0, 0));
259 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
260 // color scheme change
261
262 m_groupIndex = 0;
263 m_bDrawingRoute = false;
264 m_bRouteEditing = false;
265 m_bMarkEditing = false;
266 m_bRoutePoinDragging = false;
267 m_bIsInRadius = false;
268 m_bMayToggleMenuBar = true;
269
270 m_bFollow = false;
271 m_bShowNavobjects = true;
272 m_bTCupdate = false;
273 m_bAppendingRoute = false; // was true in MSW, why??
274 pThumbDIBShow = NULL;
275 m_bShowCurrent = false;
276 m_bShowTide = false;
277 bShowingCurrent = false;
278 pCwin = NULL;
279 warp_flag = false;
280 m_bzooming = false;
281 m_b_paint_enable = true;
282 m_routeState = 0;
283
284 pss_overlay_bmp = NULL;
285 pss_overlay_mask = NULL;
286 m_bChartDragging = false;
287 m_bMeasure_Active = false;
288 m_bMeasure_DistCircle = false;
289 m_pMeasureRoute = NULL;
290 m_pTrackRolloverWin = NULL;
291 m_pRouteRolloverWin = NULL;
292 m_pAISRolloverWin = NULL;
293 m_bedge_pan = false;
294 m_disable_edge_pan = false;
295 m_dragoffsetSet = false;
296 m_bautofind = false;
297 m_bFirstAuto = true;
298 m_groupIndex = 0;
299 m_singleChart = NULL;
300 m_upMode = NORTH_UP_MODE;
301 m_bShowAIS = true;
302 m_bShowAISScaled = false;
303 m_timed_move_vp_active = false;
304 m_inPinch = false;
305 m_disable_adjust_on_zoom = false;
306
307 m_vLat = 0.;
308 m_vLon = 0.;
309
310 m_pCIWin = NULL;
311
312 m_pSelectedRoute = NULL;
313 m_pSelectedTrack = NULL;
314 m_pRoutePointEditTarget = NULL;
315 m_pFoundPoint = NULL;
316 m_pMouseRoute = NULL;
317 m_prev_pMousePoint = NULL;
318 m_pEditRouteArray = NULL;
319 m_pFoundRoutePoint = NULL;
320 m_FinishRouteOnKillFocus = true;
321
322 m_pRolloverRouteSeg = NULL;
323 m_pRolloverTrackSeg = NULL;
324 m_bsectors_shown = false;
325
326 m_bbrightdir = false;
327 r_gamma_mult = 1;
328 g_gamma_mult = 1;
329 b_gamma_mult = 1;
330
331 m_pos_image_user_day = NULL;
332 m_pos_image_user_dusk = NULL;
333 m_pos_image_user_night = NULL;
334 m_pos_image_user_grey_day = NULL;
335 m_pos_image_user_grey_dusk = NULL;
336 m_pos_image_user_grey_night = NULL;
337
338 m_zoom_factor = 1;
339 m_rotation_speed = 0;
340 m_mustmove = 0;
341
342 m_OSoffsetx = 0.;
343 m_OSoffsety = 0.;
344
345 m_pos_image_user_yellow_day = NULL;
346 m_pos_image_user_yellow_dusk = NULL;
347 m_pos_image_user_yellow_night = NULL;
348
349 SetOwnShipState(SHIP_INVALID);
350
351 undo = new Undo(this);
352
353 VPoint.Invalidate();
354
355 m_glcc = NULL;
356
357 m_focus_indicator_pix = 1;
358
359 m_pCurrentStack = NULL;
360 m_bpersistent_quilt = false;
361 m_piano_ctx_menu = NULL;
362 m_Compass = NULL;
363 m_NotificationsList = NULL;
364 m_notification_button = NULL;
365
366 g_ChartNotRenderScaleFactor = 2.0;
367 m_bShowScaleInStatusBar = true;
368
369 m_muiBar = NULL;
370 m_bShowScaleInStatusBar = false;
371 m_show_focus_bar = true;
372
373 m_bShowOutlines = false;
374 m_bDisplayGrid = false;
375 m_bShowDepthUnits = true;
376 m_encDisplayCategory = (int)STANDARD;
377
378 m_encShowLights = true;
379 m_encShowAnchor = true;
380 m_encShowDataQual = false;
381 m_bShowGPS = true;
382 m_pQuilt = new Quilt(this);
383 SetQuiltMode(true);
384 SetAlertString("");
385 m_sector_glat = 0;
386 m_sector_glon = 0;
387 g_PrintingInProgress = false;
388
389#ifdef HAVE_WX_GESTURE_EVENTS
390 m_oldVPSScale = -1.0;
391 m_popupWanted = false;
392 m_leftdown = false;
393#endif /* HAVE_WX_GESTURE_EVENTS */
394 m_inLongPress = false;
395 m_sw_down_time = 0;
396 m_sw_up_time = 0;
397 m_sw_left_down.Start();
398 m_sw_left_up.Start();
399
400 SetupGlCanvas();
401
402 singleClickEventIsValid = false;
403
404 // Build the cursors
405
406 pCursorLeft = NULL;
407 pCursorRight = NULL;
408 pCursorUp = NULL;
409 pCursorDown = NULL;
410 pCursorArrow = NULL;
411 pCursorPencil = NULL;
412 pCursorCross = NULL;
413
414 RebuildCursors();
415
416 SetCursor(*pCursorArrow);
417
418 pPanTimer = new wxTimer(this, m_MouseDragging);
419 pPanTimer->Stop();
420
421 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
422 pMovementTimer->Stop();
423
424 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
425 pMovementStopTimer->Stop();
426
427 pRotDefTimer = new wxTimer(this, ROT_TIMER);
428 pRotDefTimer->Stop();
429
430 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
431 m_DoubleClickTimer->Stop();
432
433 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
434 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
435 m_chart_drag_inertia_active = false;
436
437 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
438 m_animationActive = false;
439 m_menuTimer.SetOwner(this, MENU_TIMER);
440 m_tap_timer.SetOwner(this, TAP_TIMER);
441
442 m_panx = m_pany = 0;
443 m_panspeed = 0;
444 m_panx_target_final = m_pany_target_final = 0;
445 m_panx_target_now = m_pany_target_now = 0;
446 m_DragTrigger = -1;
447
448 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
449 pCurTrackTimer->Stop();
450 m_curtrack_timer_msec = 10;
451
452 m_wheelzoom_stop_oneshot = 0;
453 m_last_wheel_dir = 0;
454
455 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
456
457 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
458
459 m_rollover_popup_timer_msec = 20;
460
461 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
462
463 m_b_rot_hidef = true;
464
465 proute_bm = NULL;
466 m_prot_bm = NULL;
467
468 m_upMode = NORTH_UP_MODE;
469 m_bLookAhead = false;
470
471 // Set some benign initial values
472
473 m_cs = GLOBAL_COLOR_SCHEME_DAY;
474 VPoint.clat = 0;
475 VPoint.clon = 0;
476 VPoint.view_scale_ppm = 1;
477 VPoint.Invalidate();
478 m_nMeasureState = 0;
479
480 m_canvas_scale_factor = 1.;
481
482 m_canvas_width = 1000;
483
484 m_overzoomTextWidth = 0;
485 m_overzoomTextHeight = 0;
486
487 // Create the default world chart
488 pWorldBackgroundChart = new GSHHSChart;
489 gShapeBasemap.Reset();
490
491 // Create the default depth unit emboss maps
492 m_pEM_Feet = NULL;
493 m_pEM_Meters = NULL;
494 m_pEM_Fathoms = NULL;
495
496 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
497
498 m_pEM_OverZoom = NULL;
499 SetOverzoomFont();
500 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
501
502 // Build icons for tide/current points
503 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
504 m_bmTideDay =
505 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
506
507 // Dusk
508 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
509
510 // Night
511 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
512
513 // Build Dusk/Night ownship icons
514 double factor_dusk = 0.5;
515 double factor_night = 0.25;
516
517 // Red
518 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
519
520 int rimg_width = m_os_image_red_day.GetWidth();
521 int rimg_height = m_os_image_red_day.GetHeight();
522
523 m_os_image_red_dusk = m_os_image_red_day.Copy();
524 m_os_image_red_night = m_os_image_red_day.Copy();
525
526 for (int iy = 0; iy < rimg_height; iy++) {
527 for (int ix = 0; ix < rimg_width; ix++) {
528 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
529 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
530 m_os_image_red_day.GetGreen(ix, iy),
531 m_os_image_red_day.GetBlue(ix, iy));
532 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
533 hsv.value = hsv.value * factor_dusk;
534 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
535 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
536
537 hsv = wxImage::RGBtoHSV(rgb);
538 hsv.value = hsv.value * factor_night;
539 nrgb = wxImage::HSVtoRGB(hsv);
540 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
541 }
542 }
543 }
544
545 // Grey
546 m_os_image_grey_day =
547 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
548
549 int gimg_width = m_os_image_grey_day.GetWidth();
550 int gimg_height = m_os_image_grey_day.GetHeight();
551
552 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
553 m_os_image_grey_night = m_os_image_grey_day.Copy();
554
555 for (int iy = 0; iy < gimg_height; iy++) {
556 for (int ix = 0; ix < gimg_width; ix++) {
557 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
558 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
559 m_os_image_grey_day.GetGreen(ix, iy),
560 m_os_image_grey_day.GetBlue(ix, iy));
561 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
562 hsv.value = hsv.value * factor_dusk;
563 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
564 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
565
566 hsv = wxImage::RGBtoHSV(rgb);
567 hsv.value = hsv.value * factor_night;
568 nrgb = wxImage::HSVtoRGB(hsv);
569 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
570 }
571 }
572 }
573
574 // Yellow
575 m_os_image_yellow_day = m_os_image_red_day.Copy();
576
577 gimg_width = m_os_image_yellow_day.GetWidth();
578 gimg_height = m_os_image_yellow_day.GetHeight();
579
580 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
581 m_os_image_yellow_night = m_os_image_red_day.Copy();
582
583 for (int iy = 0; iy < gimg_height; iy++) {
584 for (int ix = 0; ix < gimg_width; ix++) {
585 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
586 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
587 m_os_image_yellow_day.GetGreen(ix, iy),
588 m_os_image_yellow_day.GetBlue(ix, iy));
589 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
590 hsv.hue += 60. / 360.; // shift to yellow
591 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
592 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
593
594 hsv = wxImage::RGBtoHSV(rgb);
595 hsv.value = hsv.value * factor_dusk;
596 hsv.hue += 60. / 360.; // shift to yellow
597 nrgb = wxImage::HSVtoRGB(hsv);
598 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
599
600 hsv = wxImage::RGBtoHSV(rgb);
601 hsv.hue += 60. / 360.; // shift to yellow
602 hsv.value = hsv.value * factor_night;
603 nrgb = wxImage::HSVtoRGB(hsv);
604 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
605 }
606 }
607 }
608
609 // Set initial pointers to ownship images
610 m_pos_image_red = &m_os_image_red_day;
611 m_pos_image_yellow = &m_os_image_yellow_day;
612 m_pos_image_grey = &m_os_image_grey_day;
613
614 SetUserOwnship();
615
616 m_pBrightPopup = NULL;
617
618#ifdef ocpnUSE_GL
619 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
620#endif
621
622 SetupGridFont();
623
624 m_Piano = new Piano(this);
625
626 m_bShowCompassWin = true;
627 m_Compass = new ocpnCompass(this);
628 m_Compass->SetScaleFactor(g_compass_scalefactor);
629 m_Compass->Show(false); // Will be shown later during init chain.
630
631 if (IsPrimaryCanvas() && !g_disableNotifications) {
632 m_notification_button = new NotificationButton(this);
633 m_notification_button->SetScaleFactor(g_compass_scalefactor);
634 m_notification_button->Show(true);
635 }
636
637 m_pianoFrozen = false;
638
639 SetMinSize(wxSize(200, 200));
640
641 m_displayScale = 1.0;
642#if defined(__WXOSX__) || defined(__WXGTK3__)
643 // Support scaled HDPI displays.
644 m_displayScale = GetContentScaleFactor();
645#endif
646 VPoint.SetPixelScale(m_displayScale);
647
648#ifdef HAVE_WX_GESTURE_EVENTS
649 // if (!m_glcc)
650 {
651 if (!EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_PRESS_GESTURES)) {
652 wxLogError("Failed to enable touch events");
653 }
654
655 // Bind(wxEVT_GESTURE_ZOOM, &ChartCanvas::OnZoom, this);
656
657 Bind(wxEVT_LONG_PRESS, &ChartCanvas::OnLongPress, this);
658 Bind(wxEVT_PRESS_AND_TAP, &ChartCanvas::OnPressAndTap, this);
659
660 Bind(wxEVT_RIGHT_UP, &ChartCanvas::OnRightUp, this);
661 Bind(wxEVT_RIGHT_DOWN, &ChartCanvas::OnRightDown, this);
662
663 Bind(wxEVT_LEFT_UP, &ChartCanvas::OnLeftUp, this);
664 Bind(wxEVT_LEFT_DOWN, &ChartCanvas::OnLeftDown, this);
665
666 Bind(wxEVT_MOUSEWHEEL, &ChartCanvas::OnWheel, this);
667 Bind(wxEVT_MOTION, &ChartCanvas::OnMotion, this);
668 }
669#endif
670
671 // Listen for notification events
672 auto &noteman = NotificationManager::GetInstance();
673
674 wxDEFINE_EVENT(EVT_NOTIFICATIONLIST_CHANGE, wxCommandEvent);
675 evt_notificationlist_change_listener.Listen(
676 noteman.evt_notificationlist_change, this, EVT_NOTIFICATIONLIST_CHANGE);
677 Bind(EVT_NOTIFICATIONLIST_CHANGE, [&](wxCommandEvent &) {
678 if (m_NotificationsList && m_NotificationsList->IsShown()) {
679 m_NotificationsList->ReloadNotificationList();
680 }
681 Refresh();
682 });
683}
684
685ChartCanvas::~ChartCanvas() {
686 delete pThumbDIBShow;
687
688 // Delete Cursors
689 delete pCursorLeft;
690 delete pCursorRight;
691 delete pCursorUp;
692 delete pCursorDown;
693 delete pCursorArrow;
694 delete pCursorPencil;
695 delete pCursorCross;
696
697 delete pPanTimer;
698 delete pMovementTimer;
699 delete pMovementStopTimer;
700 delete pCurTrackTimer;
701 delete pRotDefTimer;
702 delete m_DoubleClickTimer;
703
704 delete m_pTrackRolloverWin;
705 delete m_pRouteRolloverWin;
706 delete m_pAISRolloverWin;
707 delete m_pBrightPopup;
708
709 delete m_pCIWin;
710
711 delete pscratch_bm;
712
713 m_dc_route.SelectObject(wxNullBitmap);
714 delete proute_bm;
715
716 delete pWorldBackgroundChart;
717 delete pss_overlay_bmp;
718
719 delete m_pEM_Feet;
720 delete m_pEM_Meters;
721 delete m_pEM_Fathoms;
722
723 delete m_pEM_OverZoom;
724 // delete m_pEM_CM93Offset;
725
726 delete m_prot_bm;
727
728 delete m_pos_image_user_day;
729 delete m_pos_image_user_dusk;
730 delete m_pos_image_user_night;
731 delete m_pos_image_user_grey_day;
732 delete m_pos_image_user_grey_dusk;
733 delete m_pos_image_user_grey_night;
734 delete m_pos_image_user_yellow_day;
735 delete m_pos_image_user_yellow_dusk;
736 delete m_pos_image_user_yellow_night;
737
738 delete undo;
739#ifdef ocpnUSE_GL
740 if (!g_bdisable_opengl) {
741 delete m_glcc;
742
743#if wxCHECK_VERSION(2, 9, 0)
744 if (IsPrimaryCanvas() && g_bopengl) delete g_pGLcontext;
745#endif
746 }
747#endif
748
749 // Delete the MUI bar, but make sure there is no pointer to it during destroy.
750 // wx tries to deliver events to this canvas during destroy.
751 MUIBar *muiBar = m_muiBar;
752 m_muiBar = 0;
753 delete muiBar;
754 delete m_pQuilt;
755 delete m_pCurrentStack;
756 delete m_Compass;
757 delete m_Piano;
758 delete m_notification_button;
759}
760
761void ChartCanvas::SetupGridFont() {
762 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
763 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
764 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
765 m_pgridFont = FontMgr::Get().FindOrCreateFont(
766 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
767 FALSE, wxString("Arial"));
768}
769
770void ChartCanvas::RebuildCursors() {
771 delete pCursorLeft;
772 delete pCursorRight;
773 delete pCursorUp;
774 delete pCursorDown;
775 delete pCursorArrow;
776 delete pCursorPencil;
777 delete pCursorCross;
778
779 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
780 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
781
782 double pencilScale =
783 1.0 / g_Platform->GetDisplayDIPMult(wxTheApp->GetTopWindow());
784
785 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
786 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
787 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
788 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
789 wxImage ICursorPencil =
790 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
791 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
792
793#if !defined(__WXMSW__) && !defined(__WXQT__)
794 ICursorLeft.ConvertAlphaToMask(128);
795 ICursorRight.ConvertAlphaToMask(128);
796 ICursorUp.ConvertAlphaToMask(128);
797 ICursorDown.ConvertAlphaToMask(128);
798 ICursorPencil.ConvertAlphaToMask(10);
799 ICursorCross.ConvertAlphaToMask(10);
800#endif
801
802 if (ICursorLeft.Ok()) {
803 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
804 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
805 pCursorLeft = new wxCursor(ICursorLeft);
806 } else
807 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
808
809 if (ICursorRight.Ok()) {
810 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
811 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
812 pCursorRight = new wxCursor(ICursorRight);
813 } else
814 pCursorRight = new wxCursor(wxCURSOR_ARROW);
815
816 if (ICursorUp.Ok()) {
817 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
818 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
819 pCursorUp = new wxCursor(ICursorUp);
820 } else
821 pCursorUp = new wxCursor(wxCURSOR_ARROW);
822
823 if (ICursorDown.Ok()) {
824 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
825 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
826 pCursorDown = new wxCursor(ICursorDown);
827 } else
828 pCursorDown = new wxCursor(wxCURSOR_ARROW);
829
830 if (ICursorPencil.Ok()) {
831 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
832 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
833 pCursorPencil = new wxCursor(ICursorPencil);
834 } else
835 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
836
837 if (ICursorCross.Ok()) {
838 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
839 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
840 pCursorCross = new wxCursor(ICursorCross);
841 } else
842 pCursorCross = new wxCursor(wxCURSOR_ARROW);
843
844 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
845 pPlugIn_Cursor = NULL;
846}
847
848void ChartCanvas::CanvasApplyLocale() {
849 CreateDepthUnitEmbossMaps(m_cs);
850 CreateOZEmbossMapData(m_cs);
851}
852
853void ChartCanvas::SetupGlCanvas() {
854#ifndef __ANDROID__
855#ifdef ocpnUSE_GL
856 if (!g_bdisable_opengl) {
857 if (g_bopengl) {
858 wxLogMessage("Creating glChartCanvas");
859 m_glcc = new glChartCanvas(this);
860
861 // We use one context for all GL windows, so that textures etc will be
862 // automatically shared
863 if (IsPrimaryCanvas()) {
864 // qDebug() << "Creating Primary Context";
865
866 // wxGLContextAttrs ctxAttr;
867 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
868 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
869 // NULL, &ctxAttr);
870 wxGLContext *pctx = new wxGLContext(m_glcc);
871 m_glcc->SetContext(pctx);
872 g_pGLcontext = pctx; // Save a copy of the common context
873 } else {
874#ifdef __WXOSX__
875 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
876#else
877 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
878 // saved common context
879#endif
880 }
881 }
882 }
883#endif
884#endif
885
886#ifdef __ANDROID__ // ocpnUSE_GL
887 if (!g_bdisable_opengl) {
888 if (g_bopengl) {
889 // qDebug() << "SetupGlCanvas";
890 wxLogMessage("Creating glChartCanvas");
891
892 // We use one context for all GL windows, so that textures etc will be
893 // automatically shared
894 if (IsPrimaryCanvas()) {
895 qDebug() << "Creating Primary glChartCanvas";
896
897 // wxGLContextAttrs ctxAttr;
898 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
899 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
900 // NULL, &ctxAttr);
901 m_glcc = new glChartCanvas(this);
902
903 wxGLContext *pctx = new wxGLContext(m_glcc);
904 m_glcc->SetContext(pctx);
905 g_pGLcontext = pctx; // Save a copy of the common context
906 m_glcc->m_pParentCanvas = this;
907 // m_glcc->Reparent(this);
908 } else {
909 qDebug() << "Creating Secondary glChartCanvas";
910 // QGLContext *pctx =
911 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
912 // << "pctx: " << pctx;
913
914 m_glcc =
915 new glChartCanvas(wxTheApp->GetTopWindow(),
916 top_frame::Get()->GetWxGlCanvas()); // Shared
917 // m_glcc = new glChartCanvas(this, pctx); //Shared
918 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
919 wxGLContext *pwxctx = new wxGLContext(m_glcc);
920 m_glcc->SetContext(pwxctx);
921 m_glcc->m_pParentCanvas = this;
922 // m_glcc->Reparent(this);
923 }
924 }
925 }
926#endif
927}
928
929void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
930 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
931
932 // On Android, we get a KillFocus on just about every keystroke.
933 // Why?
934#ifdef __ANDROID__
935 return;
936#endif
937
938 // Special logic:
939 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
940 // canvas focus. Why??? Who knows... So, we provide for this case by
941 // starting a timer if required to actually Finish() a route on a legitimate
942 // focus change, but not if the focus is quickly regained ( <20 msec.) on
943 // this canvas.
944#ifdef __WXOSX__
945 if (m_routeState && m_FinishRouteOnKillFocus)
946 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
947#else
948 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
949#endif
950}
951
952void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
953 m_routeFinishTimer.Stop();
954
955 // Try to keep the global top-line menubar selections up to date with the
956 // current "focus" canvas
957 top_frame::Get()->UpdateGlobalMenuItems(this);
958
959 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
960}
961
962void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
963 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
964}
965
966#ifdef HAVE_WX_GESTURE_EVENTS
967void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
968#ifdef __ANDROID__
969 /* we defer the popup menu call upon the leftup event
970 else the menu disappears immediately,
971 (see
972 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
973 */
974 m_popupWanted = true;
975#else
976 m_inLongPress = !g_bhide_context_menus;
977
978 // Send a synthetic mouse left-up event to sync the mouse pan logic.
979 m_menuPos = event.GetPosition();
980 wxMouseEvent ev(wxEVT_LEFT_UP);
981 ev.m_x = m_menuPos.x;
982 ev.m_y = m_menuPos.y;
983 wxPostEvent(this, ev);
984
985 // In touch mode, send a "RIGHT CLICK" event, for plugins
986 if (g_btouch) {
987 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
988 ev_right_click.m_x = m_menuPos.x;
989 ev_right_click.m_y = m_menuPos.y;
990 MouseEvent(ev_right_click);
991 }
992#endif
993}
994
995void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
996 // not implemented yet
997}
998
999void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
1000
1001void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1002
1003void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1004#ifdef __WXGTK__
1005 long dt = m_sw_left_up.Time() - m_sw_up_time;
1006 m_sw_up_time = m_sw_left_up.Time();
1007
1008 // printf(" dt %ld\n",dt);
1009 if (dt < 5) {
1010 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1011 // ignore it.
1012 return;
1013 }
1014#endif
1015 // printf("Left_UP\n");
1016
1017 wxPoint pos = event.GetPosition();
1018
1019 m_leftdown = false;
1020
1021 if (!m_popupWanted) {
1022 wxMouseEvent ev(wxEVT_LEFT_UP);
1023 ev.m_x = pos.x;
1024 ev.m_y = pos.y;
1025 MouseEvent(ev);
1026 return;
1027 }
1028
1029 m_popupWanted = false;
1030
1031 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1032 ev.m_x = pos.x;
1033 ev.m_y = pos.y;
1034
1035 MouseEvent(ev);
1036}
1037
1038void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1039 m_leftdown = true;
1040
1041 // Detect and manage multiple left-downs coming from GTK mouse emulation
1042#ifdef __WXGTK__
1043 long dt = m_sw_left_down.Time() - m_sw_down_time;
1044 m_sw_down_time = m_sw_left_down.Time();
1045
1046 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1047
1048 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1049 // The timing between the two events is dependent upon the wxWidgets
1050 // message queue status, and the processing time required for intervening
1051 // events.
1052 // We detect and remove the duplicate events by measuring the elapsed time
1053 // between arrival of events.
1054 // Choose a duplicate detection time long enough to catch worst case time lag
1055 // between duplicating events, but considerably shorter than the nominal
1056 // "intentional double-click" time interval defined generally as 350 msec.
1057 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1058 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1059 // ignore it.
1060 return;
1061 }
1062#endif
1063
1064 // printf("Left_DOWN\n");
1065
1066 // detect and manage double-tap
1067#ifdef __WXGTK__
1068 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1069 2; // Use system setting for distance
1070 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1071 m_lastTapPos.y - max_double_click_distance,
1072 max_double_click_distance * 2, max_double_click_distance * 2);
1073
1074 // A new tap has started, check if it's close enough and in time
1075 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1076 // printf(" TapBump 1\n");
1077 m_tap_count += 1;
1078 } else {
1079 // printf(" TapSet 1\n");
1080 m_tap_count = 1;
1081 m_lastTapPos = event.GetPosition();
1082 m_tap_timer.StartOnce(
1083 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1084 }
1085
1086 if (m_tap_count == 2) {
1087 // printf(" Doubletap detected\n");
1088 m_tap_count = 0; // Reset after a double-tap
1089
1090 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1091 ev.m_x = event.m_x;
1092 ev.m_y = event.m_y;
1093 // wxPostEvent(this, ev);
1094 MouseEvent(ev);
1095 return;
1096 }
1097
1098#endif
1099
1100 MouseEvent(event);
1101}
1102
1103void ChartCanvas::OnMotion(wxMouseEvent &event) {
1104 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1105 dragging, upon simple click, and without the OnLeftDown event before Thus,
1106 this consists in skiping it, and setting the leftdown bit according to a
1107 status that we trust */
1108 event.m_leftDown = m_leftdown;
1109 MouseEvent(event);
1110}
1111
1112void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1113 /* there are spurious end zoom events upon right-click */
1114 if (event.IsGestureEnd()) return;
1115
1116 double factor = event.GetZoomFactor();
1117
1118 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1119 m_oldVPSScale = GetVPScale();
1120 }
1121
1122 double current_vps = GetVPScale();
1123 double wanted_factor = m_oldVPSScale / current_vps * factor;
1124
1125 ZoomCanvas(wanted_factor, true, false);
1126
1127 // Allow combined zoom/pan operation
1128 if (event.IsGestureStart()) {
1129 m_zoomStartPoint = event.GetPosition();
1130 } else {
1131 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1132 PanCanvas(-delta.x, -delta.y);
1133 m_zoomStartPoint = event.GetPosition();
1134 }
1135}
1136
1137void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1138
1139void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1140 DoRotateCanvas(0.0);
1141}
1142#endif /* HAVE_WX_GESTURE_EVENTS */
1143
1144void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1145 // printf("tap timer %d\n", m_tap_count);
1146 m_tap_count = 0;
1147}
1148
1149void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1150 m_FinishRouteOnKillFocus = false;
1151 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1152 m_FinishRouteOnKillFocus = true;
1153}
1154
1155void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1156 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1157 m_vLat = pcc->iLat;
1158 m_vLon = pcc->iLon;
1159
1160 m_restore_dbindex = pcc->DBindex;
1161 m_bFollow = pcc->bFollow;
1162 if (pcc->GroupID < 0) pcc->GroupID = 0;
1163
1164 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1165 m_groupIndex = 0;
1166 else
1167 m_groupIndex = pcc->GroupID;
1168
1169 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1170
1171 ShowTides(pcc->bShowTides);
1172 ShowCurrents(pcc->bShowCurrents);
1173
1174 SetShowDepthUnits(pcc->bShowDepthUnits);
1175 SetShowGrid(pcc->bShowGrid);
1176 SetShowOutlines(pcc->bShowOutlines);
1177
1178 SetShowAIS(pcc->bShowAIS);
1179 SetAttenAIS(pcc->bAttenAIS);
1180
1181 SetbEnableBasemapTile((pcc->bEnableBasemapTile));
1182
1183 // ENC options
1184 SetShowENCText(pcc->bShowENCText);
1185 m_encDisplayCategory = pcc->nENCDisplayCategory;
1186 m_encShowDepth = pcc->bShowENCDepths;
1187 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1188 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1189 m_encShowLights = pcc->bShowENCLights;
1190 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1191 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1192 m_encShowDataQual = pcc->bShowENCDataQuality;
1193
1194 bool courseUp = pcc->bCourseUp;
1195 bool headUp = pcc->bHeadUp;
1196 m_upMode = NORTH_UP_MODE;
1197 if (courseUp)
1198 m_upMode = COURSE_UP_MODE;
1199 else if (headUp)
1200 m_upMode = HEAD_UP_MODE;
1201
1202 m_bLookAhead = pcc->bLookahead;
1203
1204 m_singleChart = NULL;
1205}
1206
1207void ChartCanvas::ApplyGlobalSettings() {
1208 // GPS compas window
1209 if (m_Compass) {
1210 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1211 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1212 }
1213 if (m_notification_button) m_notification_button->UpdateStatus();
1214}
1215
1216void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1217 bool groupOK = CheckGroup(m_groupIndex);
1218
1219 if (!groupOK) {
1220 SetGroupIndex(m_groupIndex, true);
1221 }
1222}
1223
1224void ChartCanvas::SetShowGPS(bool bshow) {
1225 if (m_bShowGPS != bshow) {
1226 delete m_Compass;
1227 m_Compass = new ocpnCompass(this, bshow);
1228 m_Compass->SetScaleFactor(g_compass_scalefactor);
1229 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1230 }
1231 m_bShowGPS = bshow;
1232}
1233
1234void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1235 m_bShowCompassWin = bshow;
1236 if (m_Compass) {
1237 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1238 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1239 }
1240}
1241
1242int ChartCanvas::GetPianoHeight() {
1243 int height = 0;
1244 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1245
1246 return height;
1247}
1248
1249void ChartCanvas::ConfigureChartBar() {
1250 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1251
1252 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1253 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1254
1255 if (GetQuiltMode()) {
1256 m_Piano->SetRoundedRectangles(true);
1257 }
1258 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1259 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1260 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1261}
1262
1263void ChartCanvas::ShowTides(bool bShow) {
1264 top_frame::Get()->LoadHarmonics();
1265
1266 if (ptcmgr->IsReady()) {
1267 SetbShowTide(bShow);
1268
1269 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1270 } else {
1271 wxLogMessage("Chart1::Event...TCMgr Not Available");
1272 SetbShowTide(false);
1273 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1274 }
1275
1276 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1277 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1278
1279 // TODO
1280 // if( GetbShowTide() ) {
1281 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1282 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1283 // update
1284 // } else
1285 // FrameTCTimer.Stop();
1286}
1287
1288void ChartCanvas::ShowCurrents(bool bShow) {
1289 top_frame::Get()->LoadHarmonics();
1290
1291 if (ptcmgr->IsReady()) {
1292 SetbShowCurrent(bShow);
1293 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1294 } else {
1295 wxLogMessage("Chart1::Event...TCMgr Not Available");
1296 SetbShowCurrent(false);
1297 top_frame::Get()->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1298 }
1299
1300 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1301 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1302
1303 // TODO
1304 // if( GetbShowCurrent() ) {
1305 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1306 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1307 // update
1308 // } else
1309 // FrameTCTimer.Stop();
1310}
1311
1312// TODO
1313static ChartDummy *pDummyChart;
1314
1317
1318void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1319
1320void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1321 SetAlertString("");
1322
1323 int new_index = index;
1324 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1325
1326 bool bgroup_override = false;
1327 int old_group_index = new_index;
1328
1329 if (!CheckGroup(new_index)) {
1330 new_index = 0;
1331 bgroup_override = true;
1332 }
1333
1334 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1335 new_index = index;
1336
1337 // Get the currently displayed chart native scale, and the current ViewPort
1338 int current_chart_native_scale = GetCanvasChartNativeScale();
1339 ViewPort vp = GetVP();
1340
1341 m_groupIndex = new_index;
1342
1343 // Are there ENCs in this group
1344 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1345
1346 // Update the MUIBar for ENC availability
1347 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1348
1349 // Allow the chart database to pre-calculate the MBTile inclusion test
1350 // boolean...
1351 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1352
1353 // Invalidate the "sticky" chart on group change, since it might not be in
1354 // the new group
1355 g_sticky_chart = -1;
1356
1357 // We need a chartstack and quilt to figure out which chart to open in the
1358 // new group
1359 UpdateCanvasOnGroupChange();
1360
1361 int dbi_now = -1;
1362 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1363
1364 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1365
1366 // If a new reference chart is indicated, set a good scale for it.
1367 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1368 double best_scale = GetBestStartScale(dbi_hint, vp);
1369 SetVPScale(best_scale);
1370 }
1371
1372 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1373
1374 // Refresh the canvas, selecting the "best" chart,
1375 // applying the prior ViewPort exactly
1376 canvasChartsRefresh(dbi_hint);
1377
1378 UpdateCanvasControlBar();
1379
1380 if (!autoSwitch && bgroup_override) {
1381 // show a short timed message box
1382 wxString msg(_("Group \""));
1383
1384 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1385 msg += pGroup->m_group_name;
1386
1387 msg += _("\" is empty.");
1388
1389 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1390
1391 return;
1392 }
1393
1394 // Message box is deferred so that canvas refresh occurs properly before
1395 // dialog
1396 if (bgroup_override) {
1397 wxString msg(_("Group \""));
1398
1399 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1400 msg += pGroup->m_group_name;
1401
1402 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1403
1404 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1405 }
1406}
1407
1408bool ChartCanvas::CheckGroup(int igroup) {
1409 if (!ChartData) return true; // Not known yet...
1410
1411 if (igroup == 0) return true; // "all charts" is always OK
1412
1413 if (igroup < 0) // negative group is an error
1414 return false;
1415
1416 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1417
1418 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1419 // and auto-shift to group 0
1420 return false;
1421
1422 for (const auto &elem : pGroup->m_element_array) {
1423 for (unsigned int ic = 0;
1424 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1425 auto &cte = ChartData->GetChartTableEntry(ic);
1426 wxString chart_full_path(cte.GetpFullPath(), wxConvUTF8);
1427
1428 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1429 }
1430 }
1431
1432 // If necessary, check for GSHHS
1433 for (const auto &elem : pGroup->m_element_array) {
1434 const wxString &element_root = elem.m_element_name;
1435 wxString test_string = "GSHH";
1436 if (element_root.Upper().Contains(test_string)) return true;
1437 }
1438
1439 return false;
1440}
1441
1442void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1443 if (!ChartData) return;
1444
1445 AbstractPlatform::ShowBusySpinner();
1446
1447 double old_scale = GetVPScale();
1448 InvalidateQuilt();
1449 SetQuiltRefChart(-1);
1450
1451 m_singleChart = NULL;
1452
1453 // delete m_pCurrentStack;
1454 // m_pCurrentStack = NULL;
1455
1456 // Build a new ChartStack
1457 if (!m_pCurrentStack) {
1458 m_pCurrentStack = new ChartStack;
1459 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1460 }
1461
1462 if (-1 != dbi_hint) {
1463 if (GetQuiltMode()) {
1464 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1465 SetQuiltRefChart(dbi_hint);
1466 } else {
1467 // Open the saved chart
1468 ChartBase *pTentative_Chart;
1469 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1470
1471 if (pTentative_Chart) {
1472 /* m_singleChart is always NULL here, (set above) should this go before
1473 * that? */
1474 if (m_singleChart) m_singleChart->Deactivate();
1475
1476 m_singleChart = pTentative_Chart;
1477 m_singleChart->Activate();
1478
1479 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1480 GetpCurrentStack(), m_singleChart->GetFullPath());
1481 }
1482 }
1483
1484 // refresh_Piano();
1485 } else {
1486 // Select reference chart from the stack, as though clicked by user
1487 // Make it the smallest scale chart on the stack
1488 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1489 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1490 SetQuiltRefChart(selected_index);
1491 }
1492
1493 // Validate the correct single chart, or set the quilt mode as appropriate
1494 SetupCanvasQuiltMode();
1495 if (!GetQuiltMode() && m_singleChart == 0) {
1496 // use a dummy like in DoChartUpdate
1497 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1498 m_singleChart = pDummyChart;
1499 SetVPScale(old_scale);
1500 }
1501
1502 ReloadVP();
1503
1504 UpdateCanvasControlBar();
1505 UpdateGPSCompassStatusBox(true);
1506
1507 SetCursor(wxCURSOR_ARROW);
1508
1509 AbstractPlatform::HideBusySpinner();
1510}
1511
1512bool ChartCanvas::DoCanvasUpdate() {
1513 double tLat, tLon; // Chart Stack location
1514 double vpLat, vpLon; // ViewPort location
1515 bool blong_jump = false;
1516 meters_to_shift = 0;
1517 dir_to_shift = 0;
1518
1519 bool bNewChart = false;
1520 bool bNewView = false;
1521 bool bCanvasChartAutoOpen = true; // debugging
1522
1523 bool bNewPiano = false;
1524 bool bOpenSpecified;
1525 ChartStack LastStack;
1526 ChartBase *pLast_Ch;
1527
1528 ChartStack WorkStack;
1529
1530 if (!GetVP().IsValid()) return false;
1531 if (bDBUpdateInProgress) return false;
1532 if (!ChartData) return false;
1533
1534 if (ChartData->IsBusy()) return false;
1535 // Do not disturb any existing animations
1536 if (m_chart_drag_inertia_active) return false;
1537 if (m_animationActive) return false;
1538
1539 // Startup case:
1540 // Quilting is enabled, but the last chart seen was not quiltable
1541 // In this case, drop to single chart mode, set persistence flag,
1542 // And open the specified chart
1543 // TODO implement this
1544 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1545 // if( GetQuiltMode() ) {
1546 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1547 // gFrame->ToggleQuiltMode();
1548 // m_bpersistent_quilt = true;
1549 // m_singleChart = NULL;
1550 // }
1551 // }
1552 // }
1553
1554 // If in auto-follow mode, use the current glat,glon to build chart
1555 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1556 // other means
1557
1558 if (m_bFollow) {
1559 tLat = gLat;
1560 tLon = gLon;
1561
1562 // Set the ViewPort center based on the OWNSHIP offset
1563 double dx = m_OSoffsetx;
1564 double dy = m_OSoffsety;
1565 double d_east = dx / GetVP().view_scale_ppm;
1566 double d_north = dy / GetVP().view_scale_ppm;
1567
1568 if (GetUpMode() == NORTH_UP_MODE) {
1569 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1570 } else {
1571 double offset_angle = atan2(d_north, d_east);
1572 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1573 double chart_angle = GetVPRotation();
1574 double target_angle = chart_angle + offset_angle;
1575 double d_east_mod = offset_distance * cos(target_angle);
1576 double d_north_mod = offset_distance * sin(target_angle);
1577 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1578 }
1579
1580 // on lookahead mode, adjust the vp center point
1581 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1582 double cog_to_use = gCog;
1583 if (g_btenhertz &&
1584 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1585 cog_to_use = gCog_gt;
1586 blong_jump = true;
1587 }
1588 if (!g_btenhertz) cog_to_use = g_COGAvg;
1589
1590 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1591
1592 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1593 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1594
1595 double pixel_delta_tent =
1596 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1597
1598 double pixel_delta = 0;
1599
1600 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1601 // avoid jumping of the vp center point during slow maneuvering, or at
1602 // anchor....
1603 if (!std::isnan(gSog)) {
1604 if (gSog < 2.0)
1605 pixel_delta = 0.;
1606 else
1607 pixel_delta = pixel_delta_tent;
1608 }
1609
1610 meters_to_shift = 0;
1611 dir_to_shift = 0;
1612 if (!std::isnan(gCog)) {
1613 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1614 dir_to_shift = cog_to_use;
1615 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1616 &vpLon);
1617 } else {
1618 vpLat = gLat;
1619 vpLon = gLon;
1620 }
1621 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1622 m_OSoffsetx = 0; // center ownship on loss of GPS
1623 m_OSoffsety = 0;
1624 vpLat = gLat;
1625 vpLon = gLon;
1626 }
1627
1628 } else {
1629 tLat = m_vLat;
1630 tLon = m_vLon;
1631 vpLat = m_vLat;
1632 vpLon = m_vLon;
1633 }
1634
1635 if (GetQuiltMode()) {
1636 int current_db_index = -1;
1637 if (m_pCurrentStack)
1638 current_db_index =
1639 m_pCurrentStack
1640 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1641 // chart dbIndex
1642 else
1643 m_pCurrentStack = new ChartStack;
1644
1645 // This logic added to enable opening a chart when there is no
1646 // previous chart indication, either from inital startup, or from adding
1647 // new chart directory
1648 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1649 m_pCurrentStack) {
1650 if (m_pCurrentStack->nEntry) {
1651 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1652 1); // smallest scale
1653 SelectQuiltRefdbChart(new_dbIndex, true);
1654 m_bautofind = false;
1655 }
1656 }
1657
1658 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1659 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1660
1661 if (m_bFirstAuto) {
1662 // Allow the chart database to pre-calculate the MBTile inclusion test
1663 // boolean...
1664 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1665
1666 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1667 // physical pixels. On standard DPI displays where logical = physical
1668 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1669 // logical pixels, this ratio would be 0.5.
1670 double proposed_scale_onscreen =
1671 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1672
1673 int initial_db_index = m_restore_dbindex;
1674 if (initial_db_index < 0) {
1675 if (m_pCurrentStack->nEntry) {
1676 initial_db_index =
1677 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1678 } else
1679 m_bautofind = true; // initial_db_index = 0;
1680 }
1681
1682 if (m_pCurrentStack->nEntry) {
1683 int initial_type = ChartData->GetDBChartType(initial_db_index);
1684
1685 // Check to see if the target new chart is quiltable as a reference
1686 // chart
1687
1688 if (!IsChartQuiltableRef(initial_db_index)) {
1689 // If it is not quiltable, then walk the stack up looking for a
1690 // satisfactory chart i.e. one that is quiltable and of the same type
1691 // XXX if there's none?
1692 int stack_index = 0;
1693
1694 if (stack_index >= 0) {
1695 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1696 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1697 if (IsChartQuiltableRef(test_db_index) &&
1698 (initial_type ==
1699 ChartData->GetDBChartType(initial_db_index))) {
1700 initial_db_index = test_db_index;
1701 break;
1702 }
1703 stack_index++;
1704 }
1705 }
1706 }
1707
1708 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1709 if (pc) {
1710 SetQuiltRefChart(initial_db_index);
1711 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1712 }
1713 }
1714 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1715 // just GetVPScale(), so I'm not sure why it's necessary to define the
1716 // proposed_scale_onscreen variable.
1717 bNewView |= SetViewPoint(vpLat, vpLon,
1718 GetCanvasScaleFactor() / proposed_scale_onscreen,
1719 0, GetVPRotation());
1720 }
1721 // Measure rough jump distance if in bfollow mode
1722 // No good reason to do smooth pan for
1723 // jump distance more than one screen width at scale.
1724 bool super_jump = false;
1725 if (m_bFollow) {
1726 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1727 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1728 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1729 }
1730 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1731 goto update_finish;
1732 }
1733
1734 // Single Chart Mode from here....
1735 pLast_Ch = m_singleChart;
1736 ChartTypeEnum new_open_type;
1737 ChartFamilyEnum new_open_family;
1738 if (pLast_Ch) {
1739 new_open_type = pLast_Ch->GetChartType();
1740 new_open_family = pLast_Ch->GetChartFamily();
1741 } else {
1742 new_open_type = CHART_TYPE_KAP;
1743 new_open_family = CHART_FAMILY_RASTER;
1744 }
1745
1746 bOpenSpecified = m_bFirstAuto;
1747
1748 // Make sure the target stack is valid
1749 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1750
1751 // Build a chart stack based on tLat, tLon
1752 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1753 m_groupIndex)) { // Bogus Lat, Lon?
1754 if (NULL == pDummyChart) {
1755 pDummyChart = new ChartDummy;
1756 bNewChart = true;
1757 }
1758
1759 if (m_singleChart)
1760 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1761
1762 m_singleChart = pDummyChart;
1763
1764 // If the current viewpoint is invalid, set the default scale to
1765 // something reasonable.
1766 double set_scale = GetVPScale();
1767 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1768
1769 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1770
1771 // If the chart stack has just changed, there is new status
1772 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1773 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1774 bNewPiano = true;
1775 bNewChart = true;
1776 }
1777 }
1778
1779 // Copy the new (by definition empty) stack into the target stack
1780 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1781
1782 goto update_finish;
1783 }
1784
1785 // Check to see if Chart Stack has changed
1786 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1787 // New chart stack, so...
1788 bNewPiano = true;
1789
1790 // Save a copy of the current stack
1791 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1792
1793 // Copy the new stack into the target stack
1794 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1795
1796 // Is Current Chart in new stack?
1797
1798 int tEntry = -1;
1799 if (NULL != m_singleChart) // this handles startup case
1800 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1801 m_singleChart->GetFullPath());
1802
1803 if (tEntry != -1) { // m_singleChart is in the new stack
1804 m_pCurrentStack->CurrentStackEntry = tEntry;
1805 bNewChart = false;
1806 }
1807
1808 else // m_singleChart is NOT in new stack, or m_singlechart not yet set
1809 { // So, need to open a new chart
1810 // Find the largest scale raster chart that opens OK
1811
1812 ChartBase *pProposed = NULL;
1813
1814 if (bCanvasChartAutoOpen) {
1815 bool search_direction =
1816 false; // default is to search from lowest to highest
1817 int start_index = 0;
1818
1819 // A special case: If panning at high scale, open largest scale
1820 // chart first
1821 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1822 (LastStack.nEntry == 0)) {
1823 search_direction = true;
1824 start_index = m_pCurrentStack->nEntry - 1;
1825 }
1826
1827 // Another special case, open specified db index on program start
1828 if (bOpenSpecified) {
1829 if (m_restore_dbindex >= 0) {
1830 pProposed =
1831 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
1832 std::vector<int> one_array;
1833 one_array.push_back(m_restore_dbindex);
1834 m_Piano->SetActiveKeyArray(one_array);
1835 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
1836 m_restore_dbindex = -1; // Mark as used...
1837 }
1838
1839 if (!pProposed) {
1840 search_direction = false;
1841 start_index = m_restore_dbindex;
1842 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1843 start_index = 0;
1844
1845 new_open_type = CHART_TYPE_DONTCARE;
1846 }
1847 }
1848
1849 if (!pProposed) {
1850 pProposed = ChartData->OpenStackChartConditional(
1851 m_pCurrentStack, start_index, search_direction, new_open_type,
1852 new_open_family);
1853
1854 // Try to open other types/families of chart in some priority
1855 if (NULL == pProposed)
1856 pProposed = ChartData->OpenStackChartConditional(
1857 m_pCurrentStack, start_index, search_direction,
1858 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1859
1860 if (NULL == pProposed)
1861 pProposed = ChartData->OpenStackChartConditional(
1862 m_pCurrentStack, start_index, search_direction,
1863 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1864
1865 bNewChart = true;
1866 }
1867 } // bCanvasChartAutoOpen
1868
1869 else
1870 pProposed = NULL;
1871
1872 // If no go, then
1873 // Open a Dummy Chart
1874 if (NULL == pProposed) {
1875 if (NULL == pDummyChart) {
1876 pDummyChart = new ChartDummy;
1877 bNewChart = true;
1878 }
1879
1880 if (pLast_Ch)
1881 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1882
1883 pProposed = pDummyChart;
1884 }
1885
1886 // Arriving here, pProposed points to an opened chart, or NULL.
1887 if (m_singleChart) m_singleChart->Deactivate();
1888 m_singleChart = pProposed;
1889
1890 if (m_singleChart) {
1891 m_singleChart->Activate();
1892 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1893 m_pCurrentStack, m_singleChart->GetFullPath());
1894 }
1895 } // need new chart
1896
1897 // Arriving here, m_singleChart is opened and OK, or NULL
1898 if (NULL != m_singleChart) {
1899 // Setup the view using the current scale
1900 double set_scale = GetVPScale();
1901
1902 double proposed_scale_onscreen;
1903
1904 if (m_bFollow) { // autoset the scale only if in autofollow
1905 double new_scale_ppm =
1906 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1907 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1908 } else
1909 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1910
1911 // This logic will bring a new chart onscreen at roughly twice the true
1912 // paper scale equivalent. Note that first chart opened on application
1913 // startup (bOpenSpecified = true) will open at the config saved scale
1914 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1915 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1916 double equivalent_vp_scale =
1917 GetCanvasScaleFactor() / proposed_scale_onscreen;
1918 double new_scale_ppm =
1919 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1920 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1921 }
1922
1923 if (m_bFollow) { // bounds-check the scale only if in autofollow
1924 proposed_scale_onscreen =
1925 wxMin(proposed_scale_onscreen,
1926 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1927 GetCanvasWidth()));
1928 proposed_scale_onscreen =
1929 wxMax(proposed_scale_onscreen,
1930 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1932 }
1933
1934 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1935
1936 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1937 m_singleChart->GetChartSkew() * PI / 180.,
1938 GetVPRotation());
1939 }
1940 } // new stack
1941
1942 else // No change in Chart Stack
1943 {
1944 double s = GetVPScale();
1945 if ((m_bFollow) && m_singleChart)
1946 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1947 m_singleChart->GetChartSkew() * PI / 180.,
1948 GetVPRotation());
1949 }
1950
1951update_finish:
1952
1953 // TODO
1954 // if( bNewPiano ) UpdateControlBar();
1955
1956 m_bFirstAuto = false; // Auto open on program start
1957
1958 // If we need a Refresh(), do it here...
1959 // But don't duplicate a Refresh() done by SetViewPoint()
1960 if (bNewChart && !bNewView) Refresh(false);
1961
1962#ifdef ocpnUSE_GL
1963 // If a new chart, need to invalidate gl viewport for refresh
1964 // so the fbo gets flushed
1965 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1966#endif
1967
1968 return bNewChart | bNewView;
1969}
1970
1971void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1972 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1973
1974 SetQuiltRefChart(db_index);
1975 if (ChartData) {
1976 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1977 if (pc) {
1978 if (b_autoscale) {
1979 double best_scale_ppm = GetBestVPScale(pc);
1980 SetVPScale(best_scale_ppm);
1981 }
1982 } else
1983 SetQuiltRefChart(-1);
1984 } else
1985 SetQuiltRefChart(-1);
1986}
1987
1988void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1989 std::vector<int> piano_chart_index_array =
1990 GetQuiltExtendedStackdbIndexArray();
1991 int current_db_index = piano_chart_index_array[selected_index];
1992
1993 SelectQuiltRefdbChart(current_db_index);
1994}
1995
1996double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1997 if (pchart) {
1998 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1999
2000 if ((g_bPreserveScaleOnX) ||
2001 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
2002 double new_scale_ppm = GetVPScale();
2003 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2004 } else {
2005 // This logic will bring the new chart onscreen at roughly twice the true
2006 // paper scale equivalent.
2007 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2008 double equivalent_vp_scale =
2009 GetCanvasScaleFactor() / proposed_scale_onscreen;
2010 double new_scale_ppm =
2011 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2012 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2013 }
2014
2015 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2016 // set. Otherwise, we get severe performance problems on all platforms
2017
2018 double max_underzoom_multiplier = 2.0;
2019 if (GetVP().b_quilt) {
2020 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2021 pchart->GetChartType(),
2022 pchart->GetChartFamily());
2023 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2024 }
2025
2026 proposed_scale_onscreen = wxMin(
2027 proposed_scale_onscreen,
2028 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2029 max_underzoom_multiplier);
2030
2031 // And, do not allow excessive overzoom either
2032 proposed_scale_onscreen =
2033 wxMax(proposed_scale_onscreen,
2034 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2035
2036 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2037 } else
2038 return 1.0;
2039}
2040
2041void ChartCanvas::SetupCanvasQuiltMode() {
2042 if (GetQuiltMode()) // going to quilt mode
2043 {
2044 ChartData->LockCache();
2045
2046 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2047
2048 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2049
2050 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2051 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2052 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2053 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2054
2055 m_Piano->SetRoundedRectangles(true);
2056
2057 // Select the proper Ref chart
2058 int target_new_dbindex = -1;
2059 if (m_pCurrentStack) {
2060 target_new_dbindex =
2061 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2062
2063 if (-1 != target_new_dbindex) {
2064 if (!IsChartQuiltableRef(target_new_dbindex)) {
2065 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2066 int type = ChartData->GetDBChartType(target_new_dbindex);
2067
2068 // walk the stack up looking for a satisfactory chart
2069 int stack_index = m_pCurrentStack->CurrentStackEntry;
2070
2071 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2072 (stack_index >= 0)) {
2073 int proj_tent = ChartData->GetDBChartProj(
2074 m_pCurrentStack->GetDBIndex(stack_index));
2075 int type_tent = ChartData->GetDBChartType(
2076 m_pCurrentStack->GetDBIndex(stack_index));
2077
2078 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2079 if ((proj == proj_tent) && (type_tent == type)) {
2080 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2081 break;
2082 }
2083 }
2084 stack_index++;
2085 }
2086 }
2087 }
2088 }
2089
2090 if (IsChartQuiltableRef(target_new_dbindex))
2091 SelectQuiltRefdbChart(target_new_dbindex,
2092 false); // Try not to allow a scale change
2093 else { // fall back to last selected no-quilt chart as new reference
2094 int stack_index = m_pCurrentStack->CurrentStackEntry;
2095 SelectQuiltRefdbChart(m_pCurrentStack->GetDBIndex(stack_index), false);
2096 }
2097
2098 m_singleChart = NULL; // Bye....
2099
2100 // Re-qualify the quilt reference chart selection
2101 AdjustQuiltRefChart();
2102
2103 // Restore projection type saved on last quilt mode toggle
2104 // TODO
2105 // if(g_sticky_projection != -1)
2106 // GetVP().SetProjectionType(g_sticky_projection);
2107 // else
2108 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2109 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2110
2111 } else // going to SC Mode
2112 {
2113 std::vector<int> empty_array;
2114 m_Piano->SetActiveKeyArray(empty_array);
2115 m_Piano->SetNoshowIndexArray(empty_array);
2116 m_Piano->SetEclipsedIndexArray(empty_array);
2117
2118 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2119 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2120 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2121 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2122 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2123
2124 m_Piano->SetRoundedRectangles(false);
2125 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2126 }
2127
2128 // When shifting from quilt to single chart mode, select the "best" single
2129 // chart to show
2130 if (!GetQuiltMode()) {
2131 if (ChartData && ChartData->IsValid()) {
2132 UnlockQuilt();
2133
2134 double tLat, tLon;
2135 if (m_bFollow == true) {
2136 tLat = gLat;
2137 tLon = gLon;
2138 } else {
2139 tLat = m_vLat;
2140 tLon = m_vLon;
2141 }
2142
2143 if (!m_singleChart) {
2144 // First choice is to adopt the outgoing quilt reference chart
2145 if (GetQuiltReferenceChartIndex() >= 0) {
2146 m_singleChart = ChartData->OpenChartFromDB(
2147 GetQuiltReferenceChartIndex(), FULL_INIT);
2148 }
2149 // Second choice is to use any "no-quilt restore index", if available
2150 else if (m_restore_dbindex >= 0) {
2151 m_singleChart =
2152 ChartData->OpenChartFromDB(m_restore_dbindex, FULL_INIT);
2153 }
2154 // Final choice it to pick a suitable chart based on current stack.
2155 else {
2156 // Build a temporary chart stack based on tLat, tLon
2157 ChartStack TempStack;
2158 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2159 m_groupIndex);
2160
2161 // Iterate over the quilt charts actually shown, looking for the
2162 // largest scale chart that will be in the new chartstack.... This
2163 // will (almost?) always be the reference chart....
2164
2165 ChartBase *Candidate_Chart = NULL;
2166 int cur_max_scale = (int)1e8;
2167
2168 ChartBase *pChart = GetFirstQuiltChart();
2169 while (pChart) {
2170 // Is this pChart in new stack?
2171 int tEntry =
2172 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2173 if (tEntry != -1) {
2174 if (pChart->GetNativeScale() < cur_max_scale) {
2175 Candidate_Chart = pChart;
2176 cur_max_scale = pChart->GetNativeScale();
2177 }
2178 }
2179 pChart = GetNextQuiltChart();
2180 }
2181
2182 m_singleChart = Candidate_Chart;
2183
2184 // If the quilt is empty, there is no "best" chart.
2185 // So, open the smallest scale chart in the current stack
2186 if (NULL == m_singleChart) {
2187 m_singleChart = ChartData->OpenStackChartConditional(
2188 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2189 CHART_FAMILY_DONTCARE);
2190 }
2191 }
2192 }
2193 // Invalidate all the charts in the quilt,
2194 // as any cached data may be region based and not have fullscreen coverage
2195 InvalidateAllQuiltPatchs();
2196
2197 if (m_singleChart) {
2198 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2199 std::vector<int> one_array;
2200 one_array.push_back(dbi);
2201 m_Piano->SetActiveKeyArray(one_array);
2202 }
2203
2204 if (m_singleChart) {
2205 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2206 }
2207 }
2208 }
2209 // Invalidate the current stack so that it will be rebuilt on next tick
2210 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2211 SetVPScale(GetVPScale() * 1.0001);
2212}
2213
2214bool ChartCanvas::IsTempMenuBarEnabled() {
2215#ifdef __WXMSW__
2216 int major;
2217 wxGetOsVersion(&major);
2218 return (major >
2219 5); // For Windows, function is only available on Vista and above
2220#else
2221 return true;
2222#endif
2223}
2224
2225double ChartCanvas::GetCanvasRangeMeters() {
2226 int width, height;
2227 GetSize(&width, &height);
2228 int minDimension = wxMin(width, height);
2229
2230 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2231 range *= cos(GetVP().clat * PI / 180.);
2232 return range;
2233}
2234
2235void ChartCanvas::SetCanvasRangeMeters(double range) {
2236 int width, height;
2237 GetSize(&width, &height);
2238 int minDimension = wxMin(width, height);
2239
2240 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2241 SetVPScale(scale_ppm / 2);
2242}
2243
2244bool ChartCanvas::SetUserOwnship() {
2245 // Look for user defined ownship image
2246 // This may be found in the shared data location along with other user
2247 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2248 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2249 double factor_dusk = 0.5;
2250 double factor_night = 0.25;
2251
2252 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2253 m_pos_image_user_day = new wxImage;
2254 *m_pos_image_user_day = pbmp->ConvertToImage();
2255 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2256
2257 int gimg_width = m_pos_image_user_day->GetWidth();
2258 int gimg_height = m_pos_image_user_day->GetHeight();
2259
2260 // Make dusk and night images
2261 m_pos_image_user_dusk = new wxImage;
2262 m_pos_image_user_night = new wxImage;
2263
2264 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2265 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2266
2267 for (int iy = 0; iy < gimg_height; iy++) {
2268 for (int ix = 0; ix < gimg_width; ix++) {
2269 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2270 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2271 m_pos_image_user_day->GetGreen(ix, iy),
2272 m_pos_image_user_day->GetBlue(ix, iy));
2273 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2274 hsv.value = hsv.value * factor_dusk;
2275 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2276 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2277 nrgb.blue);
2278
2279 hsv = wxImage::RGBtoHSV(rgb);
2280 hsv.value = hsv.value * factor_night;
2281 nrgb = wxImage::HSVtoRGB(hsv);
2282 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2283 nrgb.blue);
2284 }
2285 }
2286 }
2287
2288 // Make some alternate greyed out day/dusk/night images
2289 m_pos_image_user_grey_day = new wxImage;
2290 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2291
2292 m_pos_image_user_grey_dusk = new wxImage;
2293 m_pos_image_user_grey_night = new wxImage;
2294
2295 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2296 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2297
2298 for (int iy = 0; iy < gimg_height; iy++) {
2299 for (int ix = 0; ix < gimg_width; ix++) {
2300 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2301 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2302 m_pos_image_user_grey_day->GetGreen(ix, iy),
2303 m_pos_image_user_grey_day->GetBlue(ix, iy));
2304 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2305 hsv.value = hsv.value * factor_dusk;
2306 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2307 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2308 nrgb.blue);
2309
2310 hsv = wxImage::RGBtoHSV(rgb);
2311 hsv.value = hsv.value * factor_night;
2312 nrgb = wxImage::HSVtoRGB(hsv);
2313 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2314 nrgb.blue);
2315 }
2316 }
2317 }
2318
2319 // Make a yellow image for rendering under low accuracy chart conditions
2320 m_pos_image_user_yellow_day = new wxImage;
2321 m_pos_image_user_yellow_dusk = new wxImage;
2322 m_pos_image_user_yellow_night = new wxImage;
2323
2324 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2325 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2326 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2327
2328 for (int iy = 0; iy < gimg_height; iy++) {
2329 for (int ix = 0; ix < gimg_width; ix++) {
2330 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2331 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2332 m_pos_image_user_grey_day->GetGreen(ix, iy),
2333 m_pos_image_user_grey_day->GetBlue(ix, iy));
2334
2335 // Simply remove all "blue" from the greyscaled image...
2336 // so, what is not black becomes yellow.
2337 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2338 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2339 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2340
2341 hsv = wxImage::RGBtoHSV(rgb);
2342 hsv.value = hsv.value * factor_dusk;
2343 nrgb = wxImage::HSVtoRGB(hsv);
2344 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2345
2346 hsv = wxImage::RGBtoHSV(rgb);
2347 hsv.value = hsv.value * factor_night;
2348 nrgb = wxImage::HSVtoRGB(hsv);
2349 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2350 0);
2351 }
2352 }
2353 }
2354
2355 return true;
2356 } else
2357 return false;
2358}
2359
2361 m_display_size_mm = size;
2362
2363 // int sx, sy;
2364 // wxDisplaySize( &sx, &sy );
2365
2366 // Calculate logical pixels per mm for later reference.
2367 wxSize sd = g_Platform->getDisplaySize();
2368 double horizontal = sd.x;
2369 // Set DPI (Win) scale factor
2370 g_scaler = g_Platform->GetDisplayDIPMult(this);
2371
2372 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2373 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2374
2375 if (ps52plib) {
2376 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2377 ps52plib->SetPPMM(m_pix_per_mm);
2378 }
2379
2380 wxString msg;
2381 msg.Printf(
2382 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2383 "%d:%d ",
2384 m_display_size_mm, sd.x, sd.y);
2385 wxLogDebug(msg);
2386
2387 int ssx, ssy;
2388 ssx = g_monitor_info[g_current_monitor].width;
2389 ssy = g_monitor_info[g_current_monitor].height;
2390 msg.Printf("monitor size: %d %d", ssx, ssy);
2391 wxLogDebug(msg);
2392
2393 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2394}
2395#if 0
2396void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2397{
2398 wxString msg(event.m_string.c_str(), wxConvUTF8);
2399 // if cpus are removed between runs
2400 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2401 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2402 }
2403
2404 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2405 {
2406 compress_msg_array.RemoveAt(event.thread);
2407 compress_msg_array.Insert( msg, event.thread);
2408 }
2409 else
2410 compress_msg_array.Add(msg);
2411
2412
2413 wxString combined_msg;
2414 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2415 combined_msg += compress_msg_array[i];
2416 combined_msg += "\n";
2417 }
2418
2419 bool skip = false;
2420 pprog->Update(pprog_count, combined_msg, &skip );
2421 pprog->SetSize(pprog_size);
2422 if(skip)
2423 b_skipout = skip;
2424}
2425#endif
2426void ChartCanvas::InvalidateGL() {
2427 if (!m_glcc) return;
2428#ifdef ocpnUSE_GL
2429 if (g_bopengl) m_glcc->Invalidate();
2430#endif
2431 if (m_Compass) m_Compass->UpdateStatus(true);
2432}
2433
2434int ChartCanvas::GetCanvasChartNativeScale() {
2435 int ret = 1;
2436 if (!VPoint.b_quilt) {
2437 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2438 } else
2439 ret = (int)m_pQuilt->GetRefNativeScale();
2440
2441 return ret;
2442}
2443
2444ChartBase *ChartCanvas::GetChartAtCursor() {
2445 ChartBase *target_chart;
2446 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2447 target_chart = m_singleChart;
2448 else if (VPoint.b_quilt)
2449 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2450 else
2451 target_chart = NULL;
2452 return target_chart;
2453}
2454
2455ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2456 ChartBase *target_chart;
2457 if (VPoint.b_quilt)
2458 target_chart =
2459 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2460 else
2461 target_chart = NULL;
2462 return target_chart;
2463}
2464
2465int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2466 int new_dbIndex = -1;
2467 if (!VPoint.b_quilt) {
2468 if (m_pCurrentStack) {
2469 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2470 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2471 if (sc >= scale) {
2472 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2473 break;
2474 }
2475 }
2476 }
2477 } else {
2478 // Using the current quilt, select a useable reference chart
2479 // Said chart will be in the extended (possibly full-screen) stack,
2480 // And will have a scale equal to or just greater than the stipulated
2481 // value, and will belong to the same chart family (RNC/ENC)
2482 // If no family match is found, then family requirement is ignored.
2483 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2484 if (im > 0) {
2485 // Find closest using scale and family
2486 for (unsigned int is = 0; is < im; is++) {
2487 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2488 m_pQuilt->GetExtendedStackIndexArray()[is]);
2489 if ((m.Scale_ge(scale)) &&
2490 (m_pQuilt->GetRefFamily() == m.GetChartFamily())) {
2491 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2492 break;
2493 }
2494 }
2495 // if not found, likely due to Family requirement
2496 // That is, there is no matching family chart in the StackIndexArray.
2497 // Try again, without consideration of family.
2498 if (new_dbIndex < 0) {
2499 for (unsigned int is = 0; is < im; is++) {
2500 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2501 m_pQuilt->GetExtendedStackIndexArray()[is]);
2502 if (m.Scale_ge(scale)) {
2503 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2504 break;
2505 }
2506 }
2507 }
2508 }
2509 }
2510
2511 return new_dbIndex;
2512}
2513
2514void ChartCanvas::EnablePaint(bool b_enable) {
2515 m_b_paint_enable = b_enable;
2516#ifdef ocpnUSE_GL
2517 if (m_glcc) m_glcc->EnablePaint(b_enable);
2518#endif
2519}
2520
2521bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2522
2523void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2524
2525std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2526 return m_pQuilt->GetQuiltIndexArray();
2527 ;
2528}
2529
2530void ChartCanvas::SetQuiltMode(bool b_quilt) {
2531 VPoint.b_quilt = b_quilt;
2532 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2533}
2534
2535bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2536
2537int ChartCanvas::GetQuiltReferenceChartIndex() {
2538 return m_pQuilt->GetRefChartdbIndex();
2539}
2540
2541void ChartCanvas::InvalidateAllQuiltPatchs() {
2542 m_pQuilt->InvalidateAllQuiltPatchs();
2543}
2544
2545ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2546 return m_pQuilt->GetLargestScaleChart();
2547}
2548
2549ChartBase *ChartCanvas::GetFirstQuiltChart() {
2550 return m_pQuilt->GetFirstChart();
2551}
2552
2553ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2554
2555int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2556
2557void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2558 m_pQuilt->SetHiliteIndex(dbIndex);
2559}
2560
2561void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2562 m_pQuilt->SetHiliteIndexArray(hilite_array);
2563}
2564
2565void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2566 m_pQuilt->ClearHiliteIndexArray();
2567}
2568
2569std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2570 bool flag2) {
2571 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2572}
2573
2574int ChartCanvas::GetQuiltRefChartdbIndex() {
2575 return m_pQuilt->GetRefChartdbIndex();
2576}
2577
2578std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2579 return m_pQuilt->GetExtendedStackIndexArray();
2580}
2581
2582std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2583 return m_pQuilt->GetFullscreenIndexArray();
2584}
2585
2586std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2587 return m_pQuilt->GetEclipsedStackIndexArray();
2588}
2589
2590void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2591
2592double ChartCanvas::GetQuiltMaxErrorFactor() {
2593 return m_pQuilt->GetMaxErrorFactor();
2594}
2595
2596bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2597 return m_pQuilt->IsChartQuiltableRef(db_index);
2598}
2599
2600bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2601 double chartMaxScale =
2602 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2603 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2604}
2605
2606void ChartCanvas::StartMeasureRoute() {
2607 if (!m_routeState) { // no measure tool if currently creating route
2608 if (m_bMeasure_Active) {
2609 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2610 m_pMeasureRoute = NULL;
2611 }
2612
2613 m_bMeasure_Active = true;
2614 m_nMeasureState = 1;
2615 m_bDrawingRoute = false;
2616
2617 SetCursor(*pCursorPencil);
2618 Refresh();
2619 }
2620}
2621
2622void ChartCanvas::CancelMeasureRoute() {
2623 m_bMeasure_Active = false;
2624 m_nMeasureState = 0;
2625 m_bDrawingRoute = false;
2626
2627 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2628 m_pMeasureRoute = NULL;
2629
2630 SetCursor(*pCursorArrow);
2631}
2632
2633ViewPort &ChartCanvas::GetVP() { return VPoint; }
2634
2635void ChartCanvas::SetVP(ViewPort &vp) {
2636 VPoint = vp;
2637 VPoint.SetPixelScale(m_displayScale);
2638}
2639
2640// void ChartCanvas::SetFocus()
2641// {
2642// printf("set %d\n", m_canvasIndex);
2643// //wxWindow:SetFocus();
2644// }
2645
2646void ChartCanvas::TriggerDeferredFocus() {
2647 // #if defined(__WXGTK__) || defined(__WXOSX__)
2648
2649 m_deferredFocusTimer.Start(20, true);
2650
2651#if defined(__WXGTK__) || defined(__WXOSX__)
2652 top_frame::Get()->Raise();
2653#endif
2654
2655 // top_frame::Get()->Raise();
2656 // #else
2657 // SetFocus();
2658 // Refresh(true);
2659 // #endif
2660}
2661
2662void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2663 SetFocus();
2664 Refresh(true);
2665}
2666
2667void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2668 if (SendKeyEventToPlugins(event))
2669 return; // PlugIn did something, and does not want the canvas to do
2670 // anything else
2671
2672 int key_char = event.GetKeyCode();
2673 switch (key_char) {
2674 case '?':
2675 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2676 break;
2677 case '+':
2678 ZoomCanvas(g_plus_minus_zoom_factor, false);
2679 break;
2680 case '-':
2681 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2682 break;
2683 default:
2684 break;
2685 }
2686 if (g_benable_rotate) {
2687 switch (key_char) {
2688 case ']':
2689 RotateCanvas(1);
2690 Refresh();
2691 break;
2692
2693 case '[':
2694 RotateCanvas(-1);
2695 Refresh();
2696 break;
2697
2698 case '\\':
2699 DoRotateCanvas(0);
2700 break;
2701 }
2702 }
2703
2704 event.Skip();
2705}
2706
2707void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2708 if (SendKeyEventToPlugins(event))
2709 return; // PlugIn did something, and does not want the canvas to do
2710 // anything else
2711
2712 bool b_handled = false;
2713
2714 m_modkeys = event.GetModifiers();
2715
2716 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2717
2718#ifdef OCPN_ALT_MENUBAR
2719#ifndef __WXOSX__
2720 // If the permanent menubar is disabled, we show it temporarily when Alt is
2721 // pressed or when Alt + a letter is presssed (for the top-menu-level
2722 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2723 // some special cases.
2724 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2725 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2726 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2727 if (!g_bTempShowMenuBar) {
2728 g_bTempShowMenuBar = true;
2729 top_frame::Get()->ApplyGlobalSettings(false);
2730 }
2731 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2732 event.Skip();
2733 return;
2734 }
2735 // If another key is pressed while Alt is down, do NOT toggle the menus when
2736 // Alt is released
2737 if (event.GetKeyCode() != WXK_ALT) {
2738 m_bMayToggleMenuBar = false;
2739 }
2740 }
2741#endif
2742#endif
2743
2744 // HOTKEYS
2745 switch (event.GetKeyCode()) {
2746 case WXK_TAB:
2747 // parent_frame->SwitchKBFocus( this );
2748 break;
2749
2750 case WXK_MENU:
2751 int x, y;
2752 event.GetPosition(&x, &y);
2753 m_FinishRouteOnKillFocus = false;
2754 CallPopupMenu(x, y);
2755 m_FinishRouteOnKillFocus = true;
2756 break;
2757
2758 case WXK_ALT:
2759 m_modkeys |= wxMOD_ALT;
2760 break;
2761
2762 case WXK_CONTROL:
2763 m_modkeys |= wxMOD_CONTROL;
2764 break;
2765
2766#ifdef __WXOSX__
2767 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2768 case WXK_RAW_CONTROL:
2769 m_modkeys |= wxMOD_RAW_CONTROL;
2770 break;
2771#endif
2772
2773 case WXK_LEFT:
2774 if (m_modkeys == wxMOD_CONTROL)
2775 top_frame::Get()->DoStackDown(this);
2776 else if (g_bsmoothpanzoom) {
2777 StartTimedMovement();
2778 m_panx = -1;
2779 } else {
2780 PanCanvas(-panspeed, 0);
2781 }
2782 b_handled = true;
2783 break;
2784
2785 case WXK_UP:
2786 if (g_bsmoothpanzoom) {
2787 StartTimedMovement();
2788 m_pany = -1;
2789 } else
2790 PanCanvas(0, -panspeed);
2791 b_handled = true;
2792 break;
2793
2794 case WXK_RIGHT:
2795 if (m_modkeys == wxMOD_CONTROL)
2796 top_frame::Get()->DoStackUp(this);
2797 else if (g_bsmoothpanzoom) {
2798 StartTimedMovement();
2799 m_panx = 1;
2800 } else
2801 PanCanvas(panspeed, 0);
2802 b_handled = true;
2803
2804 break;
2805
2806 case WXK_DOWN:
2807 if (g_bsmoothpanzoom) {
2808 StartTimedMovement();
2809 m_pany = 1;
2810 } else
2811 PanCanvas(0, panspeed);
2812 b_handled = true;
2813 break;
2814
2815 case WXK_F2: {
2816 if (event.ShiftDown()) {
2817 double scale = GetVP().view_scale_ppm;
2818 ChartFamilyEnum target_family = CHART_FAMILY_RASTER;
2819
2820 std::shared_ptr<HostApi> host_api;
2821 host_api = GetHostApi();
2822 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2823
2824 if (api_121)
2825 api_121->SelectChartFamily(m_canvasIndex,
2826 (ChartFamilyEnumPI)target_family);
2827 } else
2828 TogglebFollow();
2829 break;
2830 }
2831 case WXK_F3: {
2832 if (event.ShiftDown()) {
2833 double scale = GetVP().view_scale_ppm;
2834 ChartFamilyEnum target_family = CHART_FAMILY_VECTOR;
2835
2836 std::shared_ptr<HostApi> host_api;
2837 host_api = GetHostApi();
2838 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2839
2840 if (api_121)
2841 api_121->SelectChartFamily(m_canvasIndex,
2842 (ChartFamilyEnumPI)target_family);
2843 } else {
2844 SetShowENCText(!GetShowENCText());
2845 Refresh(true);
2846 InvalidateGL();
2847 }
2848 break;
2849 }
2850 case WXK_F4:
2851 if (!m_bMeasure_Active) {
2852 if (event.ShiftDown())
2853 m_bMeasure_DistCircle = true;
2854 else
2855 m_bMeasure_DistCircle = false;
2856
2857 StartMeasureRoute();
2858 } else {
2859 CancelMeasureRoute();
2860
2861 SetCursor(*pCursorArrow);
2862
2863 // SurfaceToolbar();
2864 InvalidateGL();
2865 Refresh(false);
2866 }
2867
2868 break;
2869
2870 case WXK_F5:
2871 top_frame::Get()->ToggleColorScheme();
2872 top_frame::Get()->Raise();
2873 TriggerDeferredFocus();
2874 break;
2875
2876 case WXK_F6: {
2877 int mod = m_modkeys & wxMOD_SHIFT;
2878 if (mod != m_brightmod) {
2879 m_brightmod = mod;
2880 m_bbrightdir = !m_bbrightdir;
2881 }
2882
2883 if (!m_bbrightdir) {
2884 g_nbrightness -= 10;
2885 if (g_nbrightness <= MIN_BRIGHT) {
2886 g_nbrightness = MIN_BRIGHT;
2887 m_bbrightdir = true;
2888 }
2889 } else {
2890 g_nbrightness += 10;
2891 if (g_nbrightness >= MAX_BRIGHT) {
2892 g_nbrightness = MAX_BRIGHT;
2893 m_bbrightdir = false;
2894 }
2895 }
2896
2897 SetScreenBrightness(g_nbrightness);
2898 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2899
2900 SetFocus(); // just in case the external program steals it....
2901 top_frame::Get()->Raise(); // And reactivate the application main
2902
2903 break;
2904 }
2905
2906 case WXK_F7:
2907 top_frame::Get()->DoStackDown(this);
2908 break;
2909
2910 case WXK_F8:
2911 top_frame::Get()->DoStackUp(this);
2912 break;
2913
2914#ifndef __WXOSX__
2915 case WXK_F9: {
2916 ToggleCanvasQuiltMode();
2917 break;
2918 }
2919#endif
2920
2921 case WXK_F11:
2922 top_frame::Get()->ToggleFullScreen();
2923 b_handled = true;
2924 break;
2925
2926 case WXK_F12: {
2927 if (m_modkeys == wxMOD_ALT) {
2928 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2929 } else {
2930 ToggleChartOutlines();
2931 }
2932 break;
2933 }
2934
2935 case WXK_PAUSE: // Drop MOB
2936 top_frame::Get()->ActivateMOB();
2937 break;
2938
2939 // NUMERIC PAD
2940 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2941 case WXK_PAGEUP: {
2942 ZoomCanvas(g_plus_minus_zoom_factor, false);
2943 break;
2944 }
2945 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2946 case WXK_PAGEDOWN: {
2947 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2948 break;
2949 }
2950 case WXK_DELETE:
2951 case WXK_BACK:
2952 if (m_bMeasure_Active) {
2953 if (m_nMeasureState > 2) {
2954 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2955 m_pMeasureRoute->m_lastMousePointIndex =
2956 m_pMeasureRoute->GetnPoints();
2957 m_nMeasureState--;
2958 top_frame::Get()->RefreshAllCanvas();
2959 } else {
2960 CancelMeasureRoute();
2961 StartMeasureRoute();
2962 }
2963 }
2964 break;
2965 default:
2966 break;
2967 }
2968
2969 if (event.GetKeyCode() < 128) // ascii
2970 {
2971 int key_char = event.GetKeyCode();
2972
2973 // Handle both QWERTY and AZERTY keyboard separately for a few control
2974 // codes
2975 if (!g_b_assume_azerty) {
2976#ifdef __WXMAC__
2977 if (g_benable_rotate) {
2978 switch (key_char) {
2979 // On other platforms these are handled in OnKeyChar, which
2980 // (apparently) works better in some locales. On OS X it is better
2981 // to handle them here, since pressing Alt (which should change the
2982 // rotation speed) changes the key char and so prevents the keys
2983 // from working.
2984 case ']':
2985 RotateCanvas(1);
2986 b_handled = true;
2987 break;
2988
2989 case '[':
2990 RotateCanvas(-1);
2991 b_handled = true;
2992 break;
2993
2994 case '\\':
2995 DoRotateCanvas(0);
2996 b_handled = true;
2997 break;
2998 }
2999 }
3000#endif
3001 } else { // AZERTY
3002 switch (key_char) {
3003 case 43:
3004 ZoomCanvas(g_plus_minus_zoom_factor, false);
3005 break;
3006
3007 case 54: // '-' alpha/num pad
3008 // case 56: // '_' alpha/num pad
3009 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
3010 break;
3011 }
3012 }
3013
3014#ifdef __WXOSX__
3015 // Ctrl+Cmd+F toggles fullscreen on macOS
3016 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
3017 m_modkeys & wxMOD_RAW_CONTROL) {
3018 top_frame::Get()->ToggleFullScreen();
3019 return;
3020 }
3021#endif
3022
3023 if (event.ControlDown()) key_char -= 64;
3024
3025 if (key_char >= '0' && key_char <= '9')
3026 SetGroupIndex(key_char - '0');
3027 else
3028
3029 switch (key_char) {
3030 case 'A':
3031 SetShowENCAnchor(!GetShowENCAnchor());
3032 ReloadVP();
3033
3034 break;
3035
3036 case 'C':
3037 top_frame::Get()->ToggleColorScheme();
3038 break;
3039
3040 case 'D': {
3041 int x, y;
3042 event.GetPosition(&x, &y);
3043 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3044 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3045 // First find out what kind of chart is being used
3046 if (!pPopupDetailSlider) {
3047 if (VPoint.b_quilt) {
3048 if (m_pQuilt) {
3049 if (m_pQuilt->GetChartAtPix(
3050 VPoint,
3051 wxPoint(
3052 x, y))) // = null if no chart loaded for this point
3053 {
3054 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3055 ->GetChartType();
3056 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3057 ->GetChartFamily();
3058 }
3059 }
3060 } else {
3061 if (m_singleChart) {
3062 ChartType = m_singleChart->GetChartType();
3063 ChartFam = m_singleChart->GetChartFamily();
3064 }
3065 }
3066 // If a charttype is found show the popupslider
3067 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3068 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3070 this, -1, ChartType, ChartFam,
3071 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3072 wxDefaultSize, wxSIMPLE_BORDER, "");
3074 }
3075 } else //( !pPopupDetailSlider ) close popupslider
3076 {
3078 pPopupDetailSlider = NULL;
3079 }
3080 break;
3081 }
3082
3083 case 'E':
3084 m_nmea_log->Show();
3085 m_nmea_log->Raise();
3086 break;
3087
3088 case 'L':
3089 SetShowENCLights(!GetShowENCLights());
3090 ReloadVP();
3091
3092 break;
3093
3094 case 'M':
3095 if (event.ShiftDown())
3096 m_bMeasure_DistCircle = true;
3097 else
3098 m_bMeasure_DistCircle = false;
3099
3100 StartMeasureRoute();
3101 break;
3102
3103 case 'N':
3104 if (g_bInlandEcdis && ps52plib) {
3105 SetENCDisplayCategory((_DisCat)STANDARD);
3106 }
3107 break;
3108
3109 case 'O':
3110 ToggleChartOutlines();
3111 break;
3112
3113 case 'Q':
3114 ToggleCanvasQuiltMode();
3115 break;
3116
3117 case 'P':
3118 top_frame::Get()->ToggleTestPause();
3119 break;
3120 case 'R':
3121 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3122 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3123 g_iNavAidRadarRingsNumberVisible = 1;
3124 else if (!g_bNavAidRadarRingsShown &&
3125 g_iNavAidRadarRingsNumberVisible == 1)
3126 g_iNavAidRadarRingsNumberVisible = 0;
3127 break;
3128 case 'S':
3129 SetShowENCDepth(!m_encShowDepth);
3130 ReloadVP();
3131 break;
3132
3133 case 'T':
3134 SetShowENCText(!GetShowENCText());
3135 ReloadVP();
3136 break;
3137
3138 case 'U':
3139 SetShowENCDataQual(!GetShowENCDataQual());
3140 ReloadVP();
3141 break;
3142
3143 case 'V':
3144 m_bShowNavobjects = !m_bShowNavobjects;
3145 Refresh(true);
3146 break;
3147
3148 case 'W': // W Toggle CPA alarm
3149 ToggleCPAWarn();
3150
3151 break;
3152
3153 case 1: // Ctrl A
3154 TogglebFollow();
3155
3156 break;
3157
3158 case 2: // Ctrl B
3159 if (g_bShowMenuBar == false) top_frame::Get()->ToggleChartBar(this);
3160 break;
3161
3162 case 13: // Ctrl M // Drop Marker at cursor
3163 {
3164 if (event.ControlDown()) top_frame::Get()->DropMarker(false);
3165 break;
3166 }
3167
3168 case 14: // Ctrl N - Activate next waypoint in a route
3169 {
3170 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3171 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3172 if ((indexActive + 1) <= r->GetnPoints()) {
3174 InvalidateGL();
3175 Refresh(false);
3176 }
3177 }
3178 break;
3179 }
3180
3181 case 15: // Ctrl O - Drop Marker at boat's position
3182 {
3183 if (!g_bShowMenuBar) top_frame::Get()->DropMarker(true);
3184 break;
3185 }
3186
3187 case 32: // Special needs use space bar
3188 {
3189 if (g_bSpaceDropMark) top_frame::Get()->DropMarker(true);
3190 break;
3191 }
3192
3193 case -32: // Ctrl Space // Drop MOB
3194 {
3195 if (m_modkeys == wxMOD_CONTROL) top_frame::Get()->ActivateMOB();
3196
3197 break;
3198 }
3199
3200 case -20: // Ctrl ,
3201 {
3202 top_frame::Get()->DoSettings();
3203 break;
3204 }
3205 case 17: // Ctrl Q
3206 parent_frame->Close();
3207 return;
3208
3209 case 18: // Ctrl R
3210 StartRoute();
3211 return;
3212
3213 case 20: // Ctrl T
3214 if (NULL == pGoToPositionDialog) // There is one global instance of
3215 // the Go To Position Dialog
3217 pGoToPositionDialog->SetCanvas(this);
3218 pGoToPositionDialog->Show();
3219 break;
3220
3221 case 25: // Ctrl Y
3222 if (undo->AnythingToRedo()) {
3223 undo->RedoNextAction();
3224 InvalidateGL();
3225 Refresh(false);
3226 }
3227 break;
3228
3229 case 26:
3230 if (event.ShiftDown()) { // Shift-Ctrl-Z
3231 if (undo->AnythingToRedo()) {
3232 undo->RedoNextAction();
3233 InvalidateGL();
3234 Refresh(false);
3235 }
3236 } else { // Ctrl Z
3237 if (undo->AnythingToUndo()) {
3238 undo->UndoLastAction();
3239 InvalidateGL();
3240 Refresh(false);
3241 }
3242 }
3243 break;
3244
3245 case 27:
3246 // Generic break
3247 if (m_bMeasure_Active) {
3248 CancelMeasureRoute();
3249
3250 SetCursor(*pCursorArrow);
3251
3252 // SurfaceToolbar();
3253 top_frame::Get()->RefreshAllCanvas();
3254 }
3255
3256 if (m_routeState) // creating route?
3257 {
3258 FinishRoute();
3259 // SurfaceToolbar();
3260 InvalidateGL();
3261 Refresh(false);
3262 }
3263
3264 break;
3265
3266 case 7: // Ctrl G
3267 switch (gamma_state) {
3268 case (0):
3269 r_gamma_mult = 0;
3270 g_gamma_mult = 1;
3271 b_gamma_mult = 0;
3272 gamma_state = 1;
3273 break;
3274 case (1):
3275 r_gamma_mult = 1;
3276 g_gamma_mult = 0;
3277 b_gamma_mult = 0;
3278 gamma_state = 2;
3279 break;
3280 case (2):
3281 r_gamma_mult = 1;
3282 g_gamma_mult = 1;
3283 b_gamma_mult = 1;
3284 gamma_state = 0;
3285 break;
3286 }
3287 SetScreenBrightness(g_nbrightness);
3288
3289 break;
3290
3291 case 9: // Ctrl I
3292 if (event.ControlDown()) {
3293 m_bShowCompassWin = !m_bShowCompassWin;
3294 SetShowGPSCompassWindow(m_bShowCompassWin);
3295 Refresh(false);
3296 }
3297 break;
3298
3299 default:
3300 break;
3301
3302 } // switch
3303 }
3304
3305 // Allow OnKeyChar to catch the key events too.
3306 if (!b_handled) {
3307 event.Skip();
3308 }
3309}
3310
3311void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3312 if (SendKeyEventToPlugins(event))
3313 return; // PlugIn did something, and does not want the canvas to do
3314 // anything else
3315
3316 switch (event.GetKeyCode()) {
3317 case WXK_TAB:
3318 top_frame::Get()->SwitchKBFocus(this);
3319 break;
3320
3321 case WXK_LEFT:
3322 case WXK_RIGHT:
3323 m_panx = 0;
3324 if (!m_pany) m_panspeed = 0;
3325 break;
3326
3327 case WXK_UP:
3328 case WXK_DOWN:
3329 m_pany = 0;
3330 if (!m_panx) m_panspeed = 0;
3331 break;
3332
3333 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3334 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3335 case WXK_PAGEUP:
3336 case WXK_PAGEDOWN:
3337 if (m_mustmove) DoMovement(m_mustmove);
3338
3339 m_zoom_factor = 1;
3340 break;
3341
3342 case WXK_ALT:
3343 m_modkeys &= ~wxMOD_ALT;
3344#ifdef OCPN_ALT_MENUBAR
3345#ifndef __WXOSX__
3346 // If the permanent menu bar is disabled, and we are not in the middle of
3347 // another key combo, then show the menu bar temporarily when Alt is
3348 // released (or hide it if already visible).
3349 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3350 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3351 top_frame::Get()->ApplyGlobalSettings(false);
3352 }
3353 m_bMayToggleMenuBar = true;
3354#endif
3355#endif
3356 break;
3357
3358 case WXK_CONTROL:
3359 m_modkeys &= ~wxMOD_CONTROL;
3360 break;
3361 }
3362
3363 if (event.GetKeyCode() < 128) // ascii
3364 {
3365 int key_char = event.GetKeyCode();
3366
3367 // Handle both QWERTY and AZERTY keyboard separately for a few control
3368 // codes
3369 if (!g_b_assume_azerty) {
3370 switch (key_char) {
3371 case '+':
3372 case '=':
3373 case '-':
3374 case '_':
3375 case 54:
3376 case 56: // '_' alpha/num pad
3377 DoMovement(m_mustmove);
3378
3379 // m_zoom_factor = 1;
3380 break;
3381 case '[':
3382 case ']':
3383 DoMovement(m_mustmove);
3384 m_rotation_speed = 0;
3385 break;
3386 }
3387 } else {
3388 switch (key_char) {
3389 case 43:
3390 case 54: // '-' alpha/num pad
3391 case 56: // '_' alpha/num pad
3392 DoMovement(m_mustmove);
3393
3394 m_zoom_factor = 1;
3395 break;
3396 }
3397 }
3398 }
3399 event.Skip();
3400}
3401
3402void ChartCanvas::ToggleChartOutlines() {
3403 m_bShowOutlines = !m_bShowOutlines;
3404
3405 Refresh(false);
3406
3407#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3408 // needs a full refresh
3409 if (g_bopengl) InvalidateGL();
3410#endif
3411}
3412
3413void ChartCanvas::ToggleLookahead() {
3414 m_bLookAhead = !m_bLookAhead;
3415 m_OSoffsetx = 0; // center ownship
3416 m_OSoffsety = 0;
3417}
3418
3419void ChartCanvas::SetUpMode(int mode) {
3420 m_upMode = mode;
3421
3422 if (mode != NORTH_UP_MODE) {
3423 // Stuff the COGAvg table in case COGUp is selected
3424 double stuff = 0;
3425 if (!std::isnan(gCog)) stuff = gCog;
3426
3427 if (g_COGAvgSec > 0) {
3428 auto cog_table = top_frame::Get()->GetCOGTable();
3429 for (int i = 0; i < g_COGAvgSec; i++) cog_table[i] = stuff;
3430 }
3431 g_COGAvg = stuff;
3432 top_frame::Get()->StartCogTimer();
3433 } else {
3434 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3435 SetVPRotation(GetVPSkew());
3436 else
3437 SetVPRotation(0); /* reset to north up */
3438 }
3439
3440 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3441 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3442
3443 UpdateGPSCompassStatusBox(true);
3444 top_frame::Get()->DoChartUpdate();
3445}
3446
3447bool ChartCanvas::DoCanvasCOGSet() {
3448 if (GetUpMode() == NORTH_UP_MODE) return false;
3449 double cog_use = g_COGAvg;
3450 if (g_btenhertz) cog_use = gCog;
3451
3452 double rotation = 0;
3453 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3454 rotation = -gHdt * PI / 180.;
3455 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3456 rotation = -cog_use * PI / 180.;
3457
3458 SetVPRotation(rotation);
3459 return true;
3460}
3461
3462double easeOutCubic(double t) {
3463 // Starts quickly and slows down toward the end
3464 return 1.0 - pow(1.0 - t, 3.0);
3465}
3466
3467void ChartCanvas::StartChartDragInertia() {
3468 m_bChartDragging = false;
3469
3470 // Set some parameters
3471 m_chart_drag_inertia_time = 750; // msec
3472 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3473 m_last_elapsed = 0;
3474
3475 // Calculate ending drag velocity
3476 size_t n_vel = 10;
3477 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3478 int xacc = 0;
3479 int yacc = 0;
3480 double tacc = 0;
3481 size_t length = m_drag_vec_t.size();
3482 for (size_t i = 0; i < n_vel; i++) {
3483 xacc += m_drag_vec_x.at(length - 1 - i);
3484 yacc += m_drag_vec_y.at(length - 1 - i);
3485 tacc += m_drag_vec_t.at(length - 1 - i);
3486 }
3487
3488 if (tacc == 0) return;
3489
3490 double drag_velocity_x = xacc / tacc;
3491 double drag_velocity_y = yacc / tacc;
3492 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3493 // drag_velocity_y);
3494
3495 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3496 // touch tap.
3497 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3498
3499 m_chart_drag_velocity_x = drag_velocity_x;
3500 m_chart_drag_velocity_y = drag_velocity_y;
3501
3502 m_chart_drag_inertia_active = true;
3503 // First callback as fast as possible.
3504 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3505}
3506
3507void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3508 if (!m_chart_drag_inertia_active) return;
3509 // Calculate time fraction from 0..1
3510 wxLongLong now = wxGetLocalTimeMillis();
3511 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3512 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3513 if (t > 1.0) t = 1.0;
3514 double e = 1.0 - easeOutCubic(t); // 0..1
3515
3516 double dx =
3517 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3518 double dy =
3519 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3520
3521 m_last_elapsed = elapsed;
3522
3523 // Ensure that target destination lies on whole-pixel boundary
3524 // This allows the render engine to use a faster FBO copy method for drawing
3525 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3526 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3527 double inertia_lat, inertia_lon;
3528 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3529 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3530 // Check if ownship has moved off-screen
3531 if (!IsOwnshipOnScreen()) {
3532 m_bFollow = false; // update the follow flag
3533 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3534 UpdateFollowButtonState();
3535 m_OSoffsetx = 0;
3536 m_OSoffsety = 0;
3537 } else {
3538 m_OSoffsetx += dx;
3539 m_OSoffsety -= dy;
3540 }
3541
3542 Refresh(false);
3543
3544 // Stop condition
3545 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3546 m_chart_drag_inertia_timer.Stop();
3547
3548 // Disable chart pan movement logic
3549 m_target_lat = GetVP().clat;
3550 m_target_lon = GetVP().clon;
3551 m_pan_drag.x = m_pan_drag.y = 0;
3552 m_panx = m_pany = 0;
3553 m_chart_drag_inertia_active = false;
3554 DoCanvasUpdate();
3555
3556 } else {
3557 int target_redraw_interval = 40; // msec
3558 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3559 }
3560}
3561
3562void ChartCanvas::StopMovement() {
3563 m_panx = m_pany = 0;
3564 m_panspeed = 0;
3565 m_zoom_factor = 1;
3566 m_rotation_speed = 0;
3567 m_mustmove = 0;
3568#if 0
3569#if !defined(__WXGTK__) && !defined(__WXQT__)
3570 SetFocus();
3571 top_frame::Get()->Raise();
3572#endif
3573#endif
3574}
3575
3576/* instead of integrating in timer callbacks
3577 (which do not always get called fast enough)
3578 we can perform the integration of movement
3579 at each render frame based on the time change */
3580bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3581 // Start/restart the stop movement timer
3582 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3583
3584 if (!pMovementTimer->IsRunning()) {
3585 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3586 }
3587
3588 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3589 // already moving, gets called again because of key-repeat event
3590 return false;
3591 }
3592
3593 m_last_movement_time = wxDateTime::UNow();
3594
3595 return true;
3596}
3597void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3598 int nstep) {
3599 // Save the target
3600 m_target_lat = target_lat;
3601 m_target_lon = target_lon;
3602
3603 // Save the start point
3604 m_start_lat = GetVP().clat;
3605 m_start_lon = GetVP().clon;
3606
3607 m_VPMovementTimer.Start(1, true); // oneshot
3608 m_timed_move_vp_active = true;
3609 m_stvpc = 0;
3610 m_timedVP_step = nstep;
3611}
3612
3613void ChartCanvas::DoTimedMovementVP() {
3614 if (!m_timed_move_vp_active) return; // not active
3615 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3616 StopMovement();
3617 return;
3618 }
3619 // Stop condition
3620 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3621 double d2 =
3622 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3623 d2 = pow(d2, 0.5);
3624
3625 if (d2 < one_pix) {
3626 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3627 StopMovementVP();
3628 return;
3629 }
3630
3631 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3632 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3633 // StopMovementVP();
3634 // return;
3635 // }
3636
3637 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3638 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3639
3640 m_run_lat = new_lat;
3641 m_run_lon = new_lon;
3642
3643 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3644}
3645
3646void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3647
3648void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3649
3650void ChartCanvas::StartTimedMovementTarget() {}
3651
3652void ChartCanvas::DoTimedMovementTarget() {}
3653
3654void ChartCanvas::StopMovementTarget() {}
3655int ntm;
3656
3657void ChartCanvas::DoTimedMovement() {
3658 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3659 !m_rotation_speed)
3660 return; /* not moving */
3661
3662 wxDateTime now = wxDateTime::UNow();
3663 long dt = 0;
3664 if (m_last_movement_time.IsValid())
3665 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3666
3667 m_last_movement_time = now;
3668
3669 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3670 dt = 500;
3671
3672 DoMovement(dt);
3673}
3674
3676 /* if we get here quickly assume 1ms so that some movement occurs */
3677 if (dt == 0) dt = 1;
3678
3679 m_mustmove -= dt;
3680 if (m_mustmove < 0) m_mustmove = 0;
3681
3682 if (!m_inPinch) { // this stops compound zoom/pan
3683 if (m_pan_drag.x || m_pan_drag.y) {
3684 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3685 m_pan_drag.x = m_pan_drag.y = 0;
3686 }
3687
3688 if (m_panx || m_pany) {
3689 const double slowpan = .1, maxpan = 2;
3690 if (m_modkeys == wxMOD_ALT)
3691 m_panspeed = slowpan;
3692 else {
3693 m_panspeed += (double)dt / 500; /* apply acceleration */
3694 m_panspeed = wxMin(maxpan, m_panspeed);
3695 }
3696 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3697 }
3698 }
3699 if (m_zoom_factor != 1) {
3700 double alpha = 400, beta = 1.5;
3701 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3702
3703 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3704
3705 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3706
3707 // Try to hit the zoom target exactly.
3708 // if(m_wheelzoom_stop_oneshot > 0)
3709 {
3710 if (zoom_factor > 1) {
3711 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3712 zoom_factor = VPoint.chart_scale / m_zoom_target;
3713 }
3714
3715 else if (zoom_factor < 1) {
3716 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3717 zoom_factor = VPoint.chart_scale / m_zoom_target;
3718 }
3719 }
3720
3721 if (fabs(zoom_factor - 1) > 1e-4) {
3722 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3723 } else {
3724 StopMovement();
3725 }
3726
3727 if (m_wheelzoom_stop_oneshot > 0) {
3728 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3729 m_wheelzoom_stop_oneshot = 0;
3730 StopMovement();
3731 }
3732
3733 // Don't overshoot the zoom target.
3734 if (zoom_factor > 1) {
3735 if (VPoint.chart_scale <= m_zoom_target) {
3736 m_wheelzoom_stop_oneshot = 0;
3737 StopMovement();
3738 }
3739 } else if (zoom_factor < 1) {
3740 if (VPoint.chart_scale >= m_zoom_target) {
3741 m_wheelzoom_stop_oneshot = 0;
3742 StopMovement();
3743 }
3744 }
3745 }
3746 }
3747
3748 if (m_rotation_speed) { /* in degrees per second */
3749 double speed = m_rotation_speed;
3750 if (m_modkeys == wxMOD_ALT) speed /= 10;
3751 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3752 }
3753}
3754
3755void ChartCanvas::SetColorScheme(ColorScheme cs) {
3756 SetAlertString("");
3757
3758 // Setup ownship image pointers
3759 switch (cs) {
3760 case GLOBAL_COLOR_SCHEME_DAY:
3761 m_pos_image_red = &m_os_image_red_day;
3762 m_pos_image_grey = &m_os_image_grey_day;
3763 m_pos_image_yellow = &m_os_image_yellow_day;
3764 m_pos_image_user = m_pos_image_user_day;
3765 m_pos_image_user_grey = m_pos_image_user_grey_day;
3766 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3767 m_cTideBitmap = m_bmTideDay;
3768 m_cCurrentBitmap = m_bmCurrentDay;
3769
3770 break;
3771 case GLOBAL_COLOR_SCHEME_DUSK:
3772 m_pos_image_red = &m_os_image_red_dusk;
3773 m_pos_image_grey = &m_os_image_grey_dusk;
3774 m_pos_image_yellow = &m_os_image_yellow_dusk;
3775 m_pos_image_user = m_pos_image_user_dusk;
3776 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3777 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3778 m_cTideBitmap = m_bmTideDusk;
3779 m_cCurrentBitmap = m_bmCurrentDusk;
3780 break;
3781 case GLOBAL_COLOR_SCHEME_NIGHT:
3782 m_pos_image_red = &m_os_image_red_night;
3783 m_pos_image_grey = &m_os_image_grey_night;
3784 m_pos_image_yellow = &m_os_image_yellow_night;
3785 m_pos_image_user = m_pos_image_user_night;
3786 m_pos_image_user_grey = m_pos_image_user_grey_night;
3787 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3788 m_cTideBitmap = m_bmTideNight;
3789 m_cCurrentBitmap = m_bmCurrentNight;
3790 break;
3791 default:
3792 m_pos_image_red = &m_os_image_red_day;
3793 m_pos_image_grey = &m_os_image_grey_day;
3794 m_pos_image_yellow = &m_os_image_yellow_day;
3795 m_pos_image_user = m_pos_image_user_day;
3796 m_pos_image_user_grey = m_pos_image_user_grey_day;
3797 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3798 m_cTideBitmap = m_bmTideDay;
3799 m_cCurrentBitmap = m_bmCurrentDay;
3800 break;
3801 }
3802
3803 CreateDepthUnitEmbossMaps(cs);
3804 CreateOZEmbossMapData(cs);
3805
3806 // Set up fog effect base color
3807 m_fog_color = wxColor(
3808 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3809 float dim = 1.0;
3810 switch (cs) {
3811 case GLOBAL_COLOR_SCHEME_DUSK:
3812 dim = 0.5;
3813 break;
3814 case GLOBAL_COLOR_SCHEME_NIGHT:
3815 dim = 0.25;
3816 break;
3817 default:
3818 break;
3819 }
3820 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3821 m_fog_color.Blue() * dim);
3822
3823 // Really dark
3824#if 0
3825 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3826 SetBackgroundColour( wxColour(0,0,0) );
3827
3828 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3829 }
3830 else{
3831 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3832#ifndef __WXMAC__
3833 SetBackgroundColour( wxNullColour );
3834#endif
3835 }
3836#endif
3837
3838 // UpdateToolbarColorScheme(cs);
3839
3840 m_Piano->SetColorScheme(cs);
3841
3842 m_Compass->SetColorScheme(cs);
3843
3844 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3845
3846 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3847
3848 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3849 if (m_notification_button) {
3850 m_notification_button->SetColorScheme(cs);
3851 }
3852
3853#ifdef ocpnUSE_GL
3854 if (g_bopengl && m_glcc) {
3855 m_glcc->SetColorScheme(cs);
3856 g_glTextureManager->ClearAllRasterTextures();
3857 // m_glcc->FlushFBO();
3858 }
3859#endif
3860 SetbTCUpdate(true); // force re-render of tide/current locators
3861 m_brepaint_piano = true;
3862
3863 ReloadVP();
3864
3865 m_cs = cs;
3866}
3867
3868wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3869 wxImage img = Bitmap.ConvertToImage();
3870 int sx = img.GetWidth();
3871 int sy = img.GetHeight();
3872
3873 wxImage new_img(img);
3874
3875 for (int i = 0; i < sx; i++) {
3876 for (int j = 0; j < sy; j++) {
3877 if (!img.IsTransparent(i, j)) {
3878 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3879 (unsigned char)(img.GetGreen(i, j) * factor),
3880 (unsigned char)(img.GetBlue(i, j) * factor));
3881 }
3882 }
3883 }
3884
3885 wxBitmap ret = wxBitmap(new_img);
3886
3887 return ret;
3888}
3889
3890void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3891 int max) {
3892 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3893 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3894
3895 if (!m_pBrightPopup) {
3896 // Calculate size
3897 int x, y;
3898 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3899
3900 m_pBrightPopup = new TimedPopupWin(this, 3);
3901
3902 m_pBrightPopup->SetSize(x, y);
3903 m_pBrightPopup->Move(120, 120);
3904 }
3905
3906 int bmpsx = m_pBrightPopup->GetSize().x;
3907 int bmpsy = m_pBrightPopup->GetSize().y;
3908
3909 wxBitmap bmp(bmpsx, bmpsx);
3910 wxMemoryDC mdc(bmp);
3911
3912 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3913 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3914 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3915 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3916 mdc.Clear();
3917
3918 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3919
3920 mdc.SetFont(*pfont);
3921 wxString val;
3922
3923 if (brightness == max)
3924 val = "MAX";
3925 else if (brightness == min)
3926 val = "MIN";
3927 else
3928 val.Printf("%3d", brightness);
3929
3930 mdc.DrawText(val, 0, 0);
3931
3932 mdc.SelectObject(wxNullBitmap);
3933
3934 m_pBrightPopup->SetBitmap(bmp);
3935 m_pBrightPopup->Show();
3936 m_pBrightPopup->Refresh();
3937}
3938
3939void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3940 m_b_rot_hidef = true;
3941 ReloadVP();
3942}
3943
3944void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3945 if (!g_bRollover) return;
3946
3947 bool b_need_refresh = false;
3948
3949 wxSize win_size = GetSize() * m_displayScale;
3950 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3951
3952 // Handle the AIS Rollover Window first
3953 bool showAISRollover = false;
3954 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3955 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3956 SelectItem *pFind = pSelectAIS->FindSelection(
3957 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3958 if (pFind) {
3959 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3960 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3961
3962 if (ptarget) {
3963 showAISRollover = true;
3964
3965 if (NULL == m_pAISRolloverWin) {
3966 m_pAISRolloverWin = new RolloverWin(this);
3967 m_pAISRolloverWin->IsActive(false);
3968 b_need_refresh = true;
3969 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3970 m_AISRollover_MMSI != FoundAIS_MMSI) {
3971 // Sometimes the mouse moves fast enough to get over a new AIS
3972 // target before the one-shot has fired to remove the old target.
3973 // Result: wrong target data is shown.
3974 // Detect this case,close the existing rollover ASAP, and restart
3975 // the timer.
3976 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3977 m_pAISRolloverWin->IsActive(false);
3978 m_AISRollover_MMSI = 0;
3979 Refresh();
3980 return;
3981 }
3982
3983 m_AISRollover_MMSI = FoundAIS_MMSI;
3984
3985 if (!m_pAISRolloverWin->IsActive()) {
3986 wxString s = ptarget->GetRolloverString();
3987 m_pAISRolloverWin->SetString(s);
3988
3989 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3990 AIS_ROLLOVER, win_size);
3991 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3992 m_pAISRolloverWin->IsActive(true);
3993 b_need_refresh = true;
3994 }
3995 }
3996 } else {
3997 m_AISRollover_MMSI = 0;
3998 showAISRollover = false;
3999 }
4000 }
4001
4002 // Maybe turn the rollover off
4003 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
4004 m_pAISRolloverWin->IsActive(false);
4005 m_AISRollover_MMSI = 0;
4006 b_need_refresh = true;
4007 }
4008
4009 // Now the Route info rollover
4010 // Show the route segment info
4011 bool showRouteRollover = false;
4012
4013 if (NULL == m_pRolloverRouteSeg) {
4014 // Get a list of all selectable sgements, and search for the first
4015 // visible segment as the rollover target.
4016
4017 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4018 SelectableItemList SelList = pSelect->FindSelectionList(
4019 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
4020 auto node = SelList.begin();
4021 while (node != SelList.end()) {
4022 SelectItem *pFindSel = *node;
4023
4024 Route *pr = (Route *)pFindSel->m_pData3; // candidate
4025
4026 if (pr && pr->IsVisible()) {
4027 m_pRolloverRouteSeg = pFindSel;
4028 showRouteRollover = true;
4029
4030 if (NULL == m_pRouteRolloverWin) {
4031 m_pRouteRolloverWin = new RolloverWin(this, 10);
4032 m_pRouteRolloverWin->IsActive(false);
4033 }
4034
4035 if (!m_pRouteRolloverWin->IsActive()) {
4036 wxString s;
4037 RoutePoint *segShow_point_a =
4038 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
4039 RoutePoint *segShow_point_b =
4040 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
4041
4042 double brg, dist;
4043 DistanceBearingMercator(
4044 segShow_point_b->m_lat, segShow_point_b->m_lon,
4045 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4046
4047 if (!pr->m_bIsInLayer)
4048 s.Append(_("Route") + ": ");
4049 else
4050 s.Append(_("Layer Route: "));
4051
4052 if (pr->m_RouteNameString.IsEmpty())
4053 s.Append(_("(unnamed)"));
4054 else
4055 s.Append(pr->m_RouteNameString);
4056
4057 s << "\n"
4058 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4059 << "\n"
4060 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4061 << segShow_point_b->GetName() << "\n";
4062
4063 if (g_bShowTrue)
4064 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4065 (int)floor(brg + 0.5), 0x00B0);
4066 if (g_bShowMag) {
4067 double latAverage =
4068 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4069 double lonAverage =
4070 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4071 double varBrg =
4072 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4073
4074 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4075 (int)floor(varBrg + 0.5), 0x00B0);
4076 }
4077
4078 s << FormatDistanceAdaptive(dist);
4079
4080 // Compute and display cumulative distance from route start point to
4081 // current leg end point and RNG,TTG,ETA from ship to current leg end
4082 // point for active route
4083 double shiptoEndLeg = 0.;
4084 bool validActive = false;
4085 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4086 validActive = true;
4087
4088 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4089 auto node = pr->pRoutePointList->begin();
4090 RoutePoint *prp;
4091 float dist_to_endleg = 0;
4092 wxString t;
4093
4094 for (++node; node != pr->pRoutePointList->end(); ++node) {
4095 prp = *node;
4096 if (validActive)
4097 shiptoEndLeg += prp->m_seg_len;
4098 else if (prp->m_bIsActive)
4099 validActive = true;
4100 dist_to_endleg += prp->m_seg_len;
4101 if (prp->IsSame(segShow_point_a)) break;
4102 }
4103 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4104 }
4105 // write from ship to end selected leg point data if the route is
4106 // active
4107 if (validActive) {
4108 s << "\n"
4109 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4110 shiptoEndLeg +=
4112 ->GetCurrentRngToActivePoint(); // add distance from ship
4113 // to active point
4114 shiptoEndLeg +=
4115 segShow_point_b
4116 ->m_seg_len; // add the lenght of the selected leg
4117 s << FormatDistanceAdaptive(shiptoEndLeg);
4118 // ensure sog/cog are valid and vmg is positive to keep data
4119 // coherent
4120 double vmg = 0.;
4121 if (!std::isnan(gCog) && !std::isnan(gSog))
4122 vmg = gSog *
4123 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4124 PI / 180.);
4125 if (vmg > 0.) {
4126 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4127 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4128 s << " - "
4129 << wxString(ttg_sec > SECONDS_PER_DAY
4130 ? ttg_span.Format(_("%Dd %H:%M"))
4131 : ttg_span.Format(_("%H:%M")));
4132 wxDateTime dtnow, eta;
4133 eta = dtnow.SetToCurrent().Add(ttg_span);
4134 s << " - " << eta.Format("%b").Mid(0, 4)
4135 << eta.Format(" %d %H:%M");
4136 } else
4137 s << " ---- ----";
4138 }
4139 m_pRouteRolloverWin->SetString(s);
4140
4141 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4142 LEG_ROLLOVER, win_size);
4143 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4144 m_pRouteRolloverWin->IsActive(true);
4145 b_need_refresh = true;
4146 showRouteRollover = true;
4147 break;
4148 }
4149 } else {
4150 ++node;
4151 }
4152 }
4153 } else {
4154 // Is the cursor still in select radius, and not timed out?
4155 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4156 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4157 m_pRolloverRouteSeg))
4158 showRouteRollover = false;
4159 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4160 showRouteRollover = false;
4161 else
4162 showRouteRollover = true;
4163 }
4164
4165 // If currently creating a route, do not show this rollover window
4166 if (m_routeState) showRouteRollover = false;
4167
4168 // Similar for AIS target rollover window
4169 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4170 showRouteRollover = false;
4171
4172 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4173 !showRouteRollover) {
4174 m_pRouteRolloverWin->IsActive(false);
4175 m_pRolloverRouteSeg = NULL;
4176 m_pRouteRolloverWin->Destroy();
4177 m_pRouteRolloverWin = NULL;
4178 b_need_refresh = true;
4179 } else if (m_pRouteRolloverWin && showRouteRollover) {
4180 m_pRouteRolloverWin->IsActive(true);
4181 b_need_refresh = true;
4182 }
4183
4184 // Now the Track info rollover
4185 // Show the track segment info
4186 bool showTrackRollover = false;
4187
4188 if (NULL == m_pRolloverTrackSeg) {
4189 // Get a list of all selectable sgements, and search for the first
4190 // visible segment as the rollover target.
4191
4192 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4193 SelectableItemList SelList = pSelect->FindSelectionList(
4194 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4195
4196 auto node = SelList.begin();
4197 while (node != SelList.end()) {
4198 SelectItem *pFindSel = *node;
4199
4200 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4201
4202 if (pt && pt->IsVisible()) {
4203 m_pRolloverTrackSeg = pFindSel;
4204 showTrackRollover = true;
4205
4206 if (NULL == m_pTrackRolloverWin) {
4207 m_pTrackRolloverWin = new RolloverWin(this, 10);
4208 m_pTrackRolloverWin->IsActive(false);
4209 }
4210
4211 if (!m_pTrackRolloverWin->IsActive()) {
4212 wxString s;
4213 TrackPoint *segShow_point_a =
4214 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4215 TrackPoint *segShow_point_b =
4216 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4217
4218 double brg, dist;
4219 DistanceBearingMercator(
4220 segShow_point_b->m_lat, segShow_point_b->m_lon,
4221 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4222
4223 if (!pt->m_bIsInLayer)
4224 s.Append(_("Track") + ": ");
4225 else
4226 s.Append(_("Layer Track: "));
4227
4228 if (pt->GetName().IsEmpty())
4229 s.Append(_("(unnamed)"));
4230 else
4231 s.Append(pt->GetName());
4232 double tlenght = pt->Length();
4233 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4234 if (pt->GetLastPoint()->GetTimeString() &&
4235 pt->GetPoint(0)->GetTimeString()) {
4236 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4237 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4238 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4239 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4240 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4241 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4242 << getUsrSpeedUnit();
4243 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4244 : ttime.Format(" %H:%M"));
4245 }
4246 }
4247
4248 if (g_bShowTrackPointTime &&
4249 strlen(segShow_point_b->GetTimeString())) {
4250 wxString stamp = segShow_point_b->GetTimeString();
4251 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4252 if (timestamp.IsValid()) {
4253 // Format track rollover timestamp to OCPN global TZ setting
4256 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4257 }
4258 s << "\n" << _("Segment Created: ") << stamp;
4259 }
4260
4261 s << "\n";
4262 if (g_bShowTrue)
4263 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4264 0x00B0);
4265
4266 if (g_bShowMag) {
4267 double latAverage =
4268 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4269 double lonAverage =
4270 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4271 double varBrg =
4272 top_frame::Get()->GetMag(brg, latAverage, lonAverage);
4273
4274 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4275 0x00B0);
4276 }
4277
4278 s << FormatDistanceAdaptive(dist);
4279
4280 if (segShow_point_a->GetTimeString() &&
4281 segShow_point_b->GetTimeString()) {
4282 wxDateTime apoint = segShow_point_a->GetCreateTime();
4283 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4284 if (apoint.IsValid() && bpoint.IsValid()) {
4285 double segmentSpeed = toUsrSpeed(
4286 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4287 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4288 << getUsrSpeedUnit();
4289 }
4290 }
4291
4292 m_pTrackRolloverWin->SetString(s);
4293
4294 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4295 LEG_ROLLOVER, win_size);
4296 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4297 m_pTrackRolloverWin->IsActive(true);
4298 b_need_refresh = true;
4299 showTrackRollover = true;
4300 break;
4301 }
4302 } else {
4303 ++node;
4304 }
4305 }
4306 } else {
4307 // Is the cursor still in select radius, and not timed out?
4308 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4309 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4310 m_pRolloverTrackSeg))
4311 showTrackRollover = false;
4312 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4313 showTrackRollover = false;
4314 else
4315 showTrackRollover = true;
4316 }
4317
4318 // Similar for AIS target rollover window
4319 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4320 showTrackRollover = false;
4321
4322 // Similar for route rollover window
4323 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4324 showTrackRollover = false;
4325
4326 // TODO We onlt show tracks on primary canvas....
4327 // if(!IsPrimaryCanvas())
4328 // showTrackRollover = false;
4329
4330 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4331 !showTrackRollover) {
4332 m_pTrackRolloverWin->IsActive(false);
4333 m_pRolloverTrackSeg = NULL;
4334 m_pTrackRolloverWin->Destroy();
4335 m_pTrackRolloverWin = NULL;
4336 b_need_refresh = true;
4337 } else if (m_pTrackRolloverWin && showTrackRollover) {
4338 m_pTrackRolloverWin->IsActive(true);
4339 b_need_refresh = true;
4340 }
4341
4342 if (b_need_refresh) Refresh();
4343}
4344
4345void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4346 if ((GetShowENCLights() || m_bsectors_shown) &&
4347 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4348 extendedSectorLegs)) {
4349 if (!m_bsectors_shown) {
4350 ReloadVP(false);
4351 m_bsectors_shown = true;
4352 }
4353 } else {
4354 if (m_bsectors_shown) {
4355 ReloadVP(false);
4356 m_bsectors_shown = false;
4357 }
4358 }
4359
4360// This is here because GTK status window update is expensive..
4361// cairo using pango rebuilds the font every time so is very
4362// inefficient
4363// Anyway, only update the status bar when this timer expires
4364#if defined(__WXGTK__) || defined(__WXQT__)
4365 {
4366 // Check the absolute range of the cursor position
4367 // There could be a window wherein the chart geoereferencing is not
4368 // valid....
4369 double cursor_lat, cursor_lon;
4370 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4371
4372 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4373 while (cursor_lon < -180.) cursor_lon += 360.;
4374
4375 while (cursor_lon > 180.) cursor_lon -= 360.;
4376
4377 SetCursorStatus(cursor_lat, cursor_lon);
4378 }
4379 }
4380#endif
4381}
4382
4383void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4384 if (!top_frame::Get()->GetFrameStatusBar()) return;
4385
4386 wxString s1;
4387 s1 += " ";
4388 s1 += toSDMM(1, cursor_lat);
4389 s1 += " ";
4390 s1 += toSDMM(2, cursor_lon);
4391
4392 if (STAT_FIELD_CURSOR_LL >= 0)
4393 top_frame::Get()->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4394
4395 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4396
4397 double brg, dist;
4398 wxString sm;
4399 wxString st;
4400 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4401 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4402 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4403
4404 wxString s = st + sm;
4405 s << FormatDistanceAdaptive(dist);
4406
4407 // CUSTOMIZATION - LIVE ETA OPTION
4408 // -------------------------------------------------------
4409 // Calculate an "live" ETA based on route starting from the current
4410 // position of the boat and goes to the cursor of the mouse.
4411 // In any case, an standard ETA will be calculated with a default speed
4412 // of the boat to give an estimation of the route (in particular if GPS
4413 // is off).
4414
4415 // Display only if option "live ETA" is selected in Settings > Display >
4416 // General.
4417 if (g_bShowLiveETA) {
4418 float realTimeETA;
4419 float boatSpeed;
4420 float boatSpeedDefault = g_defaultBoatSpeed;
4421
4422 // Calculate Estimate Time to Arrival (ETA) in minutes
4423 // Check before is value not closed to zero (it will make an very big
4424 // number...)
4425 if (!std::isnan(gSog)) {
4426 boatSpeed = gSog;
4427 if (boatSpeed < 0.5) {
4428 realTimeETA = 0;
4429 } else {
4430 realTimeETA = dist / boatSpeed * 60;
4431 }
4432 } else {
4433 realTimeETA = 0;
4434 }
4435
4436 // Add space after distance display
4437 s << " ";
4438 // Display ETA
4439 s << minutesToHoursDays(realTimeETA);
4440
4441 // In any case, display also an ETA with default speed at 6knts
4442
4443 s << " [@";
4444 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4445 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4446 s << " ";
4447 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4448 s << "]";
4449 }
4450 // END OF - LIVE ETA OPTION
4451
4452 top_frame::Get()->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4453}
4454
4455// CUSTOMIZATION - FORMAT MINUTES
4456// -------------------------------------------------------
4457// New function to format minutes into a more readable format:
4458// * Hours + minutes, or
4459// * Days + hours.
4460wxString minutesToHoursDays(float timeInMinutes) {
4461 wxString s;
4462
4463 if (timeInMinutes == 0) {
4464 s << "--min";
4465 }
4466
4467 // Less than 60min, keep time in minutes
4468 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4469 s << wxString::Format("%d", (int)timeInMinutes);
4470 s << "min";
4471 }
4472
4473 // Between 1h and less than 24h, display time in hours, minutes
4474 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4475 int hours;
4476 int min;
4477 hours = (int)timeInMinutes / 60;
4478 min = (int)timeInMinutes % 60;
4479
4480 if (min == 0) {
4481 s << wxString::Format("%d", hours);
4482 s << "h";
4483 } else {
4484 s << wxString::Format("%d", hours);
4485 s << "h";
4486 s << wxString::Format("%d", min);
4487 s << "min";
4488 }
4489
4490 }
4491
4492 // More than 24h, display time in days, hours
4493 else if (timeInMinutes > 24 * 60) {
4494 int days;
4495 int hours;
4496 days = (int)(timeInMinutes / 60) / 24;
4497 hours = (int)(timeInMinutes / 60) % 24;
4498
4499 if (hours == 0) {
4500 s << wxString::Format("%d", days);
4501 s << "d";
4502 } else {
4503 s << wxString::Format("%d", days);
4504 s << "d";
4505 s << wxString::Format("%d", hours);
4506 s << "h";
4507 }
4508 }
4509
4510 return s;
4511}
4512
4513// END OF CUSTOMIZATION - FORMAT MINUTES
4514// Thanks open source code ;-)
4515// -------------------------------------------------------
4516
4517void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4518 double clat, clon;
4519 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4520 *lat = clat;
4521 *lon = clon;
4522}
4523
4524void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4525 wxPoint2DDouble *r) {
4526 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4527}
4528
4530 double rlon, wxPoint2DDouble *r) {
4531 // If the Current Chart is a raster chart, and the
4532 // requested lat/long is within the boundaries of the chart,
4533 // and the VP is not rotated,
4534 // then use the embedded BSB chart georeferencing algorithm
4535 // for greater accuracy
4536 // Additionally, use chart embedded georef if the projection is TMERC
4537 // i.e. NOT MERCATOR and NOT POLYCONIC
4538
4539 // If for some reason the chart rejects the request by returning an error,
4540 // then fall back to Viewport Projection estimate from canvas parameters
4541 if (!g_bopengl && m_singleChart &&
4542 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4543 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4544 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4545 (m_singleChart->GetChartProjectionType() !=
4546 PROJECTION_TRANSVERSE_MERCATOR) &&
4547 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4548 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4549 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4550 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4551 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4552 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4553 // Cur_BSB_Ch->GetCOVRTablenPoints
4554 // ( 0 ), rlon,
4555 // rlat );
4556 // bInside = true;
4557 // if ( bInside )
4558 if (Cur_BSB_Ch) {
4559 // This is a Raster chart....
4560 // If the VP is changing, the raster chart parameters may not yet be
4561 // setup So do that before accessing the chart's embedded
4562 // georeferencing
4563 Cur_BSB_Ch->SetVPRasterParms(vp);
4564 double rpixxd, rpixyd;
4565 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4566 r->m_x = rpixxd;
4567 r->m_y = rpixyd;
4568 return;
4569 }
4570 }
4571 }
4572
4573 // if needed, use the VPoint scaling estimator,
4574 *r = vp.GetDoublePixFromLL(rlat, rlon);
4575}
4576
4577// This routine might be deleted and all of the rendering improved
4578// to have floating point accuracy
4579bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4580 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4581}
4582
4583bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4584 wxPoint *r) {
4585 wxPoint2DDouble p;
4586 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4587
4588 // some projections give nan values when invisible values (other side of
4589 // world) are requested we should stop using integer coordinates or return
4590 // false here (and test it everywhere)
4591 if (std::isnan(p.m_x)) {
4592 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4593 return false;
4594 }
4595
4596 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4597 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4598 else
4599 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4600
4601 return true;
4602}
4603
4604void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4605 double &lon) {
4606 // If the Current Chart is a raster chart, and the
4607 // requested x,y is within the boundaries of the chart,
4608 // and the VP is not rotated,
4609 // then use the embedded BSB chart georeferencing algorithm
4610 // for greater accuracy
4611 // Additionally, use chart embedded georef if the projection is TMERC
4612 // i.e. NOT MERCATOR and NOT POLYCONIC
4613
4614 // If for some reason the chart rejects the request by returning an error,
4615 // then fall back to Viewport Projection estimate from canvas parameters
4616 bool bUseVP = true;
4617
4618 if (!g_bopengl && m_singleChart &&
4619 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4620 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4621 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4622 (m_singleChart->GetChartProjectionType() !=
4623 PROJECTION_TRANSVERSE_MERCATOR) &&
4624 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4625 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4626 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4627 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4628
4629 // TODO maybe need iterative process to validate bInside
4630 // first pass is mercator, then check chart boundaries
4631
4632 if (Cur_BSB_Ch) {
4633 // This is a Raster chart....
4634 // If the VP is changing, the raster chart parameters may not yet be
4635 // setup So do that before accessing the chart's embedded
4636 // georeferencing
4637 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4638
4639 double slat, slon;
4640 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4641 lat = slat;
4642
4643 if (slon < -180.)
4644 slon += 360.;
4645 else if (slon > 180.)
4646 slon -= 360.;
4647
4648 lon = slon;
4649 bUseVP = false;
4650 }
4651 }
4652 }
4653
4654 // if needed, use the VPoint scaling estimator
4655 if (bUseVP) {
4656 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4657 }
4658}
4659
4661 StopMovement();
4662 DoZoomCanvas(factor, false);
4663 extendedSectorLegs.clear();
4664}
4665
4666void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4667 bool stoptimer) {
4668 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4669
4670 if (g_bsmoothpanzoom) {
4671 if (StartTimedMovement(stoptimer)) {
4672 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4673 m_zoom_factor = factor;
4674 }
4675
4676 m_zoom_target = VPoint.chart_scale / factor;
4677 } else {
4678 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4679
4680 DoZoomCanvas(factor, can_zoom_to_cursor);
4681 }
4682
4683 extendedSectorLegs.clear();
4684}
4685
4686void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4687 // possible on startup
4688 if (!ChartData) return;
4689 if (!m_pCurrentStack) return;
4690
4691 /* TODO: queue the quilted loading code to a background thread
4692 so yield is never called from here, and also rendering is not delayed */
4693
4694 // Cannot allow Yield() re-entrancy here
4695 if (m_bzooming) return;
4696 m_bzooming = true;
4697
4698 double old_ppm = GetVP().view_scale_ppm;
4699
4700 // Capture current cursor position for zoom to cursor
4701 double zlat = m_cursor_lat;
4702 double zlon = m_cursor_lon;
4703
4704 double proposed_scale_onscreen =
4705 GetVP().chart_scale /
4706 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4707 bool b_do_zoom = false;
4708
4709 if (factor > 1) {
4710 b_do_zoom = true;
4711
4712 // double zoom_factor = factor;
4713
4714 ChartBase *pc = NULL;
4715
4716 if (!VPoint.b_quilt) {
4717 pc = m_singleChart;
4718 } else {
4719 if (!m_disable_adjust_on_zoom) {
4720 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4721 if (new_db_index >= 0)
4722 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4723 else { // for whatever reason, no reference chart is known
4724 // Choose the smallest scale chart on the current stack
4725 // and then adjust for scale range
4726 int current_ref_stack_index = -1;
4727 if (m_pCurrentStack->nEntry) {
4728 int trial_index =
4729 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4730 m_pQuilt->SetReferenceChart(trial_index);
4731 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4732 if (new_db_index >= 0)
4733 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4734 }
4735 }
4736
4737 if (m_pCurrentStack)
4738 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4739 new_db_index); // highlite the correct bar entry
4740 }
4741 }
4742
4743 if (pc) {
4744 // double target_scale_ppm = GetVPScale() * zoom_factor;
4745 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4746 // target_scale_ppm;
4747
4748 // Query the chart to determine the appropriate zoom range
4749 double min_allowed_scale =
4750 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4751
4752 if (proposed_scale_onscreen < min_allowed_scale) {
4753 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4754 m_zoom_factor = 1; /* stop zooming */
4755 b_do_zoom = false;
4756 } else
4757 proposed_scale_onscreen = min_allowed_scale;
4758 }
4759
4760 } else {
4761 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4762 }
4763
4764 } else if (factor < 1) {
4765 b_do_zoom = true;
4766
4767 ChartBase *pc = NULL;
4768
4769 bool b_smallest = false;
4770
4771 if (!VPoint.b_quilt) { // not quilted
4772 pc = m_singleChart;
4773
4774 if (pc) {
4775 // If m_singleChart is not on the screen, unbound the zoomout
4776 LLBBox viewbox = VPoint.GetBBox();
4777 // BoundingBox chart_box;
4778 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4779 double max_allowed_scale;
4780
4781 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4782
4783 // We can allow essentially unbounded zoomout in single chart mode
4784 // if( ChartData->GetDBBoundingBox( current_index,
4785 // &chart_box ) &&
4786 // !viewbox.IntersectOut( chart_box ) )
4787 // // Clamp the minimum scale zoom-out to the value
4788 // specified by the chart max_allowed_scale =
4789 // wxMin(max_allowed_scale, 4.0 *
4790 // pc->GetNormalScaleMax(
4791 // GetCanvasScaleFactor(),
4792 // GetCanvasWidth() ) );
4793 if (proposed_scale_onscreen > max_allowed_scale) {
4794 m_zoom_factor = 1; /* stop zooming */
4795 proposed_scale_onscreen = max_allowed_scale;
4796 }
4797 }
4798
4799 } else {
4800 if (!m_disable_adjust_on_zoom) {
4801 int new_db_index =
4802 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4803 if (new_db_index >= 0)
4804 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4805
4806 if (m_pCurrentStack)
4807 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4808 new_db_index); // highlite the correct bar entry
4809
4810 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4811
4812 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4813 proposed_scale_onscreen =
4814 wxMin(proposed_scale_onscreen,
4815 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4816 }
4817
4818 // set a minimum scale
4819 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4820 m_absolute_min_scale_ppm)
4821 proposed_scale_onscreen =
4822 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4823 }
4824 }
4825 double new_scale =
4826 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4827
4828 if (b_do_zoom) {
4829 // Disable ZTC if lookahead is ON, and currently b_follow is active
4830 bool b_allow_ztc = true;
4831 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4832 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4833 if (m_bLookAhead) {
4834 double brg, distance;
4835 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4836 &distance);
4837 dir_to_shift = brg;
4838 meters_to_shift = distance * 1852;
4839 }
4840 // Arrange to combine the zoom and pan into one operation for smoother
4841 // appearance
4842 SetVPScale(new_scale, false); // adjust, but deferred refresh
4843 wxPoint r;
4844 GetCanvasPointPix(zlat, zlon, &r);
4845 // this will emit the Refresh()
4846 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4847 } else {
4848 SetVPScale(new_scale);
4849 if (m_bFollow) DoCanvasUpdate();
4850 }
4851 }
4852
4853 m_bzooming = false;
4854}
4855
4856void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4857 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4858 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4859}
4860
4861int rot;
4862void ChartCanvas::RotateCanvas(double dir) {
4863 // SetUpMode(NORTH_UP_MODE);
4864
4865 if (g_bsmoothpanzoom) {
4866 if (StartTimedMovement()) {
4867 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4868 m_rotation_speed = dir * 60;
4869 }
4870 } else {
4871 double speed = dir * 10;
4872 if (m_modkeys == wxMOD_ALT) speed /= 20;
4873 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4874 }
4875}
4876
4877void ChartCanvas::DoRotateCanvas(double rotation) {
4878 while (rotation < 0) rotation += 2 * PI;
4879 while (rotation > 2 * PI) rotation -= 2 * PI;
4880
4881 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4882
4883 SetVPRotation(rotation);
4884 top_frame::Get()->UpdateRotationState(VPoint.rotation);
4885}
4886
4887void ChartCanvas::DoTiltCanvas(double tilt) {
4888 while (tilt < 0) tilt = 0;
4889 while (tilt > .95) tilt = .95;
4890
4891 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4892
4893 VPoint.tilt = tilt;
4894 Refresh(false);
4895}
4896
4897void ChartCanvas::TogglebFollow() {
4898 if (!m_bFollow)
4899 SetbFollow();
4900 else
4901 ClearbFollow();
4902}
4903
4904void ChartCanvas::ClearbFollow() {
4905 m_bFollow = false; // update the follow flag
4906
4907 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4908
4909 UpdateFollowButtonState();
4910
4911 DoCanvasUpdate();
4912 ReloadVP();
4913 top_frame::Get()->SetChartUpdatePeriod();
4914}
4915
4916void ChartCanvas::SetbFollow() {
4917 // Is the OWNSHIP on-screen?
4918 // If not, then reset the OWNSHIP offset to 0 (center screen)
4919 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4920 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4921 m_OSoffsetx = 0;
4922 m_OSoffsety = 0;
4923 }
4924
4925 // Apply the present b_follow offset values to ship position
4926 wxPoint2DDouble p;
4928 p.m_x += m_OSoffsetx;
4929 p.m_y -= m_OSoffsety;
4930
4931 // compute the target center screen lat/lon
4932 double dlat, dlon;
4933 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4934
4935 JumpToPosition(dlat, dlon, GetVPScale());
4936 m_bFollow = true;
4937
4938 top_frame::Get()->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4939 UpdateFollowButtonState();
4940
4941 if (!g_bSmoothRecenter) {
4942 DoCanvasUpdate();
4943 ReloadVP();
4944 }
4945 top_frame::Get()->SetChartUpdatePeriod();
4946}
4947
4948void ChartCanvas::UpdateFollowButtonState() {
4949 if (m_muiBar) {
4950 if (!m_bFollow)
4951 m_muiBar->SetFollowButtonState(0);
4952 else {
4953 if (m_bLookAhead)
4954 m_muiBar->SetFollowButtonState(2);
4955 else
4956 m_muiBar->SetFollowButtonState(1);
4957 }
4958 }
4959
4960#ifdef __ANDROID__
4961 if (!m_bFollow)
4962 androidSetFollowTool(0);
4963 else {
4964 if (m_bLookAhead)
4965 androidSetFollowTool(2);
4966 else
4967 androidSetFollowTool(1);
4968 }
4969#endif
4970
4971 // Look for plugin using API-121 or later
4972 // If found, make the follow state callback.
4973 if (g_pi_manager) {
4974 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4975 if (pic->m_enabled && pic->m_init_state) {
4976 switch (pic->m_api_version) {
4977 case 121: {
4978 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4979 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4980 break;
4981 }
4982 default:
4983 break;
4984 }
4985 }
4986 }
4987 }
4988}
4989
4990void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4991 if (g_bSmoothRecenter && !m_routeState) {
4992 if (StartSmoothJump(lat, lon, scale_ppm))
4993 return;
4994 else {
4995 // move closer to the target destination, and try again
4996 double gcDist, gcBearingEnd;
4997 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4998 &gcBearingEnd);
4999 gcBearingEnd += 180;
5000 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
5001 GetCanvasWidth() / GetVPScale(); // meters
5002 double lon_offset =
5003 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
5004 double new_lat = lat + (lat_offset / (1852 * 60));
5005 double new_lon = lon + (lon_offset / (1852 * 60));
5006 SetViewPoint(new_lat, new_lon);
5007 ReloadVP();
5008 StartSmoothJump(lat, lon, scale_ppm);
5009 return;
5010 }
5011 }
5012
5013 if (lon > 180.0) lon -= 360.0;
5014 m_vLat = lat;
5015 m_vLon = lon;
5016 StopMovement();
5017 m_bFollow = false;
5018
5019 if (!GetQuiltMode()) {
5020 double skew = 0;
5021 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
5022 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
5023 } else {
5024 if (scale_ppm != GetVPScale()) {
5025 // XXX should be done in SetViewPoint
5026 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5027 AdjustQuiltRefChart();
5028 }
5029 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
5030 }
5031
5032 ReloadVP();
5033
5034 UpdateFollowButtonState();
5035
5036 // TODO
5037 // if( g_pi_manager ) {
5038 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
5039 // }
5040}
5041
5042bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
5043 // Check distance to jump, in pixels at current chart scale
5044 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5045 // width.
5046 double gcDist;
5047 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5048 double distance_pixels = gcDist * GetVPScale();
5049 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5050 // Jump is too far, try again
5051 return false;
5052 }
5053
5054 // Save where we're coming from
5055 m_startLat = m_vLat;
5056 m_startLon = m_vLon;
5057 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5058
5059 // Save where we want to end up
5060 m_endLat = lat;
5061 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5062 m_endScale = scale_ppm;
5063
5064 // Setup timing
5065 m_animationDuration = 600; // ms
5066 m_animationStart = wxGetLocalTimeMillis();
5067
5068 // Stop any previous movement, ensure no conflicts
5069 StopMovement();
5070 m_bFollow = false;
5071
5072 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5073 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5074 m_animationActive = true;
5075
5076 return true;
5077}
5078
5079void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5080 // Calculate time fraction from 0..1
5081 wxLongLong now = wxGetLocalTimeMillis();
5082 double elapsed = (now - m_animationStart).ToDouble();
5083 double t = elapsed / m_animationDuration.ToDouble();
5084 if (t > 1.0) t = 1.0;
5085
5086 // Ease function for smoother movement
5087 double e = easeOutCubic(t);
5088
5089 // Interpolate lat/lon/scale
5090 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5091 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5092 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5093
5094 // Update viewpoint
5095 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5096 // portion)
5097 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5098 ReloadVP();
5099
5100 // If we reached the end, stop the timer and finalize
5101 if (t >= 1.0) {
5102 m_easeTimer.Stop();
5103 m_animationActive = false;
5104 UpdateFollowButtonState();
5105 ZoomCanvasSimple(1.0001);
5106 DoCanvasUpdate();
5107 ReloadVP();
5108 }
5109}
5110
5111bool ChartCanvas::PanCanvas(double dx, double dy) {
5112 if (!ChartData) return false;
5113 extendedSectorLegs.clear();
5114
5115 double dlat, dlon;
5116 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5117
5118 int iters = 0;
5119 for (;;) {
5120 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5121
5122 if (iters++ > 5) return false;
5123 if (!std::isnan(dlat)) break;
5124
5125 dx *= .5, dy *= .5;
5126 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5127 }
5128
5129 // avoid overshooting the poles
5130 if (dlat > 90)
5131 dlat = 90;
5132 else if (dlat < -90)
5133 dlat = -90;
5134
5135 if (dlon > 360.) dlon -= 360.;
5136 if (dlon < -360.) dlon += 360.;
5137
5138 // This should not really be necessary, but round-trip georef on some
5139 // charts is not perfect, So we can get creep on repeated unidimensional
5140 // pans, and corrupt chart cacheing.......
5141
5142 // But this only works on north-up projections
5143 // TODO: can we remove this now?
5144 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5145 // .001 ) ) {
5146 //
5147 // if( dx == 0 ) dlon = clon;
5148 // if( dy == 0 ) dlat = clat;
5149 // }
5150
5151 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5152
5153 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5154
5155 if (VPoint.b_quilt) {
5156 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5157 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5158 // Tweak the scale slightly for a new ref chart
5159 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5160 if (pc) {
5161 double tweak_scale_ppm =
5162 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5163 SetVPScale(tweak_scale_ppm);
5164 }
5165 }
5166
5167 if (new_ref_dbIndex == -1) {
5168#pragma GCC diagnostic push
5169#pragma GCC diagnostic ignored "-Warray-bounds"
5170 // The compiler sees a -1 index being used. Does not happen, though.
5171
5172 // for whatever reason, no reference chart is known
5173 // Probably panned out of the coverage region
5174 // If any charts are anywhere on-screen, choose the smallest
5175 // scale chart on the screen to be a new reference chart.
5176 int trial_index = -1;
5177 if (m_pCurrentStack->nEntry) {
5178 int trial_index =
5179 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5180 }
5181
5182 if (trial_index < 0) {
5183 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5184 if (full_screen_array.size())
5185 trial_index = full_screen_array[full_screen_array.size() - 1];
5186 }
5187
5188 if (trial_index >= 0) {
5189 m_pQuilt->SetReferenceChart(trial_index);
5190 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5191 VPoint.rotation);
5192 ReloadVP();
5193 }
5194#pragma GCC diagnostic pop
5195 }
5196 }
5197
5198 // Turn off bFollow only if the ownship has left the screen
5199 if (m_bFollow) {
5200 double offx, offy;
5201 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5202
5203 double offset_angle = atan2(offy, offx);
5204 double offset_distance = sqrt((offy * offy) + (offx * offx));
5205 double chart_angle = GetVPRotation();
5206 double target_angle = chart_angle - offset_angle;
5207 double d_east_mod = offset_distance * cos(target_angle);
5208 double d_north_mod = offset_distance * sin(target_angle);
5209
5210 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5211 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5212
5213 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5214 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5215 m_bFollow = false; // update the follow flag
5216 UpdateFollowButtonState();
5217 }
5218 }
5219
5220 Refresh(false);
5221
5222 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5223
5224 return true;
5225}
5226
5227bool ChartCanvas::IsOwnshipOnScreen() {
5228 wxPoint r;
5230 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5231 ((r.y > 0) && r.y < GetCanvasHeight()))
5232 return true;
5233 else
5234 return false;
5235}
5236
5237void ChartCanvas::ReloadVP(bool b_adjust) {
5238 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5239
5240 LoadVP(VPoint, b_adjust);
5241}
5242
5243void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5244#ifdef ocpnUSE_GL
5245 if (g_bopengl && m_glcc) {
5246 m_glcc->Invalidate();
5247 if (m_glcc->GetSize() != GetSize()) {
5248 m_glcc->SetSize(GetSize());
5249 }
5250 } else
5251#endif
5252 {
5253 m_cache_vp.Invalidate();
5254 m_bm_cache_vp.Invalidate();
5255 }
5256
5257 VPoint.Invalidate();
5258
5259 if (m_pQuilt) m_pQuilt->Invalidate();
5260
5261 // Make sure that the Selected Group is sensible...
5262 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5263 // m_groupIndex = 0;
5264 // if( !CheckGroup( m_groupIndex ) )
5265 // m_groupIndex = 0;
5266
5267 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5268 vp.m_projection_type, b_adjust);
5269}
5270
5271void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5272 m_pQuilt->SetReferenceChart(dbIndex);
5273 VPoint.Invalidate();
5274 m_pQuilt->Invalidate();
5275}
5276
5277double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5278 if (m_pQuilt)
5279 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5280 else
5281 return vp.view_scale_ppm;
5282}
5283
5284// Verify and adjust the current reference chart,
5285// so that it will not lead to excessive overzoom or underzoom onscreen
5286int ChartCanvas::AdjustQuiltRefChart() {
5287 int ret = -1;
5288 if (m_pQuilt) {
5289 wxASSERT(ChartData);
5290 ChartBase *pc =
5291 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5292 if (pc) {
5293 double min_ref_scale =
5294 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5295 double max_ref_scale =
5296 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5297
5298 if (VPoint.chart_scale < min_ref_scale) {
5299 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5300 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5301 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5302 } else {
5303 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5304
5305 if (!brender_ok) {
5306 int target_stack_index = wxNOT_FOUND;
5307 int il = 0;
5308 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5309 if (index == m_pQuilt->GetRefChartdbIndex()) {
5310 target_stack_index = il;
5311 break;
5312 }
5313 il++;
5314 }
5315 if (wxNOT_FOUND == target_stack_index) // should never happen...
5316 target_stack_index = 0;
5317
5318 int ref_family = pc->GetChartFamily();
5319 int extended_array_count =
5320 m_pQuilt->GetExtendedStackIndexArray().size();
5321 while ((!brender_ok) &&
5322 ((int)target_stack_index < (extended_array_count - 1))) {
5323 target_stack_index++;
5324 int test_db_index =
5325 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5326
5327 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5328 IsChartQuiltableRef(test_db_index)) {
5329 // open the target, and check the min_scale
5330 ChartBase *ptest_chart =
5331 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5332 if (ptest_chart) {
5333 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5334 }
5335 }
5336 }
5337
5338 if (brender_ok) { // found a better reference chart
5339 int new_db_index =
5340 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5341 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5342 IsChartQuiltableRef(new_db_index)) {
5343 m_pQuilt->SetReferenceChart(new_db_index);
5344 ret = new_db_index;
5345 } else
5346 ret = m_pQuilt->GetRefChartdbIndex();
5347 } else
5348 ret = m_pQuilt->GetRefChartdbIndex();
5349
5350 } else
5351 ret = m_pQuilt->GetRefChartdbIndex();
5352 }
5353 } else
5354 ret = -1;
5355 }
5356
5357 return ret;
5358}
5359
5360void ChartCanvas::UpdateCanvasOnGroupChange() {
5361 delete m_pCurrentStack;
5362 m_pCurrentStack = new ChartStack;
5363 wxASSERT(ChartData);
5364 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5365 m_groupIndex);
5366
5367 if (m_pQuilt) {
5368 m_pQuilt->Compose(VPoint);
5369 SetFocus();
5370 }
5371}
5372
5373bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5374 double latNE, double lonNE) {
5375 // Center Point
5376 double latc = (latSW + latNE) / 2.0;
5377 double lonc = (lonSW + lonNE) / 2.0;
5378
5379 // Get scale in ppm (latitude)
5380 double ne_easting, ne_northing;
5381 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5382
5383 double sw_easting, sw_northing;
5384 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5385
5386 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5387
5388 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5389}
5390
5391bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5392 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5393 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5394}
5395
5396bool ChartCanvas::SetVPProjection(int projection) {
5397 if (!g_bopengl) // alternative projections require opengl
5398 return false;
5399
5400 // the view scale varies depending on geographic location and projection
5401 // rescale to keep the relative scale on the screen the same
5402 double prev_true_scale_ppm = m_true_scale_ppm;
5403 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5404 VPoint.skew, VPoint.rotation, projection) &&
5405 SetVPScale(wxMax(
5406 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5407 m_absolute_min_scale_ppm));
5408}
5409
5410bool ChartCanvas::SetViewPoint(double lat, double lon) {
5411 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5412 VPoint.rotation);
5413}
5414
5415bool ChartCanvas::SetVPRotation(double angle) {
5416 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5417 VPoint.skew, angle);
5418}
5419bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5420 double skew, double rotation, int projection,
5421 bool b_adjust, bool b_refresh) {
5422 if (ChartData->IsBusy()) return false;
5423 bool b_ret = false;
5424 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5425 skew -= 2 * PI;
5426 // Any sensible change?
5427 if (VPoint.IsValid()) {
5428 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5429 (fabs(VPoint.skew - skew) < 1e-9) &&
5430 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5431 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5432 (VPoint.m_projection_type == projection ||
5433 projection == PROJECTION_UNKNOWN))
5434 return false;
5435 }
5436 if (VPoint.m_projection_type != projection)
5437 VPoint.InvalidateTransformCache(); // invalidate
5438
5439 // Take a local copy of the last viewport
5440 ViewPort last_vp = VPoint;
5441
5442 VPoint.skew = skew;
5443 VPoint.clat = lat;
5444 VPoint.clon = lon;
5445 VPoint.rotation = rotation;
5446 VPoint.view_scale_ppm = scale_ppm;
5447 if (projection != PROJECTION_UNKNOWN)
5448 VPoint.SetProjectionType(projection);
5449 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5450 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5451
5452 // don't allow latitude above 88 for mercator (90 is infinity)
5453 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5454 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5455 if (VPoint.clat > 89.5)
5456 VPoint.clat = 89.5;
5457 else if (VPoint.clat < -89.5)
5458 VPoint.clat = -89.5;
5459 }
5460
5461 // don't zoom out too far for transverse mercator polyconic until we resolve
5462 // issues
5463 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5464 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5465 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5466
5467 // SetVPRotation(rotation);
5468
5469 if (!g_bopengl) // tilt is not possible without opengl
5470 VPoint.tilt = 0;
5471
5472 if ((VPoint.pix_width <= 0) ||
5473 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5474 return false;
5475
5476 bool bwasValid = VPoint.IsValid();
5477 VPoint.Validate(); // Mark this ViewPoint as OK
5478
5479 // Has the Viewport scale changed? If so, invalidate the vp
5480 if (last_vp.view_scale_ppm != scale_ppm) {
5481 m_cache_vp.Invalidate();
5482 InvalidateGL();
5483 }
5484
5485 // A preliminary value, may be tweaked below
5486 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5487
5488 // recompute cursor position
5489 // and send to interested plugins if the mouse is actually in this window
5490 if (top_frame::Get()->GetCanvasIndexUnderMouse() == m_canvasIndex) {
5491 int mouseX = mouse_x;
5492 int mouseY = mouse_y;
5493 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5494 (mouseY < VPoint.pix_height)) {
5495 double lat_mouse, lon_mouse;
5496 GetCanvasPixPoint(mouseX, mouseY, lat_mouse, lon_mouse);
5497 m_cursor_lat = lat_mouse;
5498 m_cursor_lon = lon_mouse;
5499 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
5500 }
5501 }
5502
5503 if (!VPoint.b_quilt && m_singleChart) {
5504 VPoint.SetBoxes();
5505
5506 // Allow the chart to adjust the new ViewPort for performance optimization
5507 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5508 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5509
5510 // If there is a sensible change in the chart render, refresh the whole
5511 // screen
5512 if ((!m_cache_vp.IsValid()) ||
5513 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5514 Refresh(false);
5515 b_ret = true;
5516 } else {
5517 wxPoint cp_last, cp_this;
5518 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5519 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5520
5521 if (cp_last != cp_this) {
5522 Refresh(false);
5523 b_ret = true;
5524 }
5525 }
5526 // Create the stack
5527 if (m_pCurrentStack) {
5528 assert(ChartData != 0);
5529 int current_db_index;
5530 current_db_index =
5531 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5532
5533 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5534 m_groupIndex);
5535 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5536 }
5537
5538 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5539 }
5540
5541 // Handle the quilted case
5542 if (VPoint.b_quilt) {
5543 VPoint.SetBoxes();
5544
5545 if (last_vp.view_scale_ppm != scale_ppm)
5546 m_pQuilt->InvalidateAllQuiltPatchs();
5547
5548 // Create the quilt
5549 if (ChartData /*&& ChartData->IsValid()*/) {
5550 if (!m_pCurrentStack) return false;
5551
5552 int current_db_index;
5553 current_db_index =
5554 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5555
5556 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5557 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5558
5559 // Check to see if the current quilt reference chart is in the new stack
5560 int current_ref_stack_index = -1;
5561 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5562 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5563 current_ref_stack_index = i;
5564 }
5565
5566 if (g_bFullScreenQuilt) {
5567 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5568 }
5569
5570 // We might need a new Reference Chart
5571 bool b_needNewRef = false;
5572
5573 // If the new stack does not contain the current ref chart....
5574 if ((-1 == current_ref_stack_index) &&
5575 (m_pQuilt->GetRefChartdbIndex() >= 0))
5576 b_needNewRef = true;
5577
5578 // Would the current Ref Chart be excessively underzoomed?
5579 // We need to check this here to be sure, since we cannot know where the
5580 // reference chart was assigned. For instance, the reference chart may
5581 // have been selected from the config file, or from a long jump with a
5582 // chart family switch implicit. Anyway, we check to be sure....
5583 bool renderable = true;
5584 ChartBase *referenceChart =
5585 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5586 if (referenceChart) {
5587 double chartMaxScale = referenceChart->GetNormalScaleMax(
5588 GetCanvasScaleFactor(), GetCanvasWidth());
5589 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5590 }
5591 if (!renderable) b_needNewRef = true;
5592
5593 // Need new refchart?
5594 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5595 const ChartTableEntry &cte_ref =
5596 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5597 int target_scale = cte_ref.GetScale();
5598 int target_type = cte_ref.GetChartType();
5599 int candidate_stack_index;
5600
5601 // reset the ref chart in a way that does not lead to excessive
5602 // underzoom, for performance reasons Try to find a chart that is the
5603 // same type, and has a scale of just smaller than the current ref
5604 // chart
5605
5606 candidate_stack_index = 0;
5607 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5608 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5609 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5610 int candidate_scale = cte_candidate.GetScale();
5611 int candidate_type = cte_candidate.GetChartType();
5612
5613 if ((candidate_scale >= target_scale) &&
5614 (candidate_type == target_type)) {
5615 bool renderable = true;
5616 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5617 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5618 if (tentative_referenceChart) {
5619 double chartMaxScale =
5620 tentative_referenceChart->GetNormalScaleMax(
5621 GetCanvasScaleFactor(), GetCanvasWidth());
5622 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5623 }
5624
5625 if (renderable) break;
5626 }
5627
5628 candidate_stack_index++;
5629 }
5630
5631 // If that did not work, look for a chart of just larger scale and
5632 // same type
5633 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5634 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5635 while (candidate_stack_index >= 0) {
5636 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5637 if (idx >= 0) {
5638 const ChartTableEntry &cte_candidate =
5639 ChartData->GetChartTableEntry(idx);
5640 int candidate_scale = cte_candidate.GetScale();
5641 int candidate_type = cte_candidate.GetChartType();
5642
5643 if ((candidate_scale <= target_scale) &&
5644 (candidate_type == target_type))
5645 break;
5646 }
5647 candidate_stack_index--;
5648 }
5649 }
5650
5651 // and if that did not work, chose stack entry 0
5652 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5653 (candidate_stack_index < 0))
5654 candidate_stack_index = 0;
5655
5656 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5657
5658 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5659 }
5660
5661 if (!g_bopengl) {
5662 // Preset the VPoint projection type to match what the quilt projection
5663 // type will be
5664 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5665
5666 // Always keep the default Mercator projection if the reference chart is
5667 // not in the PatchList or the scale is too small for it to render.
5668
5669 bool renderable = true;
5670 ChartBase *referenceChart =
5671 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5672 if (referenceChart) {
5673 double chartMaxScale = referenceChart->GetNormalScaleMax(
5674 GetCanvasScaleFactor(), GetCanvasWidth());
5675 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5676 proj = ChartData->GetDBChartProj(ref_db_index);
5677 } else
5678 proj = PROJECTION_MERCATOR;
5679
5680 VPoint.b_MercatorProjectionOverride =
5681 (m_pQuilt->GetnCharts() == 0 || !renderable);
5682
5683 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5684
5685 VPoint.SetProjectionType(proj);
5686 }
5687
5688 // If this quilt will be a perceptible delta from the existing quilt,
5689 // then refresh the entire screen
5690 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5691 // Allow the quilt to adjust the new ViewPort for performance
5692 // optimization This will normally be only a fractional (i.e.
5693 // sub-pixel) adjustment...
5694 if (b_adjust) {
5695 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5696 }
5697
5698 // ChartData->ClearCacheInUseFlags();
5699 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5700
5701 // wxStopWatch sw;
5702
5703#ifdef __ANDROID__
5704 // This is an optimization for panning on touch screen systems.
5705 // The quilt composition is deferred until the OnPaint() message gets
5706 // finally removed and processed from the message queue.
5707 // Takes advantage of the fact that touch-screen pan gestures are
5708 // usually short in distance,
5709 // so not requiring a full quilt rebuild until the pan gesture is
5710 // complete.
5711 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5712 // qDebug() << "Force compose";
5713 m_pQuilt->Compose(VPoint);
5714 } else {
5715 m_pQuilt->Invalidate();
5716 }
5717#else
5718 m_pQuilt->Compose(VPoint);
5719#endif
5720
5721 // printf("comp time %ld\n", sw.Time());
5722
5723 // If the extended chart stack has changed, invalidate any cached
5724 // render bitmap
5725 // if(m_pQuilt->GetXStackHash() != hash1) {
5726 // m_bm_cache_vp.Invalidate();
5727 // InvalidateGL();
5728 // }
5729
5730 ChartData->PurgeCacheUnusedCharts(0.7);
5731
5732 if (b_refresh) Refresh(false);
5733
5734 b_ret = true;
5735 }
5736 }
5737
5738 VPoint.skew = 0.; // Quilting supports 0 Skew
5739 } else if (!g_bopengl) {
5740 OcpnProjType projection = PROJECTION_UNKNOWN;
5741 if (m_singleChart) // viewport projection must match chart projection
5742 // without opengl
5743 projection = m_singleChart->GetChartProjectionType();
5744 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5745 VPoint.SetProjectionType(projection);
5746 }
5747
5748 // Has the Viewport projection changed? If so, invalidate the vp
5749 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5750 m_cache_vp.Invalidate();
5751 InvalidateGL();
5752 }
5753
5754 UpdateCanvasControlBar(); // Refresh the Piano
5755
5756 VPoint.chart_scale = 1.0; // fallback default value
5757
5758 if (VPoint.GetBBox().GetValid()) {
5759 // Update the viewpoint reference scale
5760 if (m_singleChart)
5761 VPoint.ref_scale = m_singleChart->GetNativeScale();
5762 else {
5763#ifdef __ANDROID__
5764 // This is an optimization for panning on touch screen systems.
5765 // See above.
5766 // Quilt might not be fully composed at this point, so for cm93
5767 // the reference scale may not be known.
5768 // In this case, do not update the VP ref_scale.
5769 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5770 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5771 }
5772#else
5773 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5774#endif
5775 }
5776
5777 // Calculate the on-screen displayed actual scale
5778 // by a simple traverse northward from the center point
5779 // of roughly one eighth of the canvas height
5780 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5781
5782 double delta_check =
5783 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5784 delta_check /= 8.;
5785
5786 double check_point = wxMin(89., VPoint.clat);
5787
5788 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5789
5790 double rhumbDist;
5791 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5792 VPoint.clon, 0, &rhumbDist);
5793
5794 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5795 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5796 // Calculate the distance between r1 and r in physical pixels.
5797 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5798 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5799
5800 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5801
5802 // A fall back in case of very high zoom-out, giving delta_y == 0
5803 // which can probably only happen with vector charts
5804 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5805
5806 // Another fallback, for highly zoomed out charts
5807 // This adjustment makes the displayed TrueScale correspond to the
5808 // same algorithm used to calculate the chart zoom-out limit for
5809 // ChartDummy.
5810 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5811
5812 if (m_true_scale_ppm)
5813 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5814 else
5815 VPoint.chart_scale = 1.0;
5816
5817 // Create a nice renderable string
5818 double round_factor = 1000.;
5819 if (VPoint.chart_scale <= 1000.)
5820 round_factor = 10.;
5821 else if (VPoint.chart_scale <= 10000.)
5822 round_factor = 100.;
5823 else if (VPoint.chart_scale <= 100000.)
5824 round_factor = 1000.;
5825
5826 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5827 double retina_coef = 1;
5828#ifdef ocpnUSE_GL
5829#ifdef __WXOSX__
5830 if (g_bopengl) {
5831 retina_coef = GetContentScaleFactor();
5832 }
5833#endif
5834#endif
5835
5836 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5837 // rounded to the nearest 10, 100 or 1000.
5838 //
5839 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5840 // true_scale_display. That does not make sense. The chart scale should be
5841 // the same as the true scale within the limits of the rounding factor.
5842 double true_scale_display =
5843 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5844 wxString text;
5845
5846 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5847
5848 if (m_displayed_scale_factor > 10.0)
5849 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5850 m_displayed_scale_factor);
5851 else if (m_displayed_scale_factor > 1.0)
5852 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5853 m_displayed_scale_factor);
5854 else if (m_displayed_scale_factor > 0.1) {
5855 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5856 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5857 } else if (m_displayed_scale_factor > 0.01) {
5858 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5859 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5860 } else {
5861 text.Printf(
5862 "%s %4.0f (---)", _("Scale"),
5863 true_scale_display); // Generally, no chart, so no chart scale factor
5864 }
5865
5866 m_scaleValue = true_scale_display;
5867 m_scaleText = text;
5868 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5869
5870 if (m_bShowScaleInStatusBar && top_frame::Get()->GetStatusBar() &&
5871 (top_frame::Get()->GetStatusBar()->GetFieldsCount() >
5872 STAT_FIELD_SCALE)) {
5873 // Check to see if the text will fit in the StatusBar field...
5874 bool b_noshow = false;
5875 {
5876 int w = 0;
5877 int h;
5878 wxClientDC dc(top_frame::Get()->GetStatusBar());
5879 if (dc.IsOk()) {
5880 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5881 dc.SetFont(*templateFont);
5882 dc.GetTextExtent(text, &w, &h);
5883
5884 // If text is too long for the allocated field, try to reduce the text
5885 // string a bit.
5886 wxRect rect;
5887 top_frame::Get()->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE,
5888 rect);
5889 if (w && w > rect.width) {
5890 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5891 }
5892
5893 // Test again...if too big still, then give it up.
5894 dc.GetTextExtent(text, &w, &h);
5895
5896 if (w && w > rect.width) {
5897 b_noshow = true;
5898 }
5899 }
5900 }
5901
5902 if (!b_noshow) top_frame::Get()->SetStatusText(text, STAT_FIELD_SCALE);
5903 }
5904 }
5905
5906 // Maintain member vLat/vLon
5907 m_vLat = VPoint.clat;
5908 m_vLon = VPoint.clon;
5909
5910 return b_ret;
5911}
5912
5913// Static Icon definitions for some symbols requiring
5914// scaling/rotation/translation Very specific wxDC draw commands are
5915// necessary to properly render these icons...See the code in
5916// ShipDraw()
5917
5918// This icon was adapted and scaled from the S52 Presentation Library
5919// version 3_03.
5920// Symbol VECGND02
5921
5922static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5923
5924// This ownship icon was adapted and scaled from the S52 Presentation
5925// Library version 3_03 Symbol OWNSHP05
5926static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5927 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5928
5929wxColour ChartCanvas::PredColor() {
5930 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5931 // visibility.
5932 if (SHIP_NORMAL == m_ownship_state)
5933 return GetGlobalColor("URED");
5934
5935 else if (SHIP_LOWACCURACY == m_ownship_state)
5936 return GetGlobalColor("YELO1");
5937
5938 return GetGlobalColor("NODTA");
5939}
5940
5941wxColour ChartCanvas::ShipColor() {
5942 // Establish ship color
5943 // It changes color based on GPS and Chart accuracy/availability
5944
5945 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5946
5947 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5948
5949 return GetGlobalColor("URED"); // default is OK
5950}
5951
5952void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5953 wxPoint2DDouble lShipMidPoint) {
5954 dc.SetPen(wxPen(PredColor(), 2));
5955
5956 if (SHIP_NORMAL == m_ownship_state)
5957 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5958 else
5959 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5960
5961 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5962 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5963
5964 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5965 lShipMidPoint.m_y);
5966 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5967 lShipMidPoint.m_y + 12);
5968}
5969
5970void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5971 wxPoint GPSOffsetPixels,
5972 wxPoint2DDouble lGPSPoint) {
5973 // if (m_animationActive) return;
5974 // Develop a uniform length for course predictor line dash length, based on
5975 // physical display size Use this reference length to size all other graphics
5976 // elements
5977 float ref_dim = m_display_size_mm / 24;
5978 ref_dim = wxMin(ref_dim, 12);
5979 ref_dim = wxMax(ref_dim, 6);
5980
5981 wxColour cPred;
5982 cPred.Set(g_cog_predictor_color);
5983 if (cPred == wxNullColour) cPred = PredColor();
5984
5985 // Establish some graphic element line widths dependent on the platform
5986 // display resolution
5987 // double nominal_line_width_pix = wxMax(1.0,
5988 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5989 // not less than 1 pixel
5990 double nominal_line_width_pix = wxMax(
5991 1.0,
5992 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5993
5994 // If the calculated value is greater than the config file spec value, then
5995 // use it.
5996 if (nominal_line_width_pix > g_cog_predictor_width)
5997 g_cog_predictor_width = nominal_line_width_pix;
5998
5999 // Calculate ownship Position Predictor
6000 wxPoint lPredPoint, lHeadPoint;
6001
6002 float pCog = std::isnan(gCog) ? 0 : gCog;
6003 float pSog = std::isnan(gSog) ? 0 : gSog;
6004
6005 double pred_lat, pred_lon;
6006 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
6007 &pred_lat, &pred_lon);
6008 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
6009
6010 // test to catch the case where COG/HDG line crosses the screen
6011 LLBBox box;
6012
6013 // Should we draw the Head vector?
6014 // Compare the points lHeadPoint and lPredPoint
6015 // If they differ by more than n pixels, and the head vector is valid, then
6016 // render the head vector
6017
6018 float ndelta_pix = 10.;
6019 double hdg_pred_lat, hdg_pred_lon;
6020 bool b_render_hdt = false;
6021 if (!std::isnan(gHdt)) {
6022 // Calculate ownship Heading pointer as a predictor
6023 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
6024 &hdg_pred_lon);
6025 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
6026 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
6027 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
6028 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
6029 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
6030 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
6031 }
6032 }
6033
6034 // draw course over ground if they are longer than the ship
6035 wxPoint lShipMidPoint;
6036 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
6037 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
6038 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
6039 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
6040
6041 if (lpp >= img_height / 2) {
6042 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
6043 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
6044 !std::isnan(gSog)) {
6045 // COG Predictor
6046 float dash_length = ref_dim;
6047 wxDash dash_long[2];
6048 dash_long[0] =
6049 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6050 g_cog_predictor_width); // Long dash , in mm <---------+
6051 dash_long[1] = dash_long[0] / 2.0; // Short gap
6052
6053 // On ultra-hi-res displays, do not allow the dashes to be greater than
6054 // 250, since it is defined as (char)
6055 if (dash_length > 250.) {
6056 dash_long[0] = 250. / g_cog_predictor_width;
6057 dash_long[1] = dash_long[0] / 2;
6058 }
6059
6060 wxPen ppPen2(cPred, g_cog_predictor_width,
6061 (wxPenStyle)g_cog_predictor_style);
6062 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6063 ppPen2.SetDashes(2, dash_long);
6064 dc.SetPen(ppPen2);
6065 dc.StrokeLine(
6066 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6067 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6068
6069 if (g_cog_predictor_width > 1) {
6070 float line_width = g_cog_predictor_width / 3.;
6071
6072 wxDash dash_long3[2];
6073 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6074 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6075
6076 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6077 (wxPenStyle)g_cog_predictor_style);
6078 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6079 ppPen3.SetDashes(2, dash_long3);
6080 dc.SetPen(ppPen3);
6081 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6082 lGPSPoint.m_y + GPSOffsetPixels.y,
6083 lPredPoint.x + GPSOffsetPixels.x,
6084 lPredPoint.y + GPSOffsetPixels.y);
6085 }
6086
6087 if (g_cog_predictor_endmarker) {
6088 // Prepare COG predictor endpoint icon
6089 double png_pred_icon_scale_factor = .4;
6090 if (g_ShipScaleFactorExp > 1.0)
6091 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6092 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6093
6094 wxPoint icon[4];
6095
6096 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6097 (float)(lPredPoint.x - lShipMidPoint.x));
6098 cog_rad += (float)PI;
6099
6100 for (int i = 0; i < 4; i++) {
6101 int j = i * 2;
6102 double pxa = (double)(s_png_pred_icon[j]);
6103 double pya = (double)(s_png_pred_icon[j + 1]);
6104
6105 pya *= png_pred_icon_scale_factor;
6106 pxa *= png_pred_icon_scale_factor;
6107
6108 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6109 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6110
6111 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6112 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6113 }
6114
6115 // Render COG endpoint icon
6116 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6117 wxPENSTYLE_SOLID);
6118 dc.SetPen(ppPen1);
6119 dc.SetBrush(wxBrush(cPred));
6120
6121 dc.StrokePolygon(4, icon);
6122 }
6123 }
6124 }
6125
6126 // HDT Predictor
6127 if (b_render_hdt) {
6128 float hdt_dash_length = ref_dim * 0.4;
6129
6130 cPred.Set(g_ownship_HDTpredictor_color);
6131 if (cPred == wxNullColour) cPred = PredColor();
6132 float hdt_width =
6133 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6134 : g_cog_predictor_width * 0.8);
6135 wxDash dash_short[2];
6136 dash_short[0] =
6137 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6138 hdt_width); // Short dash , in mm <---------+
6139 dash_short[1] =
6140 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6141 hdt_width); // Short gap |
6142
6143 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6144 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6145 ppPen2.SetDashes(2, dash_short);
6146
6147 dc.SetPen(ppPen2);
6148 dc.StrokeLine(
6149 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6150 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6151
6152 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6153 dc.SetPen(ppPen1);
6154 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6155
6156 if (g_ownship_HDTpredictor_endmarker) {
6157 double nominal_circle_size_pixels = wxMax(
6158 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6159
6160 // Scale the circle to ChartScaleFactor, slightly softened....
6161 if (g_ShipScaleFactorExp > 1.0)
6162 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6163
6164 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6165 lHeadPoint.y + GPSOffsetPixels.y,
6166 nominal_circle_size_pixels / 2);
6167 }
6168 }
6169
6170 // Draw radar rings if activated
6171 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6172 double factor = 1.00;
6173 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6174 factor = 1 / 1.852;
6175 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6176 if (std::isnan(gSog))
6177 factor = 0.0;
6178 else
6179 factor = gSog / 60;
6180 }
6181 factor *= g_fNavAidRadarRingsStep;
6182
6183 double tlat, tlon;
6184 wxPoint r;
6185 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6186 GetCanvasPointPix(tlat, tlon, &r);
6187
6188 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6189 pow((double)(lGPSPoint.m_y - r.y), 2));
6190 int pix_radius = (int)lpp;
6191
6192 wxColor rangeringcolour =
6193 user_colors::GetDimColor(g_colourOwnshipRangeRingsColour);
6194
6195 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6196
6197 dc.SetPen(ppPen1);
6198 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6199
6200 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6201 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6202 }
6203}
6204
6205#if ocpnUSE_GL
6206void ChartCanvas::ResetGlGridFont() { GetglCanvas()->ResetGridFont(); }
6207bool ChartCanvas::CanAccelerateGlPanning() {
6208 return GetglCanvas()->CanAcceleratePanning();
6209}
6210void ChartCanvas::SetupGlCompression() { GetglCanvas()->SetupCompression(); }
6211
6212#else
6213void ChartCanvas::ResetGlGridFont() {}
6214bool ChartCanvas::CanAccelerateGlPanning() { return false; }
6215void ChartCanvas::SetupGlCompression() {}
6216#endif
6217
6218void ChartCanvas::ComputeShipScaleFactor(
6219 float icon_hdt, int ownShipWidth, int ownShipLength,
6220 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6221 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6222 float screenResolution = m_pix_per_mm;
6223
6224 // Calculate the true ship length in exact pixels
6225 double ship_bow_lat, ship_bow_lon;
6226 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6227 &ship_bow_lat, &ship_bow_lon);
6228 wxPoint lShipBowPoint;
6229 wxPoint2DDouble b_point =
6230 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6231 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6232
6233 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6234 powf((float)(b_point.m_y - a_point.m_y), 2));
6235
6236 // And in mm
6237 float shipLength_mm = shipLength_px / screenResolution;
6238
6239 // Set minimum ownship drawing size
6240 float ownship_min_mm = g_n_ownship_min_mm;
6241 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6242
6243 // Calculate Nautical Miles distance from midships to gps antenna
6244 float hdt_ant = icon_hdt + 180.;
6245 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6246 float dx = g_n_gps_antenna_offset_x / 1852.;
6247 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6248 {
6249 hdt_ant = icon_hdt;
6250 dy = -dy;
6251 }
6252
6253 // If the drawn ship size is going to be clamped, adjust the gps antenna
6254 // offsets
6255 if (shipLength_mm < ownship_min_mm) {
6256 dy /= shipLength_mm / ownship_min_mm;
6257 dx /= shipLength_mm / ownship_min_mm;
6258 }
6259
6260 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6261
6262 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6263 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6264 &ship_mid_lon1);
6265
6266 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6267 &lShipMidPoint);
6268
6269 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6270 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6271
6272 float scale_factor = shipLength_px / ownShipLength;
6273
6274 // Calculate a scale factor that would produce a reasonably sized icon
6275 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6276
6277 // And choose the correct one
6278 scale_factor = wxMax(scale_factor, scale_factor_min);
6279
6280 scale_factor_y = scale_factor;
6281 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6282 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6283}
6284
6285void ChartCanvas::ShipDraw(ocpnDC &dc) {
6286 if (!GetVP().IsValid()) return;
6287
6288 wxPoint GPSOffsetPixels(0, 0);
6289 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6290
6291 // COG/SOG may be undefined in NMEA data stream
6292 float pCog = std::isnan(gCog) ? 0 : gCog;
6293 float pSog = std::isnan(gSog) ? 0 : gSog;
6294
6295 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6296
6297 lShipMidPoint = lGPSPoint;
6298
6299 // Draw the icon rotated to the COG
6300 // or to the Hdt if available
6301 float icon_hdt = pCog;
6302 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6303
6304 // COG may be undefined in NMEA data stream
6305 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6306
6307 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6308 // predictor
6309 double osd_head_lat, osd_head_lon;
6310 wxPoint osd_head_point;
6311
6312 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6313 &osd_head_lon);
6314
6315 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6316
6317 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6318 (float)(osd_head_point.x - lShipMidPoint.m_x));
6319 icon_rad += (float)PI;
6320
6321 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6322
6323 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6324 // nominal size and is just barely outside the viewport ....
6325 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6326
6327 // TODO: fix to include actual size of boat that will be rendered
6328 int img_height = 0;
6329 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6330 if (GetVP().chart_scale >
6331 300000) // According to S52, this should be 50,000
6332 {
6333 ShipDrawLargeScale(dc, lShipMidPoint);
6334 img_height = 20;
6335 } else {
6336 wxImage pos_image;
6337
6338 // Substitute user ownship image if found
6339 if (m_pos_image_user)
6340 pos_image = m_pos_image_user->Copy();
6341 else if (SHIP_NORMAL == m_ownship_state)
6342 pos_image = m_pos_image_red->Copy();
6343 if (SHIP_LOWACCURACY == m_ownship_state)
6344 pos_image = m_pos_image_yellow->Copy();
6345 else if (SHIP_NORMAL != m_ownship_state)
6346 pos_image = m_pos_image_grey->Copy();
6347
6348 // Substitute user ownship image if found
6349 if (m_pos_image_user) {
6350 pos_image = m_pos_image_user->Copy();
6351
6352 if (SHIP_LOWACCURACY == m_ownship_state)
6353 pos_image = m_pos_image_user_yellow->Copy();
6354 else if (SHIP_NORMAL != m_ownship_state)
6355 pos_image = m_pos_image_user_grey->Copy();
6356 }
6357
6358 img_height = pos_image.GetHeight();
6359
6360 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6361 g_OwnShipIconType > 0) // use large ship
6362 {
6363 int ownShipWidth = 22; // Default values from s_ownship_icon
6364 int ownShipLength = 84;
6365 if (g_OwnShipIconType == 1) {
6366 ownShipWidth = pos_image.GetWidth();
6367 ownShipLength = pos_image.GetHeight();
6368 }
6369
6370 float scale_factor_x, scale_factor_y;
6371 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6372 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6373 scale_factor_x, scale_factor_y);
6374
6375 if (g_OwnShipIconType == 1) { // Scaled bitmap
6376 pos_image.Rescale(ownShipWidth * scale_factor_x,
6377 ownShipLength * scale_factor_y,
6378 wxIMAGE_QUALITY_HIGH);
6379 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6380 wxImage rot_image =
6381 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6382
6383 // Simple sharpening algorithm.....
6384 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6385 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6386 if (rot_image.GetAlpha(ip, jp) > 64)
6387 rot_image.SetAlpha(ip, jp, 255);
6388
6389 wxBitmap os_bm(rot_image);
6390
6391 int w = os_bm.GetWidth();
6392 int h = os_bm.GetHeight();
6393 img_height = h;
6394
6395 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6396 lShipMidPoint.m_y - h / 2, true);
6397
6398 // Maintain dirty box,, missing in __WXMSW__ library
6399 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6400 lShipMidPoint.m_y - h / 2);
6401 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6402 lShipMidPoint.m_y - h / 2 + h);
6403 }
6404
6405 else if (g_OwnShipIconType == 2) { // Scaled Vector
6406 wxPoint ownship_icon[10];
6407
6408 for (int i = 0; i < 10; i++) {
6409 int j = i * 2;
6410 float pxa = (float)(s_ownship_icon[j]);
6411 float pya = (float)(s_ownship_icon[j + 1]);
6412 pya *= scale_factor_y;
6413 pxa *= scale_factor_x;
6414
6415 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6416 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6417
6418 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6419 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6420 }
6421
6422 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6423 dc.SetPen(ppPen1);
6424 dc.SetBrush(wxBrush(ShipColor()));
6425
6426 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6427
6428 // draw reference point (midships) cross
6429 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6430 ownship_icon[7].y);
6431 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6432 ownship_icon[9].y);
6433 }
6434
6435 img_height = ownShipLength * scale_factor_y;
6436
6437 // Reference point, where the GPS antenna is
6438 int circle_rad = 3;
6439 if (m_pos_image_user) circle_rad = 1;
6440
6441 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6442 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6443 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6444 } else { // Fixed bitmap icon.
6445 /* non opengl, or suboptimal opengl via ocpndc: */
6446 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6447 wxImage rot_image =
6448 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6449
6450 // Simple sharpening algorithm.....
6451 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6452 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6453 if (rot_image.GetAlpha(ip, jp) > 64)
6454 rot_image.SetAlpha(ip, jp, 255);
6455
6456 wxBitmap os_bm(rot_image);
6457
6458 if (g_ShipScaleFactorExp > 1) {
6459 wxImage scaled_image = os_bm.ConvertToImage();
6460 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6461 1.0; // soften the scale factor a bit
6462 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6463 scaled_image.GetHeight() * factor,
6464 wxIMAGE_QUALITY_HIGH));
6465 }
6466 int w = os_bm.GetWidth();
6467 int h = os_bm.GetHeight();
6468 img_height = h;
6469
6470 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6471 lShipMidPoint.m_y - h / 2, true);
6472
6473 // Reference point, where the GPS antenna is
6474 int circle_rad = 3;
6475 if (m_pos_image_user) circle_rad = 1;
6476
6477 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6478 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6479 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6480
6481 // Maintain dirty box,, missing in __WXMSW__ library
6482 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6483 lShipMidPoint.m_y - h / 2);
6484 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6485 lShipMidPoint.m_y - h / 2 + h);
6486 }
6487 } // ownship draw
6488 }
6489
6490 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6491}
6492
6493/* @ChartCanvas::CalcGridSpacing
6494 **
6495 ** Calculate the major and minor spacing between the lat/lon grid
6496 **
6497 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6498 *window
6499 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6500 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6501 ** @return [void]
6502 */
6503void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6504 float &MinorSpacing) {
6505 // table for calculating the distance between the grids
6506 // [0] view_scale ppm
6507 // [1] spacing between major grid lines in degrees
6508 // [2] spacing between minor grid lines in degrees
6509 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6510 {.000001f, 45.0f, 15.0f},
6511 {.0002f, 30.0f, 10.0f},
6512 {.0003f, 10.0f, 2.0f},
6513 {.0008f, 5.0f, 1.0f},
6514 {.001f, 2.0f, 30.0f / 60.0f},
6515 {.003f, 1.0f, 20.0f / 60.0f},
6516 {.006f, 0.5f, 10.0f / 60.0f},
6517 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6518 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6519 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6520 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6521 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6522 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6523 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6524 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6525
6526 unsigned int tabi;
6527 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6528 if (view_scale_ppm < lltab[tabi][0]) break;
6529 MajorSpacing = lltab[tabi][1]; // major latitude distance
6530 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6531 return;
6532}
6533/* @ChartCanvas::CalcGridText *************************************
6534 **
6535 ** Calculates text to display at the major grid lines
6536 **
6537 ** @param [r] latlon [float] latitude or longitude of grid line
6538 ** @param [r] spacing [float] distance between two major grid lines
6539 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6540 **
6541 ** @return
6542 */
6543
6544wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6545 int deg = (int)fabs(latlon); // degrees
6546 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6547 char postfix;
6548
6549 // calculate postfix letter (NSEW)
6550 if (latlon > 0.0) {
6551 if (bPostfix) {
6552 postfix = 'N';
6553 } else {
6554 postfix = 'E';
6555 }
6556 } else if (latlon < 0.0) {
6557 if (bPostfix) {
6558 postfix = 'S';
6559 } else {
6560 postfix = 'W';
6561 }
6562 } else {
6563 postfix = ' '; // no postfix for equator and greenwich
6564 }
6565 // calculate text, display minutes only if spacing is smaller than one degree
6566
6567 wxString ret;
6568 if (spacing >= 1.0) {
6569 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6570 } else if (spacing >= (1.0 / 60.0)) {
6571 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6572 } else {
6573 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6574 }
6575
6576 return ret;
6577}
6578
6579/* @ChartCanvas::GridDraw *****************************************
6580 **
6581 ** Draws major and minor Lat/Lon Grid on the chart
6582 ** - distance between Grid-lm ines are calculated automatic
6583 ** - major grid lines will be across the whole chart window
6584 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6585 **
6586 ** @param [w] dc [wxDC&] the wx drawing context
6587 **
6588 ** @return [void]
6589 ************************************************************************/
6590void ChartCanvas::GridDraw(ocpnDC &dc) {
6591 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6592
6593 double nlat, elon, slat, wlon;
6594 float lat, lon;
6595 float dlon;
6596 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6597 wxCoord w, h;
6598 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6599 dc.SetPen(GridPen);
6600 if (!m_pgridFont) SetupGridFont();
6601 dc.SetFont(*m_pgridFont);
6602 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6603
6604 w = m_canvas_width;
6605 h = m_canvas_height;
6606
6607 GetCanvasPixPoint(0, 0, nlat,
6608 wlon); // get lat/lon of upper left point of the window
6609 GetCanvasPixPoint(w, h, slat,
6610 elon); // get lat/lon of lower right point of the window
6611 dlon =
6612 elon -
6613 wlon; // calculate how many degrees of longitude are shown in the window
6614 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6615 {
6616 dlon = dlon + 360.0;
6617 }
6618 // calculate distance between latitude grid lines
6619 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6620
6621 // calculate position of first major latitude grid line
6622 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6623
6624 // Draw Major latitude grid lines and text
6625 while (lat < nlat) {
6626 wxPoint r;
6627 wxString st =
6628 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6629 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6630 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6631 dc.DrawText(st, 0, r.y); // draw text
6632 lat = lat + gridlatMajor;
6633
6634 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6635 }
6636
6637 // calculate position of first minor latitude grid line
6638 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6639
6640 // Draw minor latitude grid lines
6641 while (lat < nlat) {
6642 wxPoint r;
6643 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6644 dc.DrawLine(0, r.y, 10, r.y, false);
6645 dc.DrawLine(w - 10, r.y, w, r.y, false);
6646 lat = lat + gridlatMinor;
6647 }
6648
6649 // calculate distance between grid lines
6650 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6651
6652 // calculate position of first major latitude grid line
6653 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6654
6655 // draw major longitude grid lines
6656 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6657 wxPoint r;
6658 wxString st = CalcGridText(lon, gridlonMajor, false);
6659 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6660 dc.DrawLine(r.x, 0, r.x, h, false);
6661 dc.DrawText(st, r.x, 0);
6662 lon = lon + gridlonMajor;
6663 if (lon > 180.0) {
6664 lon = lon - 360.0;
6665 }
6666
6667 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6668 }
6669
6670 // calculate position of first minor longitude grid line
6671 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6672 // draw minor longitude grid lines
6673 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6674 wxPoint r;
6675 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6676 dc.DrawLine(r.x, 0, r.x, 10, false);
6677 dc.DrawLine(r.x, h - 10, r.x, h, false);
6678 lon = lon + gridlonMinor;
6679 if (lon > 180.0) {
6680 lon = lon - 360.0;
6681 }
6682 }
6683}
6684
6685void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6686 if (0 ) {
6687 double blat, blon, tlat, tlon;
6688 wxPoint r;
6689
6690 int x_origin = m_bDisplayGrid ? 60 : 20;
6691 int y_origin = m_canvas_height - 50;
6692
6693 float dist;
6694 int count;
6695 wxPen pen1, pen2;
6696
6697 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6698 {
6699 dist = 10.0;
6700 count = 5;
6701 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6702 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6703 } else // Draw 1 mile scale as SCALEB10
6704 {
6705 dist = 1.0;
6706 count = 10;
6707 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6708 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6709 }
6710
6711 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6712 double rotation = -VPoint.rotation;
6713
6714 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6715 GetCanvasPointPix(tlat, tlon, &r);
6716 int l1 = (y_origin - r.y) / count;
6717
6718 for (int i = 0; i < count; i++) {
6719 int y = l1 * i;
6720 if (i & 1)
6721 dc.SetPen(pen1);
6722 else
6723 dc.SetPen(pen2);
6724
6725 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6726 }
6727 } else {
6728 double blat, blon, tlat, tlon;
6729
6730 int x_origin = 5.0 * GetPixPerMM();
6731 int chartbar_height = GetChartbarHeight();
6732 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6733 // if (style->chartStatusWindowTransparent)
6734 // chartbar_height = 0;
6735 int y_origin = m_canvas_height - chartbar_height - 5;
6736#ifdef __WXOSX__
6737 if (!g_bopengl)
6738 y_origin =
6739 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6740#endif
6741
6742 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6743 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6744
6745 double d;
6746 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6747 d /= 2;
6748
6749 int unit = g_iDistanceFormat;
6750 if (d < .5 &&
6751 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6752 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6753
6754 // nice number
6755 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6756 float places = floor(logdist), rem = logdist - places;
6757 dist = pow(10, places);
6758
6759 if (rem < .2)
6760 dist /= 5;
6761 else if (rem < .5)
6762 dist /= 2;
6763
6764 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6765 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6766 double rotation = -VPoint.rotation;
6767
6768 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6769 &tlat, &tlon);
6770 wxPoint r;
6771 GetCanvasPointPix(tlat, tlon, &r);
6772 int l1 = r.x - x_origin;
6773
6774 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6775 12); // Store this for later reference
6776
6777 dc.SetPen(pen1);
6778
6779 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6780 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6781 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6782
6783 if (!m_pgridFont) SetupGridFont();
6784 dc.SetFont(*m_pgridFont);
6785 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6786 int w, h;
6787 dc.GetTextExtent(s, &w, &h);
6788 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6789 if (g_bopengl) {
6790 w /= dpi_factor;
6791 h /= dpi_factor;
6792 }
6793 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6794 }
6795}
6796
6797void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6798 // Constants?
6799 double da_min = 2.;
6800 double da_max = 6.;
6801 double ra_min = 0.;
6802 double ra_max = 40.;
6803
6804 wxPen pen_save = dc.GetPen();
6805
6806 wxDateTime now = wxDateTime::Now();
6807
6808 dc.SetPen(pen);
6809
6810 int x0, y0, x1, y1;
6811
6812 x0 = x1 = x + radius; // Start point
6813 y0 = y1 = y;
6814 double angle = 0.;
6815 int i = 0;
6816
6817 while (angle < 360.) {
6818 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6819 angle += da;
6820
6821 if (angle > 360.) angle = 360.;
6822
6823 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6824
6825 double r;
6826 if (i & 1)
6827 r = radius + ra;
6828 else
6829 r = radius - ra;
6830
6831 x1 = (int)(x + cos(angle * PI / 180.) * r);
6832 y1 = (int)(y + sin(angle * PI / 180.) * r);
6833
6834 dc.DrawLine(x0, y0, x1, y1);
6835
6836 x0 = x1;
6837 y0 = y1;
6838
6839 i++;
6840 }
6841
6842 dc.DrawLine(x + radius, y, x1, y1); // closure
6843
6844 dc.SetPen(pen_save);
6845}
6846
6847static bool bAnchorSoundPlaying = false;
6848
6849static void onAnchorSoundFinished(void *ptr) {
6850 o_sound::g_anchorwatch_sound->UnLoad();
6851 bAnchorSoundPlaying = false;
6852}
6853
6854void ChartCanvas::AlertDraw(ocpnDC &dc) {
6855 using namespace o_sound;
6856 // Visual and audio alert for anchorwatch goes here
6857 bool play_sound = false;
6858 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6859 if (AnchorAlertOn1) {
6860 wxPoint TargetPoint;
6862 &TargetPoint);
6863 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6864 TargetPoint.y, 100);
6865 play_sound = true;
6866 }
6867 } else
6868 AnchorAlertOn1 = false;
6869
6870 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6871 if (AnchorAlertOn2) {
6872 wxPoint TargetPoint;
6874 &TargetPoint);
6875 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6876 TargetPoint.y, 100);
6877 play_sound = true;
6878 }
6879 } else
6880 AnchorAlertOn2 = false;
6881
6882 if (play_sound) {
6883 if (!bAnchorSoundPlaying) {
6884 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6885 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6886 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6887 if (g_anchorwatch_sound->IsOk()) {
6888 bAnchorSoundPlaying = true;
6889 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6890 g_anchorwatch_sound->Play();
6891 }
6892 }
6893 }
6894}
6895
6896void ChartCanvas::UpdateShips() {
6897 // Get the rectangle in the current dc which bounds the "ownship" symbol
6898
6899 wxClientDC dc(this);
6900 // Sometimes during startup the dc x or y size is zero, which causes the
6901 // bitmap creation to fail. Just skip the update in this case.
6902 if (!dc.IsOk() || dc.GetSize().x < 1 || dc.GetSize().y < 1) return;
6903
6904 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6905 if (!test_bitmap.IsOk()) return;
6906
6907 wxMemoryDC temp_dc(test_bitmap);
6908
6909 temp_dc.ResetBoundingBox();
6910 temp_dc.DestroyClippingRegion();
6911 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6912
6913 // Draw the ownship on the temp_dc
6914 ocpnDC ocpndc = ocpnDC(temp_dc);
6915 ShipDraw(ocpndc);
6916
6917 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6918 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6919 if (p) {
6920 wxPoint px;
6921 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6922 ocpndc.CalcBoundingBox(px.x, px.y);
6923 }
6924 }
6925
6926 ship_draw_rect =
6927 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6928 temp_dc.MaxY() - temp_dc.MinY());
6929
6930 wxRect own_ship_update_rect = ship_draw_rect;
6931
6932 if (!own_ship_update_rect.IsEmpty()) {
6933 // The required invalidate rectangle is the union of the last drawn
6934 // rectangle and this drawn rectangle
6935 own_ship_update_rect.Union(ship_draw_last_rect);
6936 own_ship_update_rect.Inflate(2);
6937 }
6938
6939 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6940
6941 ship_draw_last_rect = ship_draw_rect;
6942
6943 temp_dc.SelectObject(wxNullBitmap);
6944}
6945
6946void ChartCanvas::UpdateAlerts() {
6947 // Get the rectangle in the current dc which bounds the detected Alert
6948 // targets
6949
6950 // Use this dc
6951 wxClientDC dc(this);
6952
6953 // Get dc boundary
6954 int sx, sy;
6955 dc.GetSize(&sx, &sy);
6956
6957 // Need a bitmap
6958 wxBitmap test_bitmap(sx, sy, -1);
6959
6960 // Create a memory DC
6961 wxMemoryDC temp_dc;
6962 temp_dc.SelectObject(test_bitmap);
6963
6964 temp_dc.ResetBoundingBox();
6965 temp_dc.DestroyClippingRegion();
6966 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6967
6968 // Draw the Alert Targets on the temp_dc
6969 ocpnDC ocpndc = ocpnDC(temp_dc);
6970 AlertDraw(ocpndc);
6971
6972 // Retrieve the drawing extents
6973 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6974 temp_dc.MaxX() - temp_dc.MinX(),
6975 temp_dc.MaxY() - temp_dc.MinY());
6976
6977 if (!alert_rect.IsEmpty())
6978 alert_rect.Inflate(2); // clear all drawing artifacts
6979
6980 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6981 // The required invalidate rectangle is the union of the last drawn
6982 // rectangle and this drawn rectangle
6983 wxRect alert_update_rect = alert_draw_rect;
6984 alert_update_rect.Union(alert_rect);
6985
6986 // Invalidate the rectangular region
6987 RefreshRect(alert_update_rect, false);
6988 }
6989
6990 // Save this rectangle for next time
6991 alert_draw_rect = alert_rect;
6992
6993 temp_dc.SelectObject(wxNullBitmap); // clean up
6994}
6995
6996void ChartCanvas::UpdateAIS() {
6997 if (!g_pAIS) return;
6998
6999 // Get the rectangle in the current dc which bounds the detected AIS targets
7000
7001 // Use this dc
7002 wxClientDC dc(this);
7003
7004 // Get dc boundary
7005 int sx, sy;
7006 dc.GetSize(&sx, &sy);
7007
7008 wxRect ais_rect;
7009
7010 // How many targets are there?
7011
7012 // If more than "some number", it will be cheaper to refresh the entire
7013 // screen than to build update rectangles for each target.
7014 if (g_pAIS->GetTargetList().size() > 10) {
7015 ais_rect = wxRect(0, 0, sx, sy); // full screen
7016 } else {
7017 // Need a bitmap
7018 wxBitmap test_bitmap(sx, sy, -1);
7019
7020 // Create a memory DC
7021 wxMemoryDC temp_dc;
7022 temp_dc.SelectObject(test_bitmap);
7023
7024 temp_dc.ResetBoundingBox();
7025 temp_dc.DestroyClippingRegion();
7026 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
7027
7028 // Draw the AIS Targets on the temp_dc
7029 ocpnDC ocpndc = ocpnDC(temp_dc);
7030 AISDraw(ocpndc, GetVP(), this);
7031 AISDrawAreaNotices(ocpndc, GetVP(), this);
7032
7033 // Retrieve the drawing extents
7034 ais_rect =
7035 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
7036 temp_dc.MaxY() - temp_dc.MinY());
7037
7038 if (!ais_rect.IsEmpty())
7039 ais_rect.Inflate(2); // clear all drawing artifacts
7040
7041 temp_dc.SelectObject(wxNullBitmap); // clean up
7042 }
7043
7044 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
7045 // The required invalidate rectangle is the union of the last drawn
7046 // rectangle and this drawn rectangle
7047 wxRect ais_update_rect = ais_draw_rect;
7048 ais_update_rect.Union(ais_rect);
7049
7050 // Invalidate the rectangular region
7051 RefreshRect(ais_update_rect, false);
7052 }
7053
7054 // Save this rectangle for next time
7055 ais_draw_rect = ais_rect;
7056}
7057
7058void ChartCanvas::ToggleCPAWarn() {
7059 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
7060 wxString mess;
7061 if (g_bCPAWarn) {
7062 g_bTCPA_Max = true;
7063 mess = _("ON");
7064 } else {
7065 g_bTCPA_Max = false;
7066 mess = _("OFF");
7067 }
7068 // Print to status bar if available.
7069 if (STAT_FIELD_SCALE >= 4 && top_frame::Get()->GetStatusBar()) {
7070 top_frame::Get()->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7071 } else {
7072 if (!g_AisFirstTimeUse) {
7073 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7074 _("CPA") + " " + mess, 4, 4);
7075 }
7076 }
7077}
7078
7079void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7080
7081void ChartCanvas::OnSize(wxSizeEvent &event) {
7082 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7083 // GetClientSize returns the size of the canvas area in logical pixels.
7084 GetClientSize(&m_canvas_width, &m_canvas_height);
7085
7086#ifdef __WXOSX__
7087 // Support scaled HDPI displays.
7088 m_displayScale = GetContentScaleFactor();
7089#endif
7090
7091 // Convert to physical pixels.
7092 m_canvas_width *= m_displayScale;
7093 m_canvas_height *= m_displayScale;
7094
7095 // Resize the current viewport
7096 VPoint.pix_width = m_canvas_width;
7097 VPoint.pix_height = m_canvas_height;
7098 VPoint.SetPixelScale(m_displayScale);
7099
7100 // Get some canvas metrics
7101
7102 // Rescale to current value, in order to rebuild VPoint data
7103 // structures for new canvas size
7105
7106 m_absolute_min_scale_ppm =
7107 m_canvas_width /
7108 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7109
7110 // Inform the parent Frame that I am being resized...
7111 top_frame::Get()->ProcessCanvasResize();
7112
7113 // if MUIBar is active, size the bar
7114 // if(g_useMUI && !m_muiBar){ // rebuild if
7115 // necessary
7116 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7117 // m_muiBarHOSize = m_muiBar->GetSize();
7118 // }
7119
7120 if (m_muiBar) {
7121 SetMUIBarPosition();
7122 UpdateFollowButtonState();
7123 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7124 }
7125
7126 // Set up the scroll margins
7127 xr_margin = m_canvas_width * 95 / 100;
7128 xl_margin = m_canvas_width * 5 / 100;
7129 yt_margin = m_canvas_height * 5 / 100;
7130 yb_margin = m_canvas_height * 95 / 100;
7131
7132 if (m_pQuilt)
7133 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7134
7135 // Resize the scratch BM
7136 delete pscratch_bm;
7137 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7138 m_brepaint_piano = true;
7139
7140 // Resize the Route Calculation BM
7141 m_dc_route.SelectObject(wxNullBitmap);
7142 delete proute_bm;
7143 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7144 m_dc_route.SelectObject(*proute_bm);
7145
7146 // Resize the saved Bitmap
7147 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7148
7149 // Resize the working Bitmap
7150 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7151
7152 // Rescale again, to capture all the changes for new canvas size
7154
7155#ifdef ocpnUSE_GL
7156 if (/*g_bopengl &&*/ m_glcc) {
7157 // FIXME (dave) This can go away?
7158 m_glcc->OnSize(event);
7159 }
7160#endif
7161
7162 FormatPianoKeys();
7163 // Invalidate the whole window
7164 ReloadVP();
7165}
7166
7167void ChartCanvas::ProcessNewGUIScale() {
7168 // m_muiBar->Hide();
7169 delete m_muiBar;
7170 m_muiBar = 0;
7171
7172 CreateMUIBar();
7173}
7174
7175void ChartCanvas::CreateMUIBar() {
7176 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7177 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7178 m_muiBar->SetColorScheme(m_cs);
7179 m_muiBarHOSize = m_muiBar->m_size;
7180 }
7181
7182 if (m_muiBar) {
7183 // We need to update the m_bENCGroup flag, not least for the initial
7184 // creation of a MUIBar
7185 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7186
7187 SetMUIBarPosition();
7188 UpdateFollowButtonState();
7189 m_muiBar->UpdateDynamicValues();
7190 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7191 }
7192}
7193
7194void ChartCanvas::SetMUIBarPosition() {
7195 // if MUIBar is active, size the bar
7196 if (m_muiBar) {
7197 // We estimate the piano width based on the canvas width
7198 int pianoWidth = GetClientSize().x * 0.6f;
7199 // If the piano already exists, we can use its exact width
7200 // if(m_Piano)
7201 // pianoWidth = m_Piano->GetWidth();
7202
7203 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7204 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7205 delete m_muiBar;
7206 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7207 m_muiBar->SetColorScheme(m_cs);
7208 }
7209 }
7210
7211 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7212 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7213 delete m_muiBar;
7214 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7215 m_muiBar->SetColorScheme(m_cs);
7216 }
7217 }
7218
7219 m_muiBar->SetBestPosition();
7220 }
7221}
7222
7223void ChartCanvas::DestroyMuiBar() {
7224 if (m_muiBar) {
7225 delete m_muiBar;
7226 m_muiBar = NULL;
7227 }
7228}
7229
7230void ChartCanvas::ShowCompositeInfoWindow(
7231 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7232 if (n_charts > 0) {
7233 if (NULL == m_pCIWin) {
7234 m_pCIWin = new ChInfoWin(this);
7235 m_pCIWin->Hide();
7236 }
7237
7238 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7239 wxString s;
7240
7241 s = _("Composite of ");
7242
7243 wxString s1;
7244 s1.Printf("%d ", n_charts);
7245 if (n_charts > 1)
7246 s1 += _("charts");
7247 else
7248 s1 += _("chart");
7249 s += s1;
7250 s += '\n';
7251
7252 s1.Printf(_("Chart scale"));
7253 s1 += ": ";
7254 wxString s2;
7255 s2.Printf("1:%d\n", scale);
7256 s += s1;
7257 s += s2;
7258
7259 s1 = _("Zoom in for more information");
7260 s += s1;
7261 s += '\n';
7262
7263 int char_width = s1.Length();
7264 int char_height = 3;
7265
7266 if (g_bChartBarEx) {
7267 s += '\n';
7268 int j = 0;
7269 for (int i : index_vector) {
7270 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7271 wxString path = cte.GetFullSystemPath();
7272 s += path;
7273 s += '\n';
7274 char_height++;
7275 char_width = wxMax(char_width, path.Length());
7276 if (j++ >= 9) break;
7277 }
7278 if (j >= 9) {
7279 s += " .\n .\n .\n";
7280 char_height += 3;
7281 }
7282 s += '\n';
7283 char_height += 1;
7284
7285 char_width += 4; // Fluff
7286 }
7287
7288 m_pCIWin->SetString(s);
7289
7290 m_pCIWin->FitToChars(char_width, char_height);
7291
7292 wxPoint p;
7293 p.x = x / GetContentScaleFactor();
7294 if ((p.x + m_pCIWin->GetWinSize().x) >
7295 (m_canvas_width / GetContentScaleFactor()))
7296 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7297 m_pCIWin->GetWinSize().x) /
7298 2; // centered
7299
7300 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7301 4 - m_pCIWin->GetWinSize().y;
7302
7303 m_pCIWin->dbIndex = 0;
7304 m_pCIWin->chart_scale = 0;
7305 m_pCIWin->SetPosition(p);
7306 m_pCIWin->SetBitmap();
7307 m_pCIWin->Refresh();
7308 m_pCIWin->Show();
7309 }
7310 } else {
7311 HideChartInfoWindow();
7312 }
7313}
7314
7315void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7316 if (dbIndex >= 0) {
7317 if (NULL == m_pCIWin) {
7318 m_pCIWin = new ChInfoWin(this);
7319 m_pCIWin->Hide();
7320 }
7321
7322 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7323 wxString s;
7324 ChartBase *pc = NULL;
7325
7326 // TOCTOU race but worst case will reload chart.
7327 // need to lock it or the background spooler may evict charts in
7328 // OpenChartFromDBAndLock
7329 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7330 pc = ChartData->OpenChartFromDBAndLock(
7331 dbIndex, FULL_INIT); // this must come from cache
7332
7333 int char_width, char_height;
7334 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7335 if (pc) ChartData->UnLockCacheChart(dbIndex);
7336
7337 m_pCIWin->SetString(s);
7338 m_pCIWin->FitToChars(char_width, char_height);
7339
7340 wxPoint p;
7341 p.x = x / GetContentScaleFactor();
7342 if ((p.x + m_pCIWin->GetWinSize().x) >
7343 (m_canvas_width / GetContentScaleFactor()))
7344 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7345 m_pCIWin->GetWinSize().x) /
7346 2; // centered
7347
7348 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7349 4 - m_pCIWin->GetWinSize().y;
7350
7351 m_pCIWin->dbIndex = dbIndex;
7352 m_pCIWin->SetPosition(p);
7353 m_pCIWin->SetBitmap();
7354 m_pCIWin->Refresh();
7355 m_pCIWin->Show();
7356 }
7357 } else {
7358 HideChartInfoWindow();
7359 }
7360}
7361
7362void ChartCanvas::HideChartInfoWindow() {
7363 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7364 m_pCIWin->Hide();
7365 m_pCIWin->Destroy();
7366 m_pCIWin = NULL;
7367
7368#ifdef __ANDROID__
7369 androidForceFullRepaint();
7370#endif
7371 }
7372}
7373
7374void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7375 wxMouseEvent ev(wxEVT_MOTION);
7376 ev.m_x = mouse_x;
7377 ev.m_y = mouse_y;
7378 ev.m_leftDown = mouse_leftisdown;
7379
7380 wxEvtHandler *evthp = GetEventHandler();
7381
7382 ::wxPostEvent(evthp, ev);
7383}
7384
7385void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7386 if ((m_panx_target_final - m_panx_target_now) ||
7387 (m_pany_target_final - m_pany_target_now)) {
7388 DoTimedMovementTarget();
7389 } else
7390 DoTimedMovement();
7391}
7392
7393void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7394
7395bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7396 int delta) {
7397 if (m_disable_edge_pan) return false;
7398
7399 bool bft = false;
7400 int pan_margin = m_canvas_width * margin / 100;
7401 int pan_timer_set = 200;
7402 double pan_delta = GetVP().pix_width * delta / 100;
7403 int pan_x = 0;
7404 int pan_y = 0;
7405
7406 if (x > m_canvas_width - pan_margin) {
7407 bft = true;
7408 pan_x = pan_delta;
7409 }
7410
7411 else if (x < pan_margin) {
7412 bft = true;
7413 pan_x = -pan_delta;
7414 }
7415
7416 if (y < pan_margin) {
7417 bft = true;
7418 pan_y = -pan_delta;
7419 }
7420
7421 else if (y > m_canvas_height - pan_margin) {
7422 bft = true;
7423 pan_y = pan_delta;
7424 }
7425
7426 // Of course, if dragging, and the mouse left button is not down, we must
7427 // stop the event injection
7428 if (bdragging) {
7429 if (!g_btouch) {
7430 wxMouseState state = ::wxGetMouseState();
7431#if wxCHECK_VERSION(3, 0, 0)
7432 if (!state.LeftIsDown())
7433#else
7434 if (!state.LeftDown())
7435#endif
7436 bft = false;
7437 }
7438 }
7439 if ((bft) && !pPanTimer->IsRunning()) {
7440 PanCanvas(pan_x, pan_y);
7441 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7442 return true;
7443 }
7444
7445 // This mouse event must not be due to pan timer event injector
7446 // Mouse is out of the pan zone, so prevent any orphan event injection
7447 if ((!bft) && pPanTimer->IsRunning()) {
7448 pPanTimer->Stop();
7449 }
7450
7451 return (false);
7452}
7453
7454// Look for waypoints at the current position.
7455// Used to determine what a mouse event should act on.
7456
7457void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7458 bool setBeingEdited) {
7459 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7460 m_pRoutePointEditTarget = NULL;
7461 m_pFoundPoint = NULL;
7462
7463 SelectItem *pFind = NULL;
7464 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7465 SelectableItemList SelList = pSelect->FindSelectionList(
7466 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7467 for (SelectItem *pFind : SelList) {
7468 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7469
7470 // Get an array of all routes using this point
7471 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7472 // TODO: delete m_pEditRouteArray after use?
7473
7474 // Use route array to determine actual visibility for the point
7475 bool brp_viz = false;
7476 if (m_pEditRouteArray) {
7477 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7478 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7479 if (pr->IsVisible()) {
7480 brp_viz = true;
7481 break;
7482 }
7483 }
7484 } else
7485 brp_viz = frp->IsVisible(); // isolated point
7486
7487 if (brp_viz) {
7488 // Use route array to rubberband all affected routes
7489 if (m_pEditRouteArray) // Editing Waypoint as part of route
7490 {
7491 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7492 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7493 pr->m_bIsBeingEdited = setBeingEdited;
7494 }
7495 m_bRouteEditing = setBeingEdited;
7496 } else // editing Mark
7497 {
7498 frp->m_bRPIsBeingEdited = setBeingEdited;
7499 m_bMarkEditing = setBeingEdited;
7500 }
7501
7502 m_pRoutePointEditTarget = frp;
7503 m_pFoundPoint = pFind;
7504 break; // out of the while(node)
7505 }
7506 } // for (SelectItem...
7507}
7508std::shared_ptr<HostApi121::PiPointContext>
7509ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7510 // General Right Click
7511 // Look for selectable objects
7512 double slat, slon;
7513 GetCanvasPixPoint(x, y, slat, slon);
7514
7515 SelectItem *pFindAIS;
7516 SelectItem *pFindRP;
7517 SelectItem *pFindRouteSeg;
7518 SelectItem *pFindTrackSeg;
7519 SelectItem *pFindCurrent = NULL;
7520 SelectItem *pFindTide = NULL;
7521
7522 // Get all the selectable things at the selected point
7523 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7524 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7525 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7526 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7527 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7528
7529 if (m_bShowCurrent)
7530 pFindCurrent =
7531 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7532
7533 if (m_bShowTide) // look for tide stations
7534 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7535
7536 int seltype = 0;
7537
7538 // Try for AIS targets first
7539 int FoundAIS_MMSI = 0;
7540 if (pFindAIS) {
7541 FoundAIS_MMSI = pFindAIS->GetUserData();
7542
7543 // Make sure the target data is available
7544 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7545 seltype |= SELTYPE_AISTARGET;
7546 }
7547
7548 // Now the various Route Parts
7549
7550 RoutePoint *FoundRoutePoint = NULL;
7551 Route *SelectedRoute = NULL;
7552
7553 if (pFindRP) {
7554 RoutePoint *pFirstVizPoint = NULL;
7555 RoutePoint *pFoundActiveRoutePoint = NULL;
7556 RoutePoint *pFoundVizRoutePoint = NULL;
7557 Route *pSelectedActiveRoute = NULL;
7558 Route *pSelectedVizRoute = NULL;
7559
7560 // There is at least one routepoint, so get the whole list
7561 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7562 SelectableItemList SelList =
7563 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7564 for (SelectItem *pFindSel : SelList) {
7565 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7566
7567 // Get an array of all routes using this point
7568 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7569
7570 // Use route array (if any) to determine actual visibility for this point
7571 bool brp_viz = false;
7572 if (proute_array) {
7573 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7574 Route *pr = (Route *)proute_array->Item(ir);
7575 if (pr->IsVisible()) {
7576 brp_viz = true;
7577 break;
7578 }
7579 }
7580 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7581 // but still exists as a waypoint
7582 brp_viz = prp->IsVisible(); // so treat as isolated point
7583
7584 } else
7585 brp_viz = prp->IsVisible(); // isolated point
7586
7587 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7588
7589 // Use route array to choose the appropriate route
7590 // Give preference to any active route, otherwise select the first visible
7591 // route in the array for this point
7592 if (proute_array) {
7593 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7594 Route *pr = (Route *)proute_array->Item(ir);
7595 if (pr->m_bRtIsActive) {
7596 pSelectedActiveRoute = pr;
7597 pFoundActiveRoutePoint = prp;
7598 break;
7599 }
7600 }
7601
7602 if (NULL == pSelectedVizRoute) {
7603 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7604 Route *pr = (Route *)proute_array->Item(ir);
7605 if (pr->IsVisible()) {
7606 pSelectedVizRoute = pr;
7607 pFoundVizRoutePoint = prp;
7608 break;
7609 }
7610 }
7611 }
7612
7613 delete proute_array;
7614 }
7615 }
7616
7617 // Now choose the "best" selections
7618 if (pFoundActiveRoutePoint) {
7619 FoundRoutePoint = pFoundActiveRoutePoint;
7620 SelectedRoute = pSelectedActiveRoute;
7621 } else if (pFoundVizRoutePoint) {
7622 FoundRoutePoint = pFoundVizRoutePoint;
7623 SelectedRoute = pSelectedVizRoute;
7624 } else
7625 // default is first visible point in list
7626 FoundRoutePoint = pFirstVizPoint;
7627
7628 if (SelectedRoute) {
7629 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7630 } else if (FoundRoutePoint) {
7631 seltype |= SELTYPE_MARKPOINT;
7632 }
7633
7634 // Highlight the selected point, to verify the proper right click selection
7635#if 0
7636 if (m_pFoundRoutePoint) {
7637 m_pFoundRoutePoint->m_bPtIsSelected = true;
7638 wxRect wp_rect;
7639 RoutePointGui(*m_pFoundRoutePoint)
7640 .CalculateDCRect(m_dc_route, this, &wp_rect);
7641 RefreshRect(wp_rect, true);
7642 }
7643#endif
7644 }
7645
7646 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7647 // routes But call the popup handler with identifier appropriate to the type
7648 if (pFindRouteSeg) // there is at least one select item
7649 {
7650 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7651 SelectableItemList SelList =
7652 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7653
7654 if (NULL == SelectedRoute) // the case where a segment only is selected
7655 {
7656 // Choose the first visible route containing segment in the list
7657 for (SelectItem *pFindSel : SelList) {
7658 Route *pr = (Route *)pFindSel->m_pData3;
7659 if (pr->IsVisible()) {
7660 SelectedRoute = pr;
7661 break;
7662 }
7663 }
7664 }
7665
7666 if (SelectedRoute) {
7667 if (NULL == FoundRoutePoint)
7668 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7669
7670 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7671 seltype |= SELTYPE_ROUTESEGMENT;
7672 }
7673 }
7674
7675 if (pFindTrackSeg) {
7676 m_pSelectedTrack = NULL;
7677 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7678 SelectableItemList SelList =
7679 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7680
7681 // Choose the first visible track containing segment in the list
7682 for (SelectItem *pFindSel : SelList) {
7683 Track *pt = (Track *)pFindSel->m_pData3;
7684 if (pt->IsVisible()) {
7685 m_pSelectedTrack = pt;
7686 break;
7687 }
7688 }
7689 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7690 }
7691
7692 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7693
7694 // Populate the return struct
7695 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7696 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7697 rstruct->object_ident = "";
7698
7699 if (seltype == SELTYPE_AISTARGET) {
7700 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7701 wxString val;
7702 val.Printf("%d", FoundAIS_MMSI);
7703 rstruct->object_ident = val.ToStdString();
7704 } else if (seltype & SELTYPE_MARKPOINT) {
7705 if (FoundRoutePoint) {
7706 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7707 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7708 }
7709 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7710 if (SelectedRoute) {
7711 rstruct->object_type =
7712 HostApi121::PiContextObjectType::kObjectRoutesegment;
7713 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7714 }
7715 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7716 if (m_pSelectedTrack) {
7717 rstruct->object_type =
7718 HostApi121::PiContextObjectType::kObjectTracksegment;
7719 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7720 }
7721 }
7722
7723 return rstruct;
7724}
7725
7726void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7727 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7728 singleClickEventIsValid = false;
7729 m_DoubleClickTimer->Stop();
7730}
7731
7732bool leftIsDown;
7733
7734bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7735 if (!m_bChartDragging && !m_bDrawingRoute) {
7736 /*
7737 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7738 * mouse event coordinates are in logical pixels.
7739 */
7740 if (m_Compass && m_Compass->IsShown()) {
7741 wxRect logicalRect = m_Compass->GetLogicalRect();
7742 bool isInCompass = logicalRect.Contains(event.GetPosition());
7743 if (isInCompass || m_mouseWasInCompass) {
7744 if (m_Compass->MouseEvent(event)) {
7745 cursor_region = CENTER;
7746 if (!g_btouch) SetCanvasCursor(event);
7747 m_mouseWasInCompass = isInCompass;
7748 return true;
7749 }
7750 }
7751 m_mouseWasInCompass = isInCompass;
7752 }
7753
7754 if (m_notification_button && m_notification_button->IsShown()) {
7755 wxRect logicalRect = m_notification_button->GetLogicalRect();
7756 bool isinButton = logicalRect.Contains(event.GetPosition());
7757 if (isinButton) {
7758 SetCursor(*pCursorArrow);
7759 if (event.LeftDown()) HandleNotificationMouseClick();
7760 return true;
7761 }
7762 }
7763
7764 if (MouseEventToolbar(event)) return true;
7765
7766 if (MouseEventChartBar(event)) return true;
7767
7768 if (MouseEventMUIBar(event)) return true;
7769
7770 if (MouseEventIENCBar(event)) return true;
7771 }
7772 return false;
7773}
7774
7775void ChartCanvas::HandleNotificationMouseClick() {
7776 if (!m_NotificationsList) {
7777 m_NotificationsList = new NotificationsList(this);
7778
7779 // calculate best size for Notification list
7780 m_NotificationsList->RecalculateSize();
7781 m_NotificationsList->Hide();
7782 }
7783
7784 if (m_NotificationsList->IsShown()) {
7785 m_NotificationsList->Hide();
7786 } else {
7787 m_NotificationsList->RecalculateSize();
7788 m_NotificationsList->ReloadNotificationList();
7789 m_NotificationsList->Show();
7790 }
7791}
7792bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7793 if (!g_bShowChartBar) return false;
7794
7795 if (!m_Piano->MouseEvent(event)) return false;
7796
7797 cursor_region = CENTER;
7798 if (!g_btouch) SetCanvasCursor(event);
7799 return true;
7800}
7801
7802bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7803 if (!IsPrimaryCanvas()) return false;
7804
7805 if (g_MainToolbar) {
7806 if (!g_MainToolbar->MouseEvent(event))
7807 return false;
7808 else
7809 g_MainToolbar->RefreshToolbar();
7810 }
7811
7812 cursor_region = CENTER;
7813 if (!g_btouch) SetCanvasCursor(event);
7814 return true;
7815}
7816
7817bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7818 if (!IsPrimaryCanvas()) return false;
7819
7820 if (g_iENCToolbar) {
7821 if (!g_iENCToolbar->MouseEvent(event))
7822 return false;
7823 else {
7824 g_iENCToolbar->RefreshToolbar();
7825 return true;
7826 }
7827 }
7828 return false;
7829}
7830
7831bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7832 if (m_muiBar) {
7833 if (!m_muiBar->MouseEvent(event)) return false;
7834 }
7835
7836 cursor_region = CENTER;
7837 if (!g_btouch) SetCanvasCursor(event);
7838 if (m_muiBar)
7839 return true;
7840 else
7841 return false;
7842}
7843
7844bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7845 int x, y;
7846
7847 bool bret = false;
7848
7849 event.GetPosition(&x, &y);
7850
7851 x *= m_displayScale;
7852 y *= m_displayScale;
7853
7854 m_MouseDragging = event.Dragging();
7855
7856 // Some systems produce null drag events, where the pointer position has not
7857 // changed from the previous value. Detect this case, and abort further
7858 // processing (FS#1748)
7859#ifdef __WXMSW__
7860 if (event.Dragging()) {
7861 if ((x == mouse_x) && (y == mouse_y)) return true;
7862 }
7863#endif
7864
7865 mouse_x = x;
7866 mouse_y = y;
7867 mouse_leftisdown = event.LeftDown();
7869
7870 // Establish the event region
7871 cursor_region = CENTER;
7872
7873 int chartbar_height = GetChartbarHeight();
7874
7875 if (m_Compass && m_Compass->IsShown() &&
7876 m_Compass->GetRect().Contains(event.GetPosition())) {
7877 cursor_region = CENTER;
7878 } else if (x > xr_margin) {
7879 cursor_region = MID_RIGHT;
7880 } else if (x < xl_margin) {
7881 cursor_region = MID_LEFT;
7882 } else if (y > yb_margin - chartbar_height &&
7883 y < m_canvas_height - chartbar_height) {
7884 cursor_region = MID_TOP;
7885 } else if (y < yt_margin) {
7886 cursor_region = MID_BOT;
7887 } else {
7888 cursor_region = CENTER;
7889 }
7890
7891 if (!g_btouch) SetCanvasCursor(event);
7892
7893 // Protect from leftUp's coming from event handlers in child
7894 // windows who return focus to the canvas.
7895 leftIsDown = event.LeftDown();
7896
7897#ifndef __WXOSX__
7898 if (event.LeftDown()) {
7899 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7900 // The menu bar is temporarily visible due to alt having been pressed.
7901 // Clicking will hide it, and do nothing else.
7902 g_bTempShowMenuBar = false;
7903 top_frame::Get()->ApplyGlobalSettings(false);
7904 return (true);
7905 }
7906 }
7907#endif
7908
7909 // Update modifiers here; some window managers never send the key event
7910 m_modkeys = 0;
7911 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7912 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7913
7914#ifdef __WXMSW__
7915 // TODO Test carefully in other platforms, remove ifdef....
7916 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7917 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7918#endif
7919
7920 event.SetEventObject(this);
7921 if (SendMouseEventToPlugins(event))
7922 return (true); // PlugIn did something, and does not want the canvas to
7923 // do anything else
7924
7925 // Capture LeftUp's and time them, unless it already came from the timer.
7926
7927 // Detect end of chart dragging
7928 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7929 StartChartDragInertia();
7930 }
7931
7932 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7933 !singleClickEventIsValid) {
7934 // Ignore the second LeftUp after the DClick.
7935 if (m_DoubleClickTimer->IsRunning()) {
7936 m_DoubleClickTimer->Stop();
7937 return (true);
7938 }
7939
7940 // Save the event for later running if there is no DClick.
7941 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7942 singleClickEvent = event;
7943 singleClickEventIsValid = true;
7944 return (true);
7945 }
7946
7947 // This logic is necessary on MSW to handle the case where
7948 // a context (right-click) menu is dismissed without action
7949 // by clicking on the chart surface.
7950 // We need to avoid an unintentional pan by eating some clicks...
7951#ifdef __WXMSW__
7952 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7953 if (g_click_stop > 0) {
7954 g_click_stop--;
7955 return (true);
7956 }
7957 }
7958#endif
7959
7960 // Kick off the Rotation control timer
7961 if (GetUpMode() == COURSE_UP_MODE) {
7962 m_b_rot_hidef = false;
7963 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7964 } else
7965 pRotDefTimer->Stop();
7966
7967 // Retrigger the route leg / AIS target popup timer
7968 bool bRoll = !g_btouch;
7969#ifdef __ANDROID__
7970 bRoll = g_bRollover;
7971#endif
7972 if (bRoll) {
7973 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7974 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7975 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7976 m_RolloverPopupTimer.Start(
7977 10,
7978 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7979 else
7980 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7981 }
7982
7983 // Retrigger the cursor tracking timer
7984 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7985
7986// Show cursor position on Status Bar, if present
7987// except for GTK, under which status bar updates are very slow
7988// due to Update() call.
7989// In this case, as a workaround, update the status window
7990// after an interval timer (pCurTrackTimer) pops, which will happen
7991// whenever the mouse has stopped moving for specified interval.
7992// See the method OnCursorTrackTimerEvent()
7993#if !defined(__WXGTK__) && !defined(__WXQT__)
7994 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7995#endif
7996
7997 // Send the current cursor lat/lon to all PlugIns requesting it
7998 if (g_pi_manager) {
7999 // Occasionally, MSW will produce nonsense events on right click....
8000 // This results in an error in cursor geo position, so we skip this case
8001 if ((x >= 0) && (y >= 0))
8002 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8003 }
8004
8005 if (!g_btouch) {
8006 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8007 wxPoint p = ClientToScreen(wxPoint(x, y));
8008 }
8009 }
8010
8011 if (1 ) {
8012 // Route Creation Rubber Banding
8013 if (m_routeState >= 2) {
8014 r_rband.x = x;
8015 r_rband.y = y;
8016 m_bDrawingRoute = true;
8017
8018 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8019 Refresh(false);
8020 }
8021
8022 // Measure Tool Rubber Banding
8023 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8024 r_rband.x = x;
8025 r_rband.y = y;
8026 m_bDrawingRoute = true;
8027
8028 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8029 Refresh(false);
8030 }
8031 }
8032 return bret;
8033}
8034
8035int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8036 // On general Right Click
8037 // Look for selectable objects
8038 double slat = lat;
8039 double slon = lon;
8040
8041#if defined(__WXMAC__) || defined(__ANDROID__)
8042 wxScreenDC sdc;
8043 ocpnDC dc(sdc);
8044#else
8045 wxClientDC cdc(GetParent());
8046 ocpnDC dc(cdc);
8047#endif
8048
8049 SelectItem *pFindAIS;
8050 SelectItem *pFindRP;
8051 SelectItem *pFindRouteSeg;
8052 SelectItem *pFindTrackSeg;
8053 SelectItem *pFindCurrent = NULL;
8054 SelectItem *pFindTide = NULL;
8055
8056 // Deselect any current objects
8057 if (m_pSelectedRoute) {
8058 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8059 m_pSelectedRoute->DeSelectRoute();
8060#ifdef ocpnUSE_GL
8061 if (g_bopengl && m_glcc) {
8062 InvalidateGL();
8063 Update();
8064 } else
8065#endif
8066 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8067 }
8068
8069 if (m_pFoundRoutePoint) {
8070 m_pFoundRoutePoint->m_bPtIsSelected = false;
8071 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8072 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8073 }
8074
8077 if (g_btouch && m_pRoutePointEditTarget) {
8078 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8079 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8080 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8081 }
8082
8083 // Get all the selectable things at the cursor
8084 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8085 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8086 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8087 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8088 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8089
8090 if (m_bShowCurrent)
8091 pFindCurrent =
8092 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8093
8094 if (m_bShowTide) // look for tide stations
8095 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8096
8097 int seltype = 0;
8098
8099 // Try for AIS targets first
8100 if (pFindAIS) {
8101 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8102
8103 // Make sure the target data is available
8104 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8105 seltype |= SELTYPE_AISTARGET;
8106 }
8107
8108 // Now examine the various Route parts
8109
8110 m_pFoundRoutePoint = NULL;
8111 if (pFindRP) {
8112 RoutePoint *pFirstVizPoint = NULL;
8113 RoutePoint *pFoundActiveRoutePoint = NULL;
8114 RoutePoint *pFoundVizRoutePoint = NULL;
8115 Route *pSelectedActiveRoute = NULL;
8116 Route *pSelectedVizRoute = NULL;
8117
8118 // There is at least one routepoint, so get the whole list
8119 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8120 SelectableItemList SelList =
8121 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8122 for (SelectItem *pFindSel : SelList) {
8123 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8124
8125 // Get an array of all routes using this point
8126 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8127
8128 // Use route array (if any) to determine actual visibility for this point
8129 bool brp_viz = false;
8130 if (proute_array) {
8131 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8132 Route *pr = (Route *)proute_array->Item(ir);
8133 if (pr->IsVisible()) {
8134 brp_viz = true;
8135 break;
8136 }
8137 }
8138 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8139 // but still exists as a waypoint
8140 brp_viz = prp->IsVisible(); // so treat as isolated point
8141
8142 } else
8143 brp_viz = prp->IsVisible(); // isolated point
8144
8145 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8146
8147 // Use route array to choose the appropriate route
8148 // Give preference to any active route, otherwise select the first visible
8149 // route in the array for this point
8150 m_pSelectedRoute = NULL;
8151 if (proute_array) {
8152 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8153 Route *pr = (Route *)proute_array->Item(ir);
8154 if (pr->m_bRtIsActive) {
8155 pSelectedActiveRoute = pr;
8156 pFoundActiveRoutePoint = prp;
8157 break;
8158 }
8159 }
8160
8161 if (NULL == pSelectedVizRoute) {
8162 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8163 Route *pr = (Route *)proute_array->Item(ir);
8164 if (pr->IsVisible()) {
8165 pSelectedVizRoute = pr;
8166 pFoundVizRoutePoint = prp;
8167 break;
8168 }
8169 }
8170 }
8171
8172 delete proute_array;
8173 }
8174 }
8175
8176 // Now choose the "best" selections
8177 if (pFoundActiveRoutePoint) {
8178 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8179 m_pSelectedRoute = pSelectedActiveRoute;
8180 } else if (pFoundVizRoutePoint) {
8181 m_pFoundRoutePoint = pFoundVizRoutePoint;
8182 m_pSelectedRoute = pSelectedVizRoute;
8183 } else
8184 // default is first visible point in list
8185 m_pFoundRoutePoint = pFirstVizPoint;
8186
8187 if (m_pSelectedRoute) {
8188 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8189 } else if (m_pFoundRoutePoint) {
8190 seltype |= SELTYPE_MARKPOINT;
8191 }
8192
8193 // Highlight the selected point, to verify the proper right click selection
8194 if (m_pFoundRoutePoint) {
8195 m_pFoundRoutePoint->m_bPtIsSelected = true;
8196 wxRect wp_rect;
8197 RoutePointGui(*m_pFoundRoutePoint)
8198 .CalculateDCRect(m_dc_route, this, &wp_rect);
8199 RefreshRect(wp_rect, true);
8200 }
8201 }
8202
8203 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8204 // routes But call the popup handler with identifier appropriate to the type
8205 if (pFindRouteSeg) // there is at least one select item
8206 {
8207 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8208 SelectableItemList SelList =
8209 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8210
8211 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8212 {
8213 // Choose the first visible route containing segment in the list
8214 for (SelectItem *pFindSel : SelList) {
8215 Route *pr = (Route *)pFindSel->m_pData3;
8216 if (pr->IsVisible()) {
8217 m_pSelectedRoute = pr;
8218 break;
8219 }
8220 }
8221 }
8222
8223 if (m_pSelectedRoute) {
8224 if (NULL == m_pFoundRoutePoint)
8225 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8226
8227 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8228 if (m_pSelectedRoute->m_bRtIsSelected) {
8229#ifdef ocpnUSE_GL
8230 if (g_bopengl && m_glcc) {
8231 InvalidateGL();
8232 Update();
8233 } else
8234#endif
8235 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8236 }
8237 seltype |= SELTYPE_ROUTESEGMENT;
8238 }
8239 }
8240
8241 if (pFindTrackSeg) {
8242 m_pSelectedTrack = NULL;
8243 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8244 SelectableItemList SelList =
8245 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8246
8247 // Choose the first visible track containing segment in the list
8248 for (SelectItem *pFindSel : SelList) {
8249 Track *pt = (Track *)pFindSel->m_pData3;
8250 if (pt->IsVisible()) {
8251 m_pSelectedTrack = pt;
8252 break;
8253 }
8254 }
8255 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8256 }
8257
8258#if 0 // disable tide and current graph on right click
8259 {
8260 if (pFindCurrent) {
8261 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8262 seltype |= SELTYPE_CURRENTPOINT;
8263 }
8264
8265 else if (pFindTide) {
8266 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8267 seltype |= SELTYPE_TIDEPOINT;
8268 }
8269 }
8270#endif
8271
8272 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8273
8274 return seltype;
8275}
8276
8277IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8278 // There may be multiple current entries at the same point.
8279 // For example, there often is a current substation (with directions
8280 // specified) co-located with its master. We want to select the
8281 // substation, so that the direction will be properly indicated on the
8282 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8283 // substation)
8284 IDX_entry *pIDX_best_candidate;
8285
8286 SelectItem *pFind = NULL;
8287 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8288 SelectableItemList SelList =
8289 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8290
8291 // Default is first entry
8292 pFind = *SelList.begin();
8293 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8294
8295 auto node = SelList.begin();
8296 if (SelList.size() > 1) {
8297 for (++node; node != SelList.end(); ++node) {
8298 pFind = *node;
8299 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8300 if (pIDX_candidate->IDX_type == 'c') {
8301 pIDX_best_candidate = pIDX_candidate;
8302 break;
8303 }
8304 } // while (node)
8305 } else {
8306 pFind = *SelList.begin();
8307 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8308 }
8309
8310 return pIDX_best_candidate;
8311}
8312void ChartCanvas::CallPopupMenu(int x, int y) {
8313 last_drag.x = x;
8314 last_drag.y = y;
8315 if (m_routeState) { // creating route?
8316 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8317 return;
8318 }
8319
8321
8322 // If tide or current point is selected, then show the TC dialog immediately
8323 // without context menu
8324 if (SELTYPE_CURRENTPOINT == seltype) {
8325 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8326 Refresh(false);
8327 return;
8328 }
8329
8330 if (SELTYPE_TIDEPOINT == seltype) {
8331 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8332 Refresh(false);
8333 return;
8334 }
8335
8336 InvokeCanvasMenu(x, y, seltype);
8337
8338 // Clean up if not deleted in InvokeCanvasMenu
8339 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8340 m_pSelectedRoute->m_bRtIsSelected = false;
8341 }
8342
8343 m_pSelectedRoute = NULL;
8344
8345 if (m_pFoundRoutePoint) {
8346 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8347 m_pFoundRoutePoint->m_bPtIsSelected = false;
8348 }
8349 m_pFoundRoutePoint = NULL;
8350
8351 Refresh(true);
8352 // Refresh(false); // needed for MSW, not GTK Why??
8353}
8354
8355bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8356 // For now just bail out completely if the point clicked is not on the chart
8357 if (std::isnan(m_cursor_lat)) return false;
8358
8359 // Mouse Clicks
8360 bool ret = false; // return true if processed
8361
8362 int x, y, mx, my;
8363 event.GetPosition(&x, &y);
8364 mx = x;
8365 my = y;
8366
8367 // Calculate meaningful SelectRadius
8368 float SelectRadius;
8369 SelectRadius = g_Platform->GetSelectRadiusPix() /
8370 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8371
8373 // We start with Double Click processing. The first left click just starts a
8374 // timer and is remembered, then we actually do something if there is a
8375 // LeftDClick. If there is, the two single clicks are ignored.
8376
8377 if (event.LeftDClick() && (cursor_region == CENTER)) {
8378 m_DoubleClickTimer->Start();
8379 singleClickEventIsValid = false;
8380
8381 double zlat, zlon;
8383 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8384
8385 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8386 if (m_bShowAIS) {
8387 SelectItem *pFindAIS;
8388 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8389
8390 if (pFindAIS) {
8391 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8392 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8393 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8394 }
8395 return true;
8396 }
8397 }
8398
8399 SelectableItemList rpSelList =
8400 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8401 bool b_onRPtarget = false;
8402 for (SelectItem *pFind : rpSelList) {
8403 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8404 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8405 b_onRPtarget = true;
8406 break;
8407 }
8408 }
8409
8410 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8411
8412 // Get and honor the plugin API ContextMenuMask
8413 std::unique_ptr<HostApi> host_api = GetHostApi();
8414 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8415
8416 if (m_pRoutePointEditTarget) {
8417 if (b_onRPtarget) {
8418 if ((api_121->GetContextMenuMask() &
8419 api_121->kContextMenuDisableWaypoint))
8420 return true;
8421 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8422 return true;
8423 } else {
8424 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8425 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8426 if (g_btouch)
8427 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8428 wxRect wp_rect;
8429 RoutePointGui(*m_pRoutePointEditTarget)
8430 .CalculateDCRect(m_dc_route, this, &wp_rect);
8431 m_pRoutePointEditTarget = NULL; // cancel selection
8432 RefreshRect(wp_rect, true);
8433 return true;
8434 }
8435 } else {
8436 auto node = rpSelList.begin();
8437 if (node != rpSelList.end()) {
8438 SelectItem *pFind = *node;
8439 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8440 if (frp) {
8441 wxArrayPtrVoid *proute_array =
8443
8444 // Use route array (if any) to determine actual visibility for this
8445 // point
8446 bool brp_viz = false;
8447 if (proute_array) {
8448 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8449 Route *pr = (Route *)proute_array->Item(ir);
8450 if (pr->IsVisible()) {
8451 brp_viz = true;
8452 break;
8453 }
8454 }
8455 delete proute_array;
8456 if (!brp_viz &&
8457 frp->IsShared()) // is not visible as part of route, but
8458 // still exists as a waypoint
8459 brp_viz = frp->IsVisible(); // so treat as isolated point
8460 } else
8461 brp_viz = frp->IsVisible(); // isolated point
8462
8463 if (brp_viz) {
8464 if ((api_121->GetContextMenuMask() &
8465 api_121->kContextMenuDisableWaypoint))
8466 return true;
8467
8468 ShowMarkPropertiesDialog(frp);
8469 return true;
8470 }
8471 }
8472 }
8473 }
8474
8475 SelectItem *cursorItem;
8476
8477 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8478 if (cursorItem) {
8479 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8480 return true;
8481 Route *pr = (Route *)cursorItem->m_pData3;
8482 if (pr->IsVisible()) {
8483 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8484 return true;
8485 }
8486 }
8487
8488 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8489 if (cursorItem) {
8490 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8491 return true;
8492 Track *pt = (Track *)cursorItem->m_pData3;
8493 if (pt->IsVisible()) {
8494 ShowTrackPropertiesDialog(pt);
8495 return true;
8496 }
8497 }
8498
8499 // Tide and current points
8500 SelectItem *pFindCurrent = NULL;
8501 SelectItem *pFindTide = NULL;
8502
8503 if (m_bShowCurrent) { // look for current stations
8504 pFindCurrent =
8505 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8506 if (pFindCurrent) {
8507 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8508 // Check for plugin graphic override
8509 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8510 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8511 PlugInContainer *pic = plugin_array->Item(i);
8512 if (pic->m_enabled && pic->m_init_state &&
8513 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8514 if (ptcmgr) {
8515 TCClickInfo info;
8516 if (m_pIDXCandidate) {
8517 info.point_type = CURRENT_STATION;
8518 info.index = m_pIDXCandidate->IDX_rec_num;
8519 info.name = m_pIDXCandidate->IDX_station_name;
8520 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8521 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8522 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8523 };
8524 auto plugin =
8525 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8526 if (plugin) plugin->OnTideCurrentClick(info);
8527 return true;
8528 }
8529 }
8530 }
8531 }
8532
8533 // Default action
8534 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8535 Refresh(false);
8536 return true;
8537 }
8538 }
8539
8540 if (m_bShowTide) { // look for tide stations
8541 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8542 if (pFindTide) {
8543 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8544 // Check for plugin graphic override
8545 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8546 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8547 PlugInContainer *pic = plugin_array->Item(i);
8548 if (pic->m_enabled && pic->m_init_state &&
8549 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8550 if (ptcmgr) {
8551 TCClickInfo info;
8552 if (m_pIDXCandidate) {
8553 info.point_type = TIDE_STATION;
8554 info.index = m_pIDXCandidate->IDX_rec_num;
8555 info.name = m_pIDXCandidate->IDX_station_name;
8556 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8557 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8558 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8559 };
8560 auto plugin =
8561 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8562 if (plugin) plugin->OnTideCurrentClick(info);
8563 return true;
8564 }
8565 }
8566 }
8567 }
8568
8569 // Default action
8570 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8571 Refresh(false);
8572 return true;
8573 }
8574 }
8575
8576 // Found no object to act on, so show chart info.
8577 ShowObjectQueryWindow(x, y, zlat, zlon);
8578 return true;
8579 }
8580
8582 if (event.LeftDown()) {
8583 // This really should not be needed, but....
8584 // on Windows, when using wxAUIManager, sometimes the focus is lost
8585 // when clicking into another pane, e.g.the AIS target list, and then back
8586 // to this pane. Oddly, some mouse events are not lost, however. Like this
8587 // one....
8588 SetFocus();
8589
8590 last_drag.x = mx;
8591 last_drag.y = my;
8592 leftIsDown = true;
8593
8594 if (!g_btouch) {
8595 if (m_routeState) // creating route?
8596 {
8597 double rlat, rlon;
8598 bool appending = false;
8599 bool inserting = false;
8600 Route *tail = 0;
8601
8602 SetCursor(*pCursorPencil);
8603 rlat = m_cursor_lat;
8604 rlon = m_cursor_lon;
8605
8606 m_bRouteEditing = true;
8607
8608 if (m_routeState == 1) {
8609 m_pMouseRoute = new Route();
8610 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8611 pRouteList->push_back(m_pMouseRoute);
8612 r_rband.x = x;
8613 r_rband.y = y;
8614 }
8615
8616 // Check to see if there is a nearby point which may be reused
8617 RoutePoint *pMousePoint = NULL;
8618
8619 // Calculate meaningful SelectRadius
8620 double nearby_radius_meters =
8621 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8622
8623 RoutePoint *pNearbyPoint =
8624 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8625 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8626 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8627 wxArrayPtrVoid *proute_array =
8628 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8629
8630 // Use route array (if any) to determine actual visibility for this
8631 // point
8632 bool brp_viz = false;
8633 if (proute_array) {
8634 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8635 Route *pr = (Route *)proute_array->Item(ir);
8636 if (pr->IsVisible()) {
8637 brp_viz = true;
8638 break;
8639 }
8640 }
8641 delete proute_array;
8642 if (!brp_viz &&
8643 pNearbyPoint->IsShared()) // is not visible as part of route,
8644 // but still exists as a waypoint
8645 brp_viz =
8646 pNearbyPoint->IsVisible(); // so treat as isolated point
8647 } else
8648 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8649
8650 if (brp_viz) {
8651 wxString msg = _("Use nearby waypoint?");
8652 // Don't add a mark without name to the route. Name it if needed
8653 const bool noname(pNearbyPoint->GetName() == "");
8654 if (noname) {
8655 msg =
8656 _("Use nearby nameless waypoint and name it M with"
8657 " a unique number?");
8658 }
8659 // Avoid route finish on focus change for message dialog
8660 m_FinishRouteOnKillFocus = false;
8661 int dlg_return =
8662 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8663 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8664 m_FinishRouteOnKillFocus = true;
8665 if (dlg_return == wxID_YES) {
8666 if (noname) {
8667 if (m_pMouseRoute) {
8668 int last_wp_num = m_pMouseRoute->GetnPoints();
8669 // AP-ECRMB will truncate to 6 characters
8670 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8671 wxString wp_name = wxString::Format(
8672 "M%002i-%s", last_wp_num + 1, guid_short);
8673 pNearbyPoint->SetName(wp_name);
8674 } else
8675 pNearbyPoint->SetName("WPXX");
8676 }
8677 pMousePoint = pNearbyPoint;
8678
8679 // Using existing waypoint, so nothing to delete for undo.
8680 if (m_routeState > 1)
8681 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8682 Undo_HasParent, NULL);
8683
8684 tail =
8685 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8686 bool procede = false;
8687 if (tail) {
8688 procede = true;
8689 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8690 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8691 procede = false;
8692 }
8693
8694 if (procede) {
8695 int dlg_return;
8696 m_FinishRouteOnKillFocus = false;
8697 if (m_routeState ==
8698 1) { // first point in new route, preceeding route to be
8699 // added? Not touch case
8700
8701 wxString dmsg =
8702 _("Insert first part of this route in the new route?");
8703 if (tail->GetIndexOf(pMousePoint) ==
8704 tail->GetnPoints()) // Starting on last point of another
8705 // route?
8706 dmsg = _("Insert this route in the new route?");
8707
8708 if (tail->GetIndexOf(pMousePoint) > 0) { // Anything to do?
8709 dlg_return = OCPNMessageBox(
8710 this, dmsg, _("OpenCPN Route Create"),
8711 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8712 m_FinishRouteOnKillFocus = true;
8713
8714 if (dlg_return == wxID_YES) {
8715 inserting = true; // part of the other route will be
8716 // preceeding the new route
8717 }
8718 }
8719 } else {
8720 wxString dmsg =
8721 _("Append last part of this route to the new route?");
8722 if (tail->GetIndexOf(pMousePoint) == 1)
8723 dmsg = _(
8724 "Append this route to the new route?"); // Picking the
8725 // first point
8726 // of another
8727 // route?
8728
8729 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8730 dlg_return = OCPNMessageBox(
8731 this, dmsg, _("OpenCPN Route Create"),
8732 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8733 m_FinishRouteOnKillFocus = true;
8734
8735 if (dlg_return == wxID_YES) {
8736 appending = true; // part of the other route will be
8737 // appended to the new route
8738 }
8739 }
8740 }
8741 }
8742
8743 // check all other routes to see if this point appears in any
8744 // other route If it appears in NO other route, then it should e
8745 // considered an isolated mark
8746 if (!FindRouteContainingWaypoint(pMousePoint))
8747 pMousePoint->SetShared(true);
8748 }
8749 }
8750 }
8751
8752 if (NULL == pMousePoint) { // need a new point
8753 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8754 "", wxEmptyString);
8755 pMousePoint->SetNameShown(false);
8756
8757 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8758
8759 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8760
8761 if (m_routeState > 1)
8762 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8763 Undo_IsOrphanded, NULL);
8764 }
8765
8766 if (m_pMouseRoute) {
8767 if (m_routeState == 1) {
8768 // First point in the new route.
8769 m_pMouseRoute->AddPoint(pMousePoint);
8770 } else {
8771 if (m_pMouseRoute->m_NextLegGreatCircle) {
8772 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8773 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8774 &rhumbBearing, &rhumbDist);
8775 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8776 rlat, &gcDist, &gcBearing, NULL);
8777 double gcDistNM = gcDist / 1852.0;
8778
8779 // Empirically found expression to get reasonable route segments.
8780 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8781 pow(rhumbDist - gcDistNM - 1, 0.5);
8782
8783 wxString msg;
8784 msg << _("For this leg the Great Circle route is ")
8785 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8786 << _(" shorter than rhumbline.\n\n")
8787 << _("Would you like include the Great Circle routing points "
8788 "for this leg?");
8789
8790 m_FinishRouteOnKillFocus = false;
8791 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8792 // does not fully capture mouse
8793
8794 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8795 wxYES_NO | wxNO_DEFAULT);
8796
8797 m_disable_edge_pan = false;
8798 m_FinishRouteOnKillFocus = true;
8799
8800 if (answer == wxID_YES) {
8801 RoutePoint *gcPoint;
8802 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8803 wxRealPoint gcCoord;
8804
8805 for (int i = 1; i <= segmentCount; i++) {
8806 double fraction = (double)i * (1.0 / (double)segmentCount);
8807 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8808 gcDist * fraction, gcBearing,
8809 &gcCoord.x, &gcCoord.y, NULL);
8810
8811 if (i < segmentCount) {
8812 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8813 wxEmptyString);
8814 gcPoint->SetNameShown(false);
8815 // pConfig->AddNewWayPoint(gcPoint, -1);
8816 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8817
8818 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8819 gcPoint);
8820 } else {
8821 gcPoint = pMousePoint; // Last point, previously exsisting!
8822 }
8823
8824 m_pMouseRoute->AddPoint(gcPoint);
8825 pSelect->AddSelectableRouteSegment(
8826 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8827 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8828 prevGcPoint = gcPoint;
8829 }
8830
8831 undo->CancelUndoableAction(true);
8832
8833 } else {
8834 m_pMouseRoute->AddPoint(pMousePoint);
8835 pSelect->AddSelectableRouteSegment(
8836 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8837 pMousePoint, m_pMouseRoute);
8838 undo->AfterUndoableAction(m_pMouseRoute);
8839 }
8840 } else {
8841 // Ordinary rhumblinesegment.
8842 m_pMouseRoute->AddPoint(pMousePoint);
8843 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8844 rlon, m_prev_pMousePoint,
8845 pMousePoint, m_pMouseRoute);
8846 undo->AfterUndoableAction(m_pMouseRoute);
8847 }
8848 }
8849 }
8850 m_prev_rlat = rlat;
8851 m_prev_rlon = rlon;
8852 m_prev_pMousePoint = pMousePoint;
8853 if (m_pMouseRoute)
8854 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8855
8856 m_routeState++;
8857
8858 if (appending ||
8859 inserting) { // Appending a route or making a new route
8860 int connect = tail->GetIndexOf(pMousePoint);
8861 if (connect == 0) {
8862 inserting = false; // there is nothing to insert
8863 appending = true; // so append
8864 }
8865 int length = tail->GetnPoints();
8866
8867 int i;
8868 int start, stop;
8869 if (appending) {
8870 start = connect + 1;
8871 stop = length;
8872 } else { // inserting
8873 start = 1;
8874 stop = connect + 1;
8875 // Remove the first and only point of the new route
8876 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8877 }
8878 for (i = start; i <= stop; i++) {
8879 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8880 if (m_pMouseRoute)
8881 m_pMouseRoute->m_lastMousePointIndex =
8882 m_pMouseRoute->GetnPoints();
8883 m_routeState++;
8884 top_frame::Get()->RefreshAllCanvas();
8885 ret = true;
8886 }
8887 m_prev_rlat =
8888 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8889 m_prev_rlon =
8890 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8891 m_pMouseRoute->FinalizeForRendering();
8892 }
8893 top_frame::Get()->RefreshAllCanvas();
8894 ret = true;
8895 }
8896
8897 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8898 {
8899 SetCursor(*pCursorPencil);
8900
8901 if (!m_pMeasureRoute) {
8902 m_pMeasureRoute = new Route();
8903 pRouteList->push_back(m_pMeasureRoute);
8904 }
8905
8906 if (m_nMeasureState == 1) {
8907 r_rband.x = x;
8908 r_rband.y = y;
8909 }
8910
8911 RoutePoint *pMousePoint =
8912 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8913 wxEmptyString, wxEmptyString);
8914 pMousePoint->m_bShowName = false;
8915 pMousePoint->SetShowWaypointRangeRings(false);
8916
8917 m_pMeasureRoute->AddPoint(pMousePoint);
8918
8919 m_prev_rlat = m_cursor_lat;
8920 m_prev_rlon = m_cursor_lon;
8921 m_prev_pMousePoint = pMousePoint;
8922 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8923
8924 m_nMeasureState++;
8925 top_frame::Get()->RefreshAllCanvas();
8926 ret = true;
8927 }
8928
8929 else {
8930 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8931 }
8932 } // !g_btouch
8933 else { // g_btouch
8934 m_last_touch_down_pos = event.GetPosition();
8935
8936 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8937 // if near screen edge, pan with injection
8938 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8939 // return;
8940 // }
8941 }
8942 }
8943
8944 if (ret) return true;
8945 }
8946
8947 if (event.Dragging()) {
8948 // in touch screen mode ensure the finger/cursor is on the selected point's
8949 // radius to allow dragging
8950 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8951 if (g_btouch) {
8952 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8953 SelectItem *pFind = NULL;
8954 SelectableItemList SelList = pSelect->FindSelectionList(
8955 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8956 for (SelectItem *pFind : SelList) {
8957 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8958 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8959 }
8960 }
8961
8962 // Check for use of dragHandle
8963 if (m_pRoutePointEditTarget &&
8964 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8965 SelectItem *pFind = NULL;
8966 SelectableItemList SelList = pSelect->FindSelectionList(
8967 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8968 for (SelectItem *pFind : SelList) {
8969 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8970 if (m_pRoutePointEditTarget == frp) {
8971 m_bIsInRadius = true;
8972 break;
8973 }
8974 }
8975
8976 if (!m_dragoffsetSet) {
8977 RoutePointGui(*m_pRoutePointEditTarget)
8978 .PresetDragOffset(this, mouse_x, mouse_y);
8979 m_dragoffsetSet = true;
8980 }
8981 }
8982 }
8983
8984 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8985 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8986
8987 if (NULL == g_pMarkInfoDialog) {
8988 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8989 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8990 DraggingAllowed = false;
8991
8992 if (m_pRoutePointEditTarget &&
8993 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8994 DraggingAllowed = false;
8995
8996 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8997
8998 if (DraggingAllowed) {
8999 if (!undo->InUndoableAction()) {
9000 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9001 Undo_NeedsCopy, m_pFoundPoint);
9002 }
9003
9004 // Get the update rectangle for the union of the un-edited routes
9005 wxRect pre_rect;
9006
9007 if (!g_bopengl && m_pEditRouteArray) {
9008 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9009 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9010 // Need to validate route pointer
9011 // Route may be gone due to drgging close to ownship with
9012 // "Delete On Arrival" state set, as in the case of
9013 // navigating to an isolated waypoint on a temporary route
9014 if (g_pRouteMan->IsRouteValid(pr)) {
9015 wxRect route_rect;
9016 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9017 pre_rect.Union(route_rect);
9018 }
9019 }
9020 }
9021
9022 double new_cursor_lat = m_cursor_lat;
9023 double new_cursor_lon = m_cursor_lon;
9024
9025 if (CheckEdgePan(x, y, true, 5, 2))
9026 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9027
9028 // update the point itself
9029 if (g_btouch) {
9030 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9031 // new_cursor_lat, new_cursor_lon);
9032 RoutePointGui(*m_pRoutePointEditTarget)
9033 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9034 // update the Drag Handle entry in the pSelect list
9035 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9036 m_pRoutePointEditTarget,
9037 SELTYPE_DRAGHANDLE);
9038 m_pFoundPoint->m_slat =
9039 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9040 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9041 } else {
9042 m_pRoutePointEditTarget->m_lat =
9043 new_cursor_lat; // update the RoutePoint entry
9044 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9045 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9046 m_pFoundPoint->m_slat =
9047 new_cursor_lat; // update the SelectList entry
9048 m_pFoundPoint->m_slon = new_cursor_lon;
9049 }
9050
9051 // Update the MarkProperties Dialog, if currently shown
9052 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9053 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9054 g_pMarkInfoDialog->UpdateProperties(true);
9055 }
9056
9057 if (g_bopengl) {
9058 // InvalidateGL();
9059 Refresh(false);
9060 } else {
9061 // Get the update rectangle for the edited route
9062 wxRect post_rect;
9063
9064 if (m_pEditRouteArray) {
9065 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9066 ir++) {
9067 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9068 if (g_pRouteMan->IsRouteValid(pr)) {
9069 wxRect route_rect;
9070 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9071 post_rect.Union(route_rect);
9072 }
9073 }
9074 }
9075
9076 // Invalidate the union region
9077 pre_rect.Union(post_rect);
9078 RefreshRect(pre_rect, false);
9079 }
9080 top_frame::Get()->RefreshCanvasOther(this);
9081 m_bRoutePoinDragging = true;
9082 }
9083 ret = true;
9084 } // if Route Editing
9085
9086 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9087 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9088
9089 if (NULL == g_pMarkInfoDialog) {
9090 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9091 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9092 DraggingAllowed = false;
9093
9094 if (m_pRoutePointEditTarget &&
9095 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9096 DraggingAllowed = false;
9097
9098 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9099
9100 if (DraggingAllowed) {
9101 if (!undo->InUndoableAction()) {
9102 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9103 Undo_NeedsCopy, m_pFoundPoint);
9104 }
9105
9106 // The mark may be an anchorwatch
9107 double lpp1 = 0.;
9108 double lpp2 = 0.;
9109 double lppmax;
9110
9111 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9112 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9113 }
9114 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9115 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9116 }
9117 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9118
9119 // Get the update rectangle for the un-edited mark
9120 wxRect pre_rect;
9121 if (!g_bopengl) {
9122 RoutePointGui(*m_pRoutePointEditTarget)
9123 .CalculateDCRect(m_dc_route, this, &pre_rect);
9124 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9125 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9126 (int)(lppmax - (pre_rect.height / 2)));
9127 }
9128
9129 // update the point itself
9130 if (g_btouch) {
9131 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9132 // m_cursor_lat, m_cursor_lon);
9133 RoutePointGui(*m_pRoutePointEditTarget)
9134 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9135 // update the Drag Handle entry in the pSelect list
9136 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9137 m_pRoutePointEditTarget,
9138 SELTYPE_DRAGHANDLE);
9139 m_pFoundPoint->m_slat =
9140 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9141 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9142 } else {
9143 m_pRoutePointEditTarget->m_lat =
9144 m_cursor_lat; // update the RoutePoint entry
9145 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9146 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9147 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9148 m_pFoundPoint->m_slon = m_cursor_lon;
9149 }
9150
9151 // Update the MarkProperties Dialog, if currently shown
9152 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9153 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9154 g_pMarkInfoDialog->UpdateProperties(true);
9155 }
9156
9157 // Invalidate the union region
9158 if (g_bopengl) {
9159 if (!g_btouch) InvalidateGL();
9160 Refresh(false);
9161 } else {
9162 // Get the update rectangle for the edited mark
9163 wxRect post_rect;
9164 RoutePointGui(*m_pRoutePointEditTarget)
9165 .CalculateDCRect(m_dc_route, this, &post_rect);
9166 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9167 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9168 (int)(lppmax - (post_rect.height / 2)));
9169
9170 // Invalidate the union region
9171 pre_rect.Union(post_rect);
9172 RefreshRect(pre_rect, false);
9173 }
9174 top_frame::Get()->RefreshCanvasOther(this);
9175 m_bRoutePoinDragging = true;
9176 }
9177 ret = g_btouch ? m_bRoutePoinDragging : true;
9178 }
9179
9180 if (ret) return true;
9181 } // dragging
9182
9183 if (event.LeftUp()) {
9184 bool b_startedit_route = false;
9185 m_dragoffsetSet = false;
9186
9187 if (g_btouch) {
9188 m_bChartDragging = false;
9189 m_bIsInRadius = false;
9190
9191 if (m_routeState) // creating route?
9192 {
9193 // Check displacement from touchdown to distinguish taps from pans.
9194 // On touchscreens, even a "tap" can have several pixels of jitter,
9195 // so use a generous threshold (20px) to avoid false positives.
9196 int dx = x - m_touchdownPos.x;
9197 int dy = y - m_touchdownPos.y;
9198 int dist2 = dx * dx + dy * dy;
9199 bool wasPan = (dist2 > 20 * 20);
9200
9201 if (wasPan) {
9202 return false;
9203 }
9204
9205 if (m_bedge_pan) {
9206 m_bedge_pan = false;
9207 return false;
9208 }
9209
9210 double rlat, rlon;
9211 bool appending = false;
9212 bool inserting = false;
9213 Route *tail = 0;
9214
9215 rlat = m_cursor_lat;
9216 rlon = m_cursor_lon;
9217
9218 if (m_pRoutePointEditTarget) {
9219 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9220 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9221 if (!g_bopengl) {
9222 wxRect wp_rect;
9223 RoutePointGui(*m_pRoutePointEditTarget)
9224 .CalculateDCRect(m_dc_route, this, &wp_rect);
9225 RefreshRect(wp_rect, true);
9226 }
9227 m_pRoutePointEditTarget = NULL;
9228 }
9229 m_bRouteEditing = true;
9230
9231 if (m_routeState == 1) {
9232 m_pMouseRoute = new Route();
9233 m_pMouseRoute->SetHiLite(50);
9234 pRouteList->push_back(m_pMouseRoute);
9235 r_rband.x = x;
9236 r_rband.y = y;
9237 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9238 }
9239
9240 // Check to see if there is a nearby point which may be reused
9241 RoutePoint *pMousePoint = NULL;
9242
9243 // Calculate meaningful SelectRadius
9244 double nearby_radius_meters =
9245 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9246
9247 RoutePoint *pNearbyPoint =
9248 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9249 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9250 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9251 int dlg_return;
9252#ifndef __WXOSX__
9253 m_FinishRouteOnKillFocus =
9254 false; // Avoid route finish on focus change for message dialog
9255 dlg_return = OCPNMessageBox(
9256 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9257 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9258 m_FinishRouteOnKillFocus = true;
9259#else
9260 dlg_return = wxID_YES;
9261#endif
9262 if (dlg_return == wxID_YES) {
9263 pMousePoint = pNearbyPoint;
9264
9265 // Using existing waypoint, so nothing to delete for undo.
9266 if (m_routeState > 1)
9267 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9268 Undo_HasParent, NULL);
9269 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9270
9271 bool procede = false;
9272 if (tail) {
9273 procede = true;
9274 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9275 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9276 procede = false;
9277 }
9278
9279 if (procede) {
9280 int dlg_return;
9281 m_FinishRouteOnKillFocus = false;
9282 if (m_routeState == 1) { // first point in new route, preceeding
9283 // route to be added? touch case
9284
9285 wxString dmsg =
9286 _("Insert first part of this route in the new route?");
9287 if (tail->GetIndexOf(pMousePoint) ==
9288 tail->GetnPoints()) // Starting on last point of another
9289 // route?
9290 dmsg = _("Insert this route in the new route?");
9291
9292 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9293 dlg_return =
9294 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9295 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9296 m_FinishRouteOnKillFocus = true;
9297
9298 if (dlg_return == wxID_YES) {
9299 inserting = true; // part of the other route will be
9300 // preceeding the new route
9301 }
9302 }
9303 } else {
9304 wxString dmsg =
9305 _("Append last part of this route to the new route?");
9306 if (tail->GetIndexOf(pMousePoint) == 1)
9307 dmsg = _(
9308 "Append this route to the new route?"); // Picking the
9309 // first point of
9310 // another route?
9311
9312 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9313 dlg_return =
9314 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9315 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9316 m_FinishRouteOnKillFocus = true;
9317
9318 if (dlg_return == wxID_YES) {
9319 appending = true; // part of the other route will be
9320 // appended to the new route
9321 }
9322 }
9323 }
9324 }
9325
9326 // check all other routes to see if this point appears in any other
9327 // route If it appears in NO other route, then it should e
9328 // considered an isolated mark
9329 if (!FindRouteContainingWaypoint(pMousePoint))
9330 pMousePoint->SetShared(true);
9331 }
9332 }
9333
9334 if (NULL == pMousePoint) { // need a new point
9335 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9336 "", wxEmptyString);
9337 pMousePoint->SetNameShown(false);
9338
9339 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9340
9341 if (m_routeState > 1)
9342 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9343 Undo_IsOrphanded, NULL);
9344 }
9345
9346 if (m_routeState == 1) {
9347 // First point in the route.
9348 m_pMouseRoute->AddPoint(pMousePoint);
9349 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9350
9351 } else {
9352 if (m_pMouseRoute->m_NextLegGreatCircle) {
9353 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9354 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9355 &rhumbBearing, &rhumbDist);
9356 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9357 &gcDist, &gcBearing, NULL);
9358 double gcDistNM = gcDist / 1852.0;
9359
9360 // Empirically found expression to get reasonable route segments.
9361 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9362 pow(rhumbDist - gcDistNM - 1, 0.5);
9363
9364 wxString msg;
9365 msg << _("For this leg the Great Circle route is ")
9366 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9367 << _(" shorter than rhumbline.\n\n")
9368 << _("Would you like include the Great Circle routing points "
9369 "for this leg?");
9370
9371#ifndef __WXOSX__
9372 m_FinishRouteOnKillFocus = false;
9373 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9374 wxYES_NO | wxNO_DEFAULT);
9375 m_FinishRouteOnKillFocus = true;
9376#else
9377 int answer = wxID_NO;
9378#endif
9379
9380 if (answer == wxID_YES) {
9381 RoutePoint *gcPoint;
9382 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9383 wxRealPoint gcCoord;
9384
9385 for (int i = 1; i <= segmentCount; i++) {
9386 double fraction = (double)i * (1.0 / (double)segmentCount);
9387 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9388 gcDist * fraction, gcBearing,
9389 &gcCoord.x, &gcCoord.y, NULL);
9390
9391 if (i < segmentCount) {
9392 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9393 wxEmptyString);
9394 gcPoint->SetNameShown(false);
9395 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9396 gcPoint);
9397 } else {
9398 gcPoint = pMousePoint; // Last point, previously exsisting!
9399 }
9400
9401 m_pMouseRoute->AddPoint(gcPoint);
9402 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9403
9404 pSelect->AddSelectableRouteSegment(
9405 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9406 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9407 prevGcPoint = gcPoint;
9408 }
9409
9410 undo->CancelUndoableAction(true);
9411
9412 } else {
9413 m_pMouseRoute->AddPoint(pMousePoint);
9414 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9415 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9416 rlon, m_prev_pMousePoint,
9417 pMousePoint, m_pMouseRoute);
9418 undo->AfterUndoableAction(m_pMouseRoute);
9419 }
9420 } else {
9421 // Ordinary rhumblinesegment.
9422 m_pMouseRoute->AddPoint(pMousePoint);
9423 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9424
9425 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9426 rlon, m_prev_pMousePoint,
9427 pMousePoint, m_pMouseRoute);
9428 undo->AfterUndoableAction(m_pMouseRoute);
9429 }
9430 }
9431
9432 m_prev_rlat = rlat;
9433 m_prev_rlon = rlon;
9434 m_prev_pMousePoint = pMousePoint;
9435 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9436
9437 m_routeState++;
9438
9439 if (appending ||
9440 inserting) { // Appending a route or making a new route
9441 int connect = tail->GetIndexOf(pMousePoint);
9442 if (connect == 1) {
9443 inserting = false; // there is nothing to insert
9444 appending = true; // so append
9445 }
9446 int length = tail->GetnPoints();
9447
9448 int i;
9449 int start, stop;
9450 if (appending) {
9451 start = connect + 1;
9452 stop = length;
9453 } else { // inserting
9454 start = 1;
9455 stop = connect;
9456 m_pMouseRoute->RemovePoint(
9457 m_pMouseRoute
9458 ->GetLastPoint()); // Remove the first and only point
9459 }
9460 for (i = start; i <= stop; i++) {
9461 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9462 if (m_pMouseRoute)
9463 m_pMouseRoute->m_lastMousePointIndex =
9464 m_pMouseRoute->GetnPoints();
9465 m_routeState++;
9466 top_frame::Get()->RefreshAllCanvas();
9467 ret = true;
9468 }
9469 m_prev_rlat =
9470 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9471 m_prev_rlon =
9472 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9473 m_pMouseRoute->FinalizeForRendering();
9474 }
9475
9476 Refresh(true);
9477 ret = true;
9478 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9479 {
9480 if (m_bedge_pan) {
9481 m_bedge_pan = false;
9482 return false;
9483 }
9484
9485 // Skip if user panned (same logic as route handler above)
9486 {
9487 int mdx = x - m_touchdownPos.x;
9488 int mdy = y - m_touchdownPos.y;
9489 if (mdx * mdx + mdy * mdy > 20 * 20) return false;
9490 }
9491
9492 if (m_nMeasureState == 1) {
9493 m_pMeasureRoute = new Route();
9494 pRouteList->push_back(m_pMeasureRoute);
9495 r_rband.x = x;
9496 r_rband.y = y;
9497 }
9498
9499 if (m_pMeasureRoute) {
9500 RoutePoint *pMousePoint =
9501 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9502 wxEmptyString, wxEmptyString);
9503 pMousePoint->m_bShowName = false;
9504
9505 m_pMeasureRoute->AddPoint(pMousePoint);
9506
9507 m_prev_rlat = m_cursor_lat;
9508 m_prev_rlon = m_cursor_lon;
9509 m_prev_pMousePoint = pMousePoint;
9510 m_pMeasureRoute->m_lastMousePointIndex =
9511 m_pMeasureRoute->GetnPoints();
9512
9513 m_nMeasureState++;
9514 } else {
9515 CancelMeasureRoute();
9516 }
9517
9518 Refresh(true);
9519 ret = true;
9520 } else {
9521 bool bSelectAllowed = true;
9522 if (NULL == g_pMarkInfoDialog) {
9523 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9524 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9525 bSelectAllowed = false;
9526
9527 // Avoid accidental selection of routepoint if last touchdown started
9528 // a significant chart drag operation
9529 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9530 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9531 significant_drag) ||
9532 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9533 significant_drag)) {
9534 bSelectAllowed = false;
9535 }
9536
9537 /*if this left up happens at the end of a route point dragging and if
9538 the cursor/thumb is on the draghandle icon, not on the point iself a new
9539 selection will select nothing and the drag will never be ended, so the
9540 legs around this point never selectable. At this step we don't need a
9541 new selection, just keep the previoulsly selected and dragged point */
9542 if (m_bRoutePoinDragging) bSelectAllowed = false;
9543
9544 if (bSelectAllowed) {
9545 bool b_was_editing_mark = m_bMarkEditing;
9546 bool b_was_editing_route = m_bRouteEditing;
9547 FindRoutePointsAtCursor(SelectRadius,
9548 true); // Possibly selecting a point in a
9549 // route for later dragging
9550
9551 /*route and a mark points in layer can't be dragged so should't be
9552 * selected and no draghandle icon*/
9553 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9554 m_pRoutePointEditTarget = NULL;
9555
9556 if (!b_was_editing_route) {
9557 if (m_pEditRouteArray) {
9558 b_startedit_route = true;
9559
9560 // Hide the track and route rollover during route point edit, not
9561 // needed, and may be confusing
9562 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9563 m_pTrackRolloverWin->IsActive(false);
9564 }
9565 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9566 m_pRouteRolloverWin->IsActive(false);
9567 }
9568
9569 wxRect pre_rect;
9570 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9571 ir++) {
9572 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9573 // Need to validate route pointer
9574 // Route may be gone due to drgging close to ownship with
9575 // "Delete On Arrival" state set, as in the case of
9576 // navigating to an isolated waypoint on a temporary route
9577 if (g_pRouteMan->IsRouteValid(pr)) {
9578 // pr->SetHiLite(50);
9579 wxRect route_rect;
9580 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9581 pre_rect.Union(route_rect);
9582 }
9583 }
9584 RefreshRect(pre_rect, true);
9585 }
9586 } else {
9587 b_startedit_route = false;
9588 }
9589
9590 // Mark editing in touch mode, left-up event.
9591 if (m_pRoutePointEditTarget) {
9592 if (b_was_editing_mark ||
9593 b_was_editing_route) { // kill previous hilight
9594 if (m_lastRoutePointEditTarget) {
9595 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9596 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9597 RoutePointGui(*m_lastRoutePointEditTarget)
9598 .EnableDragHandle(false);
9599 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9600 SELTYPE_DRAGHANDLE);
9601 }
9602 }
9603
9604 if (m_pRoutePointEditTarget) {
9605 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9606 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9607 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9608 wxPoint2DDouble dragHandlePoint =
9609 RoutePointGui(*m_pRoutePointEditTarget)
9610 .GetDragHandlePoint(this);
9611 pSelect->AddSelectablePoint(
9612 dragHandlePoint.m_y, dragHandlePoint.m_x,
9613 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9614 }
9615 } else { // Deselect everything
9616 if (m_lastRoutePointEditTarget) {
9617 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9618 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9619 RoutePointGui(*m_lastRoutePointEditTarget)
9620 .EnableDragHandle(false);
9621 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9622 SELTYPE_DRAGHANDLE);
9623
9624 // Clear any routes being edited, probably orphans
9625 wxArrayPtrVoid *lastEditRouteArray =
9627 m_lastRoutePointEditTarget);
9628 if (lastEditRouteArray) {
9629 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9630 ir++) {
9631 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9632 if (g_pRouteMan->IsRouteValid(pr)) {
9633 pr->m_bIsBeingEdited = false;
9634 }
9635 }
9636 delete lastEditRouteArray;
9637 }
9638 }
9639 }
9640
9641 // Do the refresh
9642
9643 if (g_bopengl) {
9644 InvalidateGL();
9645 Refresh(false);
9646 } else {
9647 if (m_lastRoutePointEditTarget) {
9648 wxRect wp_rect;
9649 RoutePointGui(*m_lastRoutePointEditTarget)
9650 .CalculateDCRect(m_dc_route, this, &wp_rect);
9651 RefreshRect(wp_rect, true);
9652 }
9653
9654 if (m_pRoutePointEditTarget) {
9655 wxRect wp_rect;
9656 RoutePointGui(*m_pRoutePointEditTarget)
9657 .CalculateDCRect(m_dc_route, this, &wp_rect);
9658 RefreshRect(wp_rect, true);
9659 }
9660 }
9661 }
9662 } // bSelectAllowed
9663
9664 // Check to see if there is a route or AIS target under the cursor
9665 // If so, start the rollover timer which creates the popup
9666 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9667 bool b_start_rollover = false;
9668 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9669 SelectItem *pFind = pSelectAIS->FindSelection(
9670 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9671 if (pFind) b_start_rollover = true;
9672 }
9673
9674 if (!b_start_rollover && !b_startedit_route) {
9675 SelectableItemList SelList = pSelect->FindSelectionList(
9676 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9677 for (SelectItem *pFindSel : SelList) {
9678 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9679 if (pr && pr->IsVisible()) {
9680 b_start_rollover = true;
9681 break;
9682 }
9683 } // while
9684 }
9685
9686 if (!b_start_rollover && !b_startedit_route) {
9687 SelectableItemList SelList = pSelect->FindSelectionList(
9688 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9689 for (SelectItem *pFindSel : SelList) {
9690 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9691 if (tr && tr->IsVisible()) {
9692 b_start_rollover = true;
9693 break;
9694 }
9695 } // while
9696 }
9697
9698 if (b_start_rollover)
9699 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9700 wxTIMER_ONE_SHOT);
9701 Route *tail = 0;
9702 Route *current = 0;
9703 bool appending = false;
9704 bool inserting = false;
9705 int connect = 0;
9706 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9707 // drag
9708 if (m_pRoutePointEditTarget) {
9709 // Check to see if there is a nearby point which may replace the
9710 // dragged one
9711 RoutePoint *pMousePoint = NULL;
9712
9713 int index_last;
9714 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9715 double nearby_radius_meters =
9716 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9717 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9718 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9719 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9720 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9721 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9722 bool duplicate =
9723 false; // ensure we won't create duplicate point in routes
9724 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9725 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9726 ir++) {
9727 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9728 if (pr && pr->pRoutePointList) {
9729 auto *list = pr->pRoutePointList;
9730 auto pos =
9731 std::find(list->begin(), list->end(), pNearbyPoint);
9732 if (pos != list->end()) {
9733 duplicate = true;
9734 break;
9735 }
9736 }
9737 }
9738 }
9739
9740 // Special case:
9741 // Allow "re-use" of a route's waypoints iff it is a simple
9742 // isolated route. This allows, for instance, creation of a closed
9743 // polygon route
9744 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9745
9746 if (!duplicate) {
9747 int dlg_return;
9748 dlg_return =
9749 OCPNMessageBox(this,
9750 _("Replace this RoutePoint by the nearby "
9751 "Waypoint?"),
9752 _("OpenCPN RoutePoint change"),
9753 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9754 if (dlg_return == wxID_YES) {
9755 /*double confirmation if the dragged point has been manually
9756 * created which can be important and could be deleted
9757 * unintentionally*/
9758
9759 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9760 pNearbyPoint);
9761 current =
9762 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9763
9764 if (tail && current && (tail != current)) {
9765 int dlg_return1;
9766 connect = tail->GetIndexOf(pNearbyPoint);
9767 int index_current_route =
9768 current->GetIndexOf(m_pRoutePointEditTarget);
9769 index_last = current->GetIndexOf(current->GetLastPoint());
9770 dlg_return1 = wxID_NO;
9771 if (index_last ==
9772 index_current_route) { // we are dragging the last
9773 // point of the route
9774 if (connect != tail->GetnPoints()) { // anything to do?
9775
9776 wxString dmsg(
9777 _("Last part of route to be appended to dragged "
9778 "route?"));
9779 if (connect == 1)
9780 dmsg =
9781 _("Full route to be appended to dragged route?");
9782
9783 dlg_return1 = OCPNMessageBox(
9784 this, dmsg, _("OpenCPN Route Create"),
9785 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9786 if (dlg_return1 == wxID_YES) {
9787 appending = true;
9788 }
9789 }
9790 } else if (index_current_route ==
9791 1) { // dragging the first point of the route
9792 if (connect != 1) { // anything to do?
9793
9794 wxString dmsg(
9795 _("First part of route to be inserted into dragged "
9796 "route?"));
9797 if (connect == tail->GetnPoints())
9798 dmsg = _(
9799 "Full route to be inserted into dragged route?");
9800
9801 dlg_return1 = OCPNMessageBox(
9802 this, dmsg, _("OpenCPN Route Create"),
9803 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9804 if (dlg_return1 == wxID_YES) {
9805 inserting = true;
9806 }
9807 }
9808 }
9809 }
9810
9811 if (m_pRoutePointEditTarget->IsShared()) {
9812 // dlg_return = wxID_NO;
9813 dlg_return = OCPNMessageBox(
9814 this,
9815 _("Do you really want to delete and replace this "
9816 "WayPoint") +
9817 "\n" + _("which has been created manually?"),
9818 ("OpenCPN RoutePoint warning"),
9819 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9820 }
9821 }
9822 if (dlg_return == wxID_YES) {
9823 pMousePoint = pNearbyPoint;
9824 if (pMousePoint->m_bIsolatedMark) {
9825 pMousePoint->SetShared(true);
9826 }
9827 pMousePoint->m_bIsolatedMark =
9828 false; // definitely no longer isolated
9829 pMousePoint->m_bIsInRoute = true;
9830 }
9831 }
9832 }
9833 }
9834 if (!pMousePoint)
9835 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9836
9837 if (m_pEditRouteArray) {
9838 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9839 ir++) {
9840 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9841 if (g_pRouteMan->IsRouteValid(pr)) {
9842 if (pMousePoint) { // remove the dragged point and insert the
9843 // nearby
9844 auto *list = pr->pRoutePointList;
9845 auto pos = std::find(list->begin(), list->end(),
9846 m_pRoutePointEditTarget);
9847
9848 pSelect->DeleteAllSelectableRoutePoints(pr);
9849 pSelect->DeleteAllSelectableRouteSegments(pr);
9850
9851 pr->pRoutePointList->insert(pos, pMousePoint);
9852 pos = std::find(list->begin(), list->end(),
9853 m_pRoutePointEditTarget);
9854 pr->pRoutePointList->erase(pos);
9855
9856 pSelect->AddAllSelectableRouteSegments(pr);
9857 pSelect->AddAllSelectableRoutePoints(pr);
9858 }
9859 pr->FinalizeForRendering();
9860 pr->UpdateSegmentDistances();
9861 if (m_bRoutePoinDragging) {
9862 // pConfig->UpdateRoute(pr);
9863 NavObj_dB::GetInstance().UpdateRoute(pr);
9864 }
9865 }
9866 }
9867 }
9868
9869 // Update the RouteProperties Dialog, if currently shown
9870 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9871 if (m_pEditRouteArray) {
9872 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9873 ir++) {
9874 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9875 if (g_pRouteMan->IsRouteValid(pr)) {
9876 if (pRoutePropDialog->GetRoute() == pr) {
9877 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9878 }
9879 /* cannot edit track points anyway
9880 else if ( ( NULL !=
9881 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9882 pTrackPropDialog->m_pTrack == pr ) {
9883 pTrackPropDialog->SetTrackAndUpdate(
9884 pr );
9885 }
9886 */
9887 }
9888 }
9889 }
9890 }
9891 if (pMousePoint) { // clear all about the dragged point
9892 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9893 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9894 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9895 // Hide mark properties dialog if open on the replaced point
9896 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9897 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9898 g_pMarkInfoDialog->Hide();
9899
9900 delete m_pRoutePointEditTarget;
9901 m_lastRoutePointEditTarget = NULL;
9902 m_pRoutePointEditTarget = NULL;
9903 undo->AfterUndoableAction(pMousePoint);
9904 undo->InvalidateUndo();
9905 }
9906 }
9907 }
9908
9909 else if (m_bMarkEditing) { // End of way point drag
9910 if (m_pRoutePointEditTarget)
9911 if (m_bRoutePoinDragging) {
9912 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9913 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9914 }
9915 }
9916
9917 if (m_pRoutePointEditTarget)
9918 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9919
9920 if (!m_pRoutePointEditTarget) {
9921 delete m_pEditRouteArray;
9922 m_pEditRouteArray = NULL;
9923 m_bRouteEditing = false;
9924 }
9925 m_bRoutePoinDragging = false;
9926
9927 if (appending) { // Appending to the route of which the last point is
9928 // dragged onto another route
9929
9930 // copy tail from connect until length to end of current after dragging
9931
9932 int length = tail->GetnPoints();
9933 for (int i = connect + 1; i <= length; i++) {
9934 current->AddPointAndSegment(tail->GetPoint(i), false);
9935 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9936 m_routeState++;
9937 top_frame::Get()->RefreshAllCanvas();
9938 ret = true;
9939 }
9940 current->FinalizeForRendering();
9941 current->m_bIsBeingEdited = false;
9942 FinishRoute();
9943 g_pRouteMan->DeleteRoute(tail);
9944 }
9945 if (inserting) {
9946 pSelect->DeleteAllSelectableRoutePoints(current);
9947 pSelect->DeleteAllSelectableRouteSegments(current);
9948 for (int i = 1; i < connect; i++) { // numbering in the tail route
9949 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9950 }
9951 pSelect->AddAllSelectableRouteSegments(current);
9952 pSelect->AddAllSelectableRoutePoints(current);
9953 current->FinalizeForRendering();
9954 current->m_bIsBeingEdited = false;
9955 g_pRouteMan->DeleteRoute(tail);
9956 }
9957
9958 // Update the RouteProperties Dialog, if currently shown
9959 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9960 if (m_pEditRouteArray) {
9961 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9962 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9963 if (g_pRouteMan->IsRouteValid(pr)) {
9964 if (pRoutePropDialog->GetRoute() == pr) {
9965 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9966 }
9967 }
9968 }
9969 }
9970 }
9971
9972 } // g_btouch
9973
9974 else { // !g_btouch
9975 if (m_bRouteEditing) { // End of RoutePoint drag
9976 Route *tail = 0;
9977 Route *current = 0;
9978 bool appending = false;
9979 bool inserting = false;
9980 int connect = 0;
9981 int index_last;
9982 if (m_pRoutePointEditTarget) {
9983 m_pRoutePointEditTarget->m_bBlink = false;
9984 // Check to see if there is a nearby point which may replace the
9985 // dragged one
9986 RoutePoint *pMousePoint = NULL;
9987 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9988 double nearby_radius_meters =
9989 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9990 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9991 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9992 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9993 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9994 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9995 bool duplicate = false; // don't create duplicate point in routes
9996 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9997 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9998 ir++) {
9999 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10000 if (pr && pr->pRoutePointList) {
10001 auto *list = pr->pRoutePointList;
10002 auto pos =
10003 std::find(list->begin(), list->end(), pNearbyPoint);
10004 if (pos != list->end()) {
10005 duplicate = true;
10006 break;
10007 }
10008 }
10009 }
10010 }
10011
10012 // Special case:
10013 // Allow "re-use" of a route's waypoints iff it is a simple
10014 // isolated route. This allows, for instance, creation of a closed
10015 // polygon route
10016 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
10017
10018 if (!duplicate) {
10019 int dlg_return;
10020 dlg_return =
10021 OCPNMessageBox(this,
10022 _("Replace this RoutePoint by the nearby "
10023 "Waypoint?"),
10024 _("OpenCPN RoutePoint change"),
10025 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10026 if (dlg_return == wxID_YES) {
10027 /*double confirmation if the dragged point has been manually
10028 * created which can be important and could be deleted
10029 * unintentionally*/
10030 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10031 pNearbyPoint);
10032 current =
10033 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10034
10035 if (tail && current && (tail != current)) {
10036 int dlg_return1;
10037 connect = tail->GetIndexOf(pNearbyPoint);
10038 int index_current_route =
10039 current->GetIndexOf(m_pRoutePointEditTarget);
10040 index_last = current->GetIndexOf(current->GetLastPoint());
10041 dlg_return1 = wxID_NO;
10042 if (index_last ==
10043 index_current_route) { // we are dragging the last
10044 // point of the route
10045 if (connect != tail->GetnPoints()) { // anything to do?
10046
10047 wxString dmsg(
10048 _("Last part of route to be appended to dragged "
10049 "route?"));
10050 if (connect == 1)
10051 dmsg =
10052 _("Full route to be appended to dragged route?");
10053
10054 dlg_return1 = OCPNMessageBox(
10055 this, dmsg, _("OpenCPN Route Create"),
10056 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10057 if (dlg_return1 == wxID_YES) {
10058 appending = true;
10059 }
10060 }
10061 } else if (index_current_route ==
10062 1) { // dragging the first point of the route
10063 if (connect != 1) { // anything to do?
10064
10065 wxString dmsg(
10066 _("First part of route to be inserted into dragged "
10067 "route?"));
10068 if (connect == tail->GetnPoints())
10069 dmsg = _(
10070 "Full route to be inserted into dragged route?");
10071
10072 dlg_return1 = OCPNMessageBox(
10073 this, dmsg, _("OpenCPN Route Create"),
10074 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10075 if (dlg_return1 == wxID_YES) {
10076 inserting = true;
10077 }
10078 }
10079 }
10080 }
10081
10082 if (m_pRoutePointEditTarget->IsShared()) {
10083 dlg_return = wxID_NO;
10084 dlg_return = OCPNMessageBox(
10085 this,
10086 _("Do you really want to delete and replace this "
10087 "WayPoint") +
10088 "\n" + _("which has been created manually?"),
10089 ("OpenCPN RoutePoint warning"),
10090 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10091 }
10092 }
10093 if (dlg_return == wxID_YES) {
10094 pMousePoint = pNearbyPoint;
10095 if (pMousePoint->m_bIsolatedMark) {
10096 pMousePoint->SetShared(true);
10097 }
10098 pMousePoint->m_bIsolatedMark =
10099 false; // definitely no longer isolated
10100 pMousePoint->m_bIsInRoute = true;
10101 }
10102 }
10103 }
10104 }
10105 if (!pMousePoint)
10106 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10107
10108 if (m_pEditRouteArray) {
10109 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10110 ir++) {
10111 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10112 if (g_pRouteMan->IsRouteValid(pr)) {
10113 if (pMousePoint) { // replace dragged point by nearby one
10114 auto *list = pr->pRoutePointList;
10115 auto pos = std::find(list->begin(), list->end(),
10116 m_pRoutePointEditTarget);
10117
10118 pSelect->DeleteAllSelectableRoutePoints(pr);
10119 pSelect->DeleteAllSelectableRouteSegments(pr);
10120
10121 pr->pRoutePointList->insert(pos, pMousePoint);
10122 pos = std::find(list->begin(), list->end(),
10123 m_pRoutePointEditTarget);
10124 if (pos != list->end()) list->erase(pos);
10125 // pr->pRoutePointList->erase(pos + 1);
10126
10127 pSelect->AddAllSelectableRouteSegments(pr);
10128 pSelect->AddAllSelectableRoutePoints(pr);
10129 }
10130 pr->FinalizeForRendering();
10131 pr->UpdateSegmentDistances();
10132 pr->m_bIsBeingEdited = false;
10133
10134 if (m_bRoutePoinDragging) {
10135 // Special case optimization.
10136 // Dragging a single point of a route
10137 // without any point additions or re-ordering
10138 if (!pMousePoint)
10139 NavObj_dB::GetInstance().UpdateRoutePoint(
10140 m_pRoutePointEditTarget);
10141 else
10142 NavObj_dB::GetInstance().UpdateRoute(pr);
10143 }
10144 pr->SetHiLite(0);
10145 }
10146 }
10147 Refresh(false);
10148 }
10149
10150 if (appending) {
10151 // copy tail from connect until length to end of current after
10152 // dragging
10153
10154 int length = tail->GetnPoints();
10155 for (int i = connect + 1; i <= length; i++) {
10156 current->AddPointAndSegment(tail->GetPoint(i), false);
10157 if (current)
10158 current->m_lastMousePointIndex = current->GetnPoints();
10159 m_routeState++;
10160 top_frame::Get()->RefreshAllCanvas();
10161 ret = true;
10162 }
10163 current->FinalizeForRendering();
10164 current->m_bIsBeingEdited = false;
10165 FinishRoute();
10166 g_pRouteMan->DeleteRoute(tail);
10167 }
10168 if (inserting) {
10169 pSelect->DeleteAllSelectableRoutePoints(current);
10170 pSelect->DeleteAllSelectableRouteSegments(current);
10171 for (int i = 1; i < connect; i++) { // numbering in the tail route
10172 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10173 }
10174 pSelect->AddAllSelectableRouteSegments(current);
10175 pSelect->AddAllSelectableRoutePoints(current);
10176 current->FinalizeForRendering();
10177 current->m_bIsBeingEdited = false;
10178 g_pRouteMan->DeleteRoute(tail);
10179 }
10180
10181 // Update the RouteProperties Dialog, if currently shown
10182 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10183 if (m_pEditRouteArray) {
10184 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10185 ir++) {
10186 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10187 if (g_pRouteMan->IsRouteValid(pr)) {
10188 if (pRoutePropDialog->GetRoute() == pr) {
10189 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10190 }
10191 }
10192 }
10193 }
10194 }
10195
10196 if (pMousePoint) {
10197 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10198 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10199 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10200 // Hide mark properties dialog if open on the replaced point
10201 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10202 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10203 g_pMarkInfoDialog->Hide();
10204
10205 delete m_pRoutePointEditTarget;
10206 m_lastRoutePointEditTarget = NULL;
10207 undo->AfterUndoableAction(pMousePoint);
10208 undo->InvalidateUndo();
10209 } else {
10210 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10211 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10212
10213 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10214 }
10215
10216 delete m_pEditRouteArray;
10217 m_pEditRouteArray = NULL;
10218 }
10219
10220 InvalidateGL();
10221 m_bRouteEditing = false;
10222 m_pRoutePointEditTarget = NULL;
10223
10224 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10225 ret = true;
10226 }
10227
10228 else if (m_bMarkEditing) { // end of Waypoint drag
10229 if (m_pRoutePointEditTarget) {
10230 if (m_bRoutePoinDragging) {
10231 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10232 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10233 }
10234 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10235 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10236 if (!g_bopengl) {
10237 wxRect wp_rect;
10238 RoutePointGui(*m_pRoutePointEditTarget)
10239 .CalculateDCRect(m_dc_route, this, &wp_rect);
10240 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10241 RefreshRect(wp_rect, true);
10242 }
10243 }
10244 m_pRoutePointEditTarget = NULL;
10245 m_bMarkEditing = false;
10246 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10247 ret = true;
10248 }
10249
10250 else if (leftIsDown) { // left click for chart center
10251 leftIsDown = false;
10252 ret = false;
10253
10254 if (!g_btouch) {
10255 if (!m_bChartDragging && !m_bMeasure_Active) {
10256 } else {
10257 m_bChartDragging = false;
10258 }
10259 }
10260 }
10261 m_bRoutePoinDragging = false;
10262 } // !btouch
10263
10264 if (ret) return true;
10265 } // left up
10266
10267 if (event.RightDown()) {
10268 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10269 last_drag.x = mx;
10270 last_drag.y = my;
10271
10272 if (g_btouch) {
10273 // if( m_pRoutePointEditTarget )
10274 // return false;
10275 }
10276
10277 ret = true;
10278 m_FinishRouteOnKillFocus = false;
10279 CallPopupMenu(mx, my);
10280 m_FinishRouteOnKillFocus = true;
10281 } // Right down
10282
10283 return ret;
10284}
10285
10286bool panleftIsDown;
10287bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10288 // Skip all mouse processing if shift is held.
10289 // This allows plugins to implement shift+drag behaviors.
10290 if (event.ShiftDown()) {
10291 return false;
10292 }
10293 int x, y;
10294 event.GetPosition(&x, &y);
10295
10296 x *= m_displayScale;
10297 y *= m_displayScale;
10298
10299 // Check for wheel rotation
10300 // ideally, should be just longer than the time between
10301 // processing accumulated mouse events from the event queue
10302 // as would happen during screen redraws.
10303 int wheel_dir = event.GetWheelRotation();
10304
10305 if (wheel_dir) {
10306 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10307 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10308
10309 double factor = g_mouse_zoom_sensitivity;
10310 if (wheel_dir < 0) factor = 1 / factor;
10311
10312 if (g_bsmoothpanzoom) {
10313 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10314 if (wheel_dir == m_last_wheel_dir) {
10315 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10316 // m_zoom_target /= factor;
10317 } else
10318 StopMovement();
10319 } else {
10320 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10321 m_wheelstopwatch.Start(0);
10322 // m_zoom_target = VPoint.chart_scale / factor;
10323 }
10324 }
10325
10326 m_last_wheel_dir = wheel_dir;
10327
10328 ZoomCanvas(factor, true, false);
10329 }
10330
10331 if (event.LeftDown()) {
10332 // Skip the first left click if it will cause a canvas focus shift
10333 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10334 return false;
10335 }
10336
10337 last_drag.x = x, last_drag.y = y;
10338 m_touchdownPos = wxPoint(x, y);
10339 panleftIsDown = true;
10340 }
10341
10342 if (event.LeftUp()) {
10343 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10344 // seen here.
10345 panleftIsDown = false;
10346
10347 if (!g_btouch) {
10348 if (!m_bChartDragging && !m_bMeasure_Active) {
10349 switch (cursor_region) {
10350 case MID_RIGHT: {
10351 PanCanvas(100, 0);
10352 break;
10353 }
10354
10355 case MID_LEFT: {
10356 PanCanvas(-100, 0);
10357 break;
10358 }
10359
10360 case MID_TOP: {
10361 PanCanvas(0, 100);
10362 break;
10363 }
10364
10365 case MID_BOT: {
10366 PanCanvas(0, -100);
10367 break;
10368 }
10369
10370 case CENTER: {
10371 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10372 break;
10373 }
10374 }
10375 } else {
10376 m_bChartDragging = false;
10377 }
10378 }
10379 }
10380 }
10381
10382 if (event.Dragging() && event.LeftIsDown()) {
10383 /*
10384 * fixed dragging.
10385 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10386 * before the drag event. Hence, as there is no mouse down event, last_drag
10387 * is not reset before the drag. And that results in one single drag
10388 * session, meaning you cannot drag the map a few miles north, lift your
10389 * finger, and the go even further north. Instead, the map resets itself
10390 * always to the very first drag start (since there is not reset of
10391 * last_drag).
10392 *
10393 * Besides, should not left down and dragging be enough of a situation to
10394 * start a drag procedure?
10395 *
10396 * Anyways, guarded it to be active in touch situations only.
10397 */
10398 if (g_btouch && !m_inPinch) {
10399 struct timespec now;
10400 clock_gettime(CLOCK_MONOTONIC, &now);
10401 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10402
10403 bool trigger_hold = false;
10404 if (false == m_bChartDragging) {
10405 if (m_DragTrigger < 0) {
10406 // printf("\ntrigger1\n");
10407 m_DragTrigger = 0;
10408 m_DragTriggerStartTime = tnow;
10409 trigger_hold = true;
10410 } else {
10411 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10412 m_DragTrigger = -1; // Reset trigger
10413 // printf("trigger fired\n");
10414 }
10415 }
10416 }
10417 if (trigger_hold) return true;
10418
10419 if (false == m_bChartDragging) {
10420 // printf("starting drag\n");
10421 // Reset drag calculation members
10422 last_drag.x = x - 1, last_drag.y = y - 1;
10423 m_bChartDragging = true;
10424 m_chart_drag_total_time = 0;
10425 m_chart_drag_total_x = 0;
10426 m_chart_drag_total_y = 0;
10427 m_inertia_last_drag_x = x;
10428 m_inertia_last_drag_y = y;
10429 m_drag_vec_x.clear();
10430 m_drag_vec_y.clear();
10431 m_drag_vec_t.clear();
10432 m_last_drag_time = tnow;
10433 }
10434
10435 // Calculate and store drag dynamics.
10436 uint64_t delta_t = tnow - m_last_drag_time;
10437 double delta_tf = delta_t / 1e9;
10438
10439 m_chart_drag_total_time += delta_tf;
10440 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10441 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10442
10443 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10444 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10445 m_drag_vec_t.push_back(delta_tf);
10446
10447 m_inertia_last_drag_x = x;
10448 m_inertia_last_drag_y = y;
10449 m_last_drag_time = tnow;
10450
10451 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10452 m_bChartDragging = true;
10453 StartTimedMovement();
10454 m_pan_drag.x += last_drag.x - x;
10455 m_pan_drag.y += last_drag.y - y;
10456 last_drag.x = x, last_drag.y = y;
10457 }
10458 } else if (!g_btouch) {
10459 if ((last_drag.x != x) || (last_drag.y != y)) {
10460 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10461 // dragging on route create.
10462 // github #2994
10463 m_bChartDragging = true;
10464 StartTimedMovement();
10465 m_pan_drag.x += last_drag.x - x;
10466 m_pan_drag.y += last_drag.y - y;
10467 last_drag.x = x, last_drag.y = y;
10468 }
10469 }
10470 }
10471
10472 // Handle some special cases
10473 if (g_btouch) {
10474 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10475 // LeftUp handler uses displacement check to distinguish taps from pans.
10476 m_DoubleClickTimer->Start();
10477 singleClickEventIsValid = false;
10478 }
10479 }
10480 }
10481
10482 return true;
10483}
10484
10485void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10486 if (MouseEventOverlayWindows(event)) return;
10487
10488 if (MouseEventSetup(event)) return; // handled, no further action required
10489
10490 bool nm = MouseEventProcessObjects(event);
10491 if (!nm) MouseEventProcessCanvas(event);
10492}
10493
10494void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10495 // Switch to the appropriate cursor on mouse movement
10496
10497 wxCursor *ptarget_cursor = pCursorArrow;
10498 if (!pPlugIn_Cursor) {
10499 ptarget_cursor = pCursorArrow;
10500 if ((!m_routeState) &&
10501 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10502 if (cursor_region == MID_RIGHT) {
10503 ptarget_cursor = pCursorRight;
10504 } else if (cursor_region == MID_LEFT) {
10505 ptarget_cursor = pCursorLeft;
10506 } else if (cursor_region == MID_TOP) {
10507 ptarget_cursor = pCursorDown;
10508 } else if (cursor_region == MID_BOT) {
10509 ptarget_cursor = pCursorUp;
10510 } else {
10511 ptarget_cursor = pCursorArrow;
10512 }
10513 } else if (m_bMeasure_Active ||
10514 m_routeState) // If Measure tool use Pencil Cursor
10515 ptarget_cursor = pCursorPencil;
10516 } else {
10517 ptarget_cursor = pPlugIn_Cursor;
10518 }
10519
10520 SetCursor(*ptarget_cursor);
10521}
10522
10523void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10524 SetCursor(*pCursorArrow);
10525}
10526
10527void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10528 ChartPlugInWrapper *target_plugin_chart = NULL;
10529 s57chart *Chs57 = NULL;
10530 wxFileName file;
10531 wxArrayString files;
10532
10533 ChartBase *target_chart = GetChartAtCursor();
10534 if (target_chart) {
10535 file.Assign(target_chart->GetFullPath());
10536 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10537 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10538 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10539 else
10540 Chs57 = dynamic_cast<s57chart *>(target_chart);
10541 } else { // target_chart = null, might be mbtiles
10542 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10543 unsigned int im = stackIndexArray.size();
10544 int scale = 2147483647; // max 32b integer
10545 if (VPoint.b_quilt && im > 0) {
10546 for (unsigned int is = 0; is < im; is++) {
10547 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10548 CHART_TYPE_MBTILES) {
10549 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10550 double lat, lon;
10551 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10552 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10553 .GetBBox()
10554 .Contains(lat, lon)) {
10555 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10556 scale) {
10557 scale =
10558 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10559 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10560 }
10561 }
10562 }
10563 }
10564 }
10565 }
10566
10567 std::vector<Ais8_001_22 *> area_notices;
10568
10569 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10570 float vp_scale = GetVPScale();
10571
10572 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10573 auto target_data = target.second;
10574 if (!target_data->area_notices.empty()) {
10575 for (auto &ani : target_data->area_notices) {
10576 Ais8_001_22 &area_notice = ani.second;
10577
10578 BoundingBox bbox;
10579
10580 for (Ais8_001_22_SubAreaList::iterator sa =
10581 area_notice.sub_areas.begin();
10582 sa != area_notice.sub_areas.end(); ++sa) {
10583 switch (sa->shape) {
10584 case AIS8_001_22_SHAPE_CIRCLE: {
10585 wxPoint target_point;
10586 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10587 bbox.Expand(target_point);
10588 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10589 break;
10590 }
10591 case AIS8_001_22_SHAPE_RECT: {
10592 wxPoint target_point;
10593 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10594 bbox.Expand(target_point);
10595 if (sa->e_dim_m > sa->n_dim_m)
10596 bbox.EnLarge(sa->e_dim_m * vp_scale);
10597 else
10598 bbox.EnLarge(sa->n_dim_m * vp_scale);
10599 break;
10600 }
10601 case AIS8_001_22_SHAPE_POLYGON:
10602 case AIS8_001_22_SHAPE_POLYLINE: {
10603 for (int i = 0; i < 4; ++i) {
10604 double lat = sa->latitude;
10605 double lon = sa->longitude;
10606 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10607 &lat, &lon);
10608 wxPoint target_point;
10609 GetCanvasPointPix(lat, lon, &target_point);
10610 bbox.Expand(target_point);
10611 }
10612 break;
10613 }
10614 case AIS8_001_22_SHAPE_SECTOR: {
10615 double lat1 = sa->latitude;
10616 double lon1 = sa->longitude;
10617 double lat, lon;
10618 wxPoint target_point;
10619 GetCanvasPointPix(lat1, lon1, &target_point);
10620 bbox.Expand(target_point);
10621 for (int i = 0; i < 18; ++i) {
10622 ll_gc_ll(
10623 lat1, lon1,
10624 sa->left_bound_deg +
10625 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10626 sa->radius_m / 1852.0, &lat, &lon);
10627 GetCanvasPointPix(lat, lon, &target_point);
10628 bbox.Expand(target_point);
10629 }
10630 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10631 &lat, &lon);
10632 GetCanvasPointPix(lat, lon, &target_point);
10633 bbox.Expand(target_point);
10634 break;
10635 }
10636 }
10637 }
10638
10639 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10640 area_notices.push_back(&area_notice);
10641 }
10642 }
10643 }
10644 }
10645 }
10646
10647 if (target_chart || !area_notices.empty() || file.HasName()) {
10648 // Go get the array of all objects at the cursor lat/lon
10649 int sel_rad_pix = 5;
10650 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10651
10652 // Make sure we always get the lights from an object, even if we are
10653 // currently not displaying lights on the chart.
10654
10655 SetCursor(wxCURSOR_WAIT);
10656 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10657 if (!lightsVis) SetShowENCLights(true);
10658 ;
10659
10660 ListOfObjRazRules *rule_list = NULL;
10661 ListOfPI_S57Obj *pi_rule_list = NULL;
10662 if (Chs57)
10663 rule_list =
10664 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10665 else if (target_plugin_chart)
10666 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10667 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10668
10669 ListOfObjRazRules *overlay_rule_list = NULL;
10670 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10671 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10672
10673 if (CHs57_Overlay) {
10674 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10675 zlat, zlon, SelectRadius, &GetVP());
10676 }
10677
10678 if (!lightsVis) SetShowENCLights(false);
10679
10680 wxString objText;
10681 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10682 wxString face = dFont->GetFaceName();
10683
10684 if (NULL == g_pObjectQueryDialog) {
10686 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10687 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10688 }
10689
10690 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10691 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10692
10693#ifdef __WXOSX__
10694 // Auto Adjustment for dark mode
10695 fg = g_pObjectQueryDialog->GetForegroundColour();
10696#endif
10697
10698 objText.Printf(
10699 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10700 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10701
10702#ifdef __WXOSX__
10703 int points = dFont->GetPointSize();
10704#else
10705 int points = dFont->GetPointSize() + 1;
10706#endif
10707
10708 int sizes[7];
10709 for (int i = -2; i < 5; i++) {
10710 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10711 }
10712 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10713
10714 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10715
10716 if (overlay_rule_list && CHs57_Overlay) {
10717 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10718 objText << "<hr noshade>";
10719 }
10720
10721 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10722 an != area_notices.end(); ++an) {
10723 objText << "<b>AIS Area Notice:</b> ";
10724 objText << ais8_001_22_notice_names[(*an)->notice_type];
10725 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10726 (*an)->sub_areas.begin();
10727 sa != (*an)->sub_areas.end(); ++sa)
10728 if (!sa->text.empty()) objText << sa->text;
10729 objText << "<br>expires: " << (*an)->expiry_time.Format();
10730 objText << "<hr noshade>";
10731 }
10732
10733 if (Chs57)
10734 objText << Chs57->CreateObjDescriptions(rule_list);
10735 else if (target_plugin_chart)
10736 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10737 pi_rule_list);
10738
10739 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10740
10741 // Add the additional info files
10742 wxString AddFiles, filenameOK;
10743 int filecount = 0;
10744 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10745 // plugin
10746
10747 AddFiles = wxString::Format(
10748 "<hr noshade><br><b>Additional info files attached to: </b> "
10749 "<font "
10750 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10751 "cellpadding=3>",
10752 file.GetFullName());
10753 file.Normalize();
10754 file.Assign(file.GetPath(), "");
10755 wxDir dir(file.GetFullPath());
10756 wxString filename;
10757 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10758 while (cont) {
10759 file.Assign(dir.GetNameWithSep().append(filename));
10760 wxString FormatString =
10761 "<td valign=top><font size=-2><a "
10762 "href=\"%s\">%s</a></font></td>";
10763 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10764 filenameOK = file.GetFullPath(); // remember last valid name
10765 // we are making a 3 columns table. New row only every third file
10766 if (3 * ((int)filecount / 3) == filecount)
10767 FormatString.Prepend("<tr>"); // new row
10768 else
10769 FormatString.Prepend(
10770 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10771 // spacer column
10772
10773 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10774 file.GetFullName());
10775 filecount++;
10776 }
10777 cont = dir.GetNext(&filename);
10778 }
10779 objText << AddFiles << "</table>";
10780 }
10781 objText << "</font>";
10782 objText << "</body></html>";
10783
10784 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10785 g_pObjectQueryDialog->SetHTMLPage(objText);
10786 g_pObjectQueryDialog->Show();
10787 }
10788 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10789 // generate an event to avoid double code
10790 wxHtmlLinkInfo hli(filenameOK);
10791 wxHtmlLinkEvent hle(1, hli);
10792 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10793 }
10794
10795 if (rule_list) rule_list->Clear();
10796 delete rule_list;
10797
10798 if (overlay_rule_list) overlay_rule_list->Clear();
10799 delete overlay_rule_list;
10800
10801 if (pi_rule_list) pi_rule_list->Clear();
10802 delete pi_rule_list;
10803
10804 SetCursor(wxCURSOR_ARROW);
10805 }
10806}
10807
10808void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10809 bool bNew = false;
10810 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10811 // Dialog
10812 g_pMarkInfoDialog = new MarkInfoDlg(this);
10813 bNew = true;
10814 }
10815
10816 if (1 /*g_bresponsive*/) {
10817 wxSize canvas_size = GetSize();
10818
10819 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10820 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10821
10822 g_pMarkInfoDialog->Layout();
10823
10824 wxPoint canvas_pos = GetPosition();
10825 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10826
10827 bool newFit = false;
10828 if (canvas_size.x < fitted_size.x) {
10829 fitted_size.x = canvas_size.x - 40;
10830 if (canvas_size.y < fitted_size.y)
10831 fitted_size.y -= 40; // scrollbar added
10832 }
10833 if (canvas_size.y < fitted_size.y) {
10834 fitted_size.y = canvas_size.y - 40;
10835 if (canvas_size.x < fitted_size.x)
10836 fitted_size.x -= 40; // scrollbar added
10837 }
10838
10839 if (newFit) {
10840 g_pMarkInfoDialog->SetSize(fitted_size);
10841 g_pMarkInfoDialog->Centre();
10842 }
10843 }
10844
10845 markPoint->m_bRPIsBeingEdited = false;
10846
10847 wxString title_base = _("Mark Properties");
10848 if (markPoint->m_bIsInRoute) {
10849 title_base = _("Waypoint Properties");
10850 }
10851 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10852 g_pMarkInfoDialog->UpdateProperties();
10853 if (markPoint->m_bIsInLayer) {
10854 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10855 GetLayerName(markPoint->m_LayerID)));
10856 g_pMarkInfoDialog->SetDialogTitle(caption);
10857 } else
10858 g_pMarkInfoDialog->SetDialogTitle(title_base);
10859
10860 g_pMarkInfoDialog->Show();
10861 g_pMarkInfoDialog->Raise();
10862 g_pMarkInfoDialog->InitialFocus();
10863 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10864}
10865
10866void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10867 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10868 pRoutePropDialog->SetRouteAndUpdate(selected);
10869 // pNew->UpdateProperties();
10870 pRoutePropDialog->Show();
10871 pRoutePropDialog->Raise();
10872 return;
10873 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10874 this); // There is one global instance of the RouteProp Dialog
10875
10876 if (g_bresponsive) {
10877 wxSize canvas_size = GetSize();
10878 wxPoint canvas_pos = GetPosition();
10879 wxSize fitted_size = pRoutePropDialog->GetSize();
10880 ;
10881
10882 if (canvas_size.x < fitted_size.x) {
10883 fitted_size.x = canvas_size.x;
10884 if (canvas_size.y < fitted_size.y)
10885 fitted_size.y -= 20; // scrollbar added
10886 }
10887 if (canvas_size.y < fitted_size.y) {
10888 fitted_size.y = canvas_size.y;
10889 if (canvas_size.x < fitted_size.x)
10890 fitted_size.x -= 20; // scrollbar added
10891 }
10892
10893 pRoutePropDialog->SetSize(fitted_size);
10894 pRoutePropDialog->Centre();
10895
10896 // int xp = (canvas_size.x - fitted_size.x)/2;
10897 // int yp = (canvas_size.y - fitted_size.y)/2;
10898
10899 wxPoint xxp = ClientToScreen(canvas_pos);
10900 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10901 }
10902
10903 pRoutePropDialog->SetRouteAndUpdate(selected);
10904
10905 pRoutePropDialog->Show();
10906
10907 Refresh(false);
10908}
10909
10910void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10911 pTrackPropDialog = TrackPropDlg::getInstance(
10912 this); // There is one global instance of the RouteProp Dialog
10913
10914 pTrackPropDialog->SetTrackAndUpdate(selected);
10916
10917 pTrackPropDialog->Show();
10918
10919 Refresh(false);
10920}
10921
10922void pupHandler_PasteWaypoint() {
10923 Kml kml;
10924
10925 int pasteBuffer = kml.ParsePasteBuffer();
10926 RoutePoint *pasted = kml.GetParsedRoutePoint();
10927 if (!pasted) return;
10928
10929 double nearby_radius_meters =
10930 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10931
10932 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10933 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10934
10935 int answer = wxID_NO;
10936 if (nearPoint && !nearPoint->m_bIsInLayer) {
10937 wxString msg;
10938 msg << _(
10939 "There is an existing waypoint at the same location as the one you are "
10940 "pasting. Would you like to merge the pasted data with it?\n\n");
10941 msg << _("Answering 'No' will create a new waypoint at the same location.");
10942 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10943 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10944 }
10945
10946 if (answer == wxID_YES) {
10947 nearPoint->SetName(pasted->GetName());
10948 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10949 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10950 pRouteManagerDialog->UpdateWptListCtrl();
10951 }
10952
10953 if (answer == wxID_NO) {
10954 RoutePoint *newPoint = new RoutePoint(pasted);
10955 newPoint->m_bIsolatedMark = true;
10956 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10957 newPoint);
10958 // pConfig->AddNewWayPoint(newPoint, -1);
10959 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10960
10961 pWayPointMan->AddRoutePoint(newPoint);
10962 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10963 pRouteManagerDialog->UpdateWptListCtrl();
10964 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10965 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10966 }
10967
10968 top_frame::Get()->InvalidateAllGL();
10969 top_frame::Get()->RefreshAllCanvas(false);
10970}
10971
10972void pupHandler_PasteRoute() {
10973 Kml kml;
10974
10975 int pasteBuffer = kml.ParsePasteBuffer();
10976 Route *pasted = kml.GetParsedRoute();
10977 if (!pasted) return;
10978
10979 double nearby_radius_meters =
10980 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10981
10982 RoutePoint *curPoint;
10983 RoutePoint *nearPoint;
10984 RoutePoint *prevPoint = NULL;
10985
10986 bool mergepoints = false;
10987 bool createNewRoute = true;
10988 int existingWaypointCounter = 0;
10989
10990 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10991 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10992 nearPoint = pWayPointMan->GetNearbyWaypoint(
10993 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10994 if (nearPoint) {
10995 mergepoints = true;
10996 existingWaypointCounter++;
10997 // Small hack here to avoid both extending RoutePoint and repeating all
10998 // the GetNearbyWaypoint calculations. Use existin data field in
10999 // RoutePoint as temporary storage.
11000 curPoint->m_bPtIsSelected = true;
11001 }
11002 }
11003
11004 int answer = wxID_NO;
11005 if (mergepoints) {
11006 wxString msg;
11007 msg << _(
11008 "There are existing waypoints at the same location as some of the ones "
11009 "you are pasting. Would you like to just merge the pasted data into "
11010 "them?\n\n");
11011 msg << _("Answering 'No' will create all new waypoints for this route.");
11012 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
11013 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
11014
11015 if (answer == wxID_CANCEL) {
11016 return;
11017 }
11018 }
11019
11020 // If all waypoints exist since before, and a route with the same name, we
11021 // don't create a new route.
11022 if (mergepoints && answer == wxID_YES &&
11023 existingWaypointCounter == pasted->GetnPoints()) {
11024 for (Route *proute : *pRouteList) {
11025 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11026 createNewRoute = false;
11027 break;
11028 }
11029 }
11030 }
11031
11032 Route *newRoute = 0;
11033 RoutePoint *newPoint = 0;
11034
11035 if (createNewRoute) {
11036 newRoute = new Route();
11037 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11038 }
11039
11040 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11041 curPoint = pasted->GetPoint(i);
11042 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11043 curPoint->m_bPtIsSelected = false;
11044 newPoint = pWayPointMan->GetNearbyWaypoint(
11045 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11046 newPoint->SetName(curPoint->GetName());
11047 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11048
11049 if (createNewRoute) newRoute->AddPoint(newPoint);
11050 } else {
11051 curPoint->m_bPtIsSelected = false;
11052
11053 newPoint = new RoutePoint(curPoint);
11054 newPoint->m_bIsolatedMark = false;
11055 newPoint->SetIconName("circle");
11056 newPoint->m_bIsVisible = true;
11057 newPoint->m_bShowName = false;
11058 newPoint->SetShared(false);
11059
11060 newRoute->AddPoint(newPoint);
11061 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11062 newPoint);
11063 // pConfig->AddNewWayPoint(newPoint, -1);
11064 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11065 pWayPointMan->AddRoutePoint(newPoint);
11066 }
11067 if (i > 1 && createNewRoute)
11068 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11069 curPoint->m_lat, curPoint->m_lon,
11070 prevPoint, newPoint, newRoute);
11071 prevPoint = newPoint;
11072 }
11073
11074 if (createNewRoute) {
11075 pRouteList->push_back(newRoute);
11076 // pConfig->AddNewRoute(newRoute); // use auto next num
11077 NavObj_dB::GetInstance().InsertRoute(newRoute);
11078
11079 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11080 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11081 }
11082
11083 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11084 pRouteManagerDialog->UpdateRouteListCtrl();
11085 pRouteManagerDialog->UpdateWptListCtrl();
11086 }
11087 top_frame::Get()->InvalidateAllGL();
11088 top_frame::Get()->RefreshAllCanvas(false);
11089 }
11090 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11091 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11092}
11093
11094void pupHandler_PasteTrack() {
11095 Kml kml;
11096
11097 int pasteBuffer = kml.ParsePasteBuffer();
11098 Track *pasted = kml.GetParsedTrack();
11099 if (!pasted) return;
11100
11101 TrackPoint *curPoint;
11102
11103 Track *newTrack = new Track();
11104 TrackPoint *newPoint;
11105 TrackPoint *prevPoint = NULL;
11106
11107 newTrack->SetName(pasted->GetName());
11108
11109 for (int i = 0; i < pasted->GetnPoints(); i++) {
11110 curPoint = pasted->GetPoint(i);
11111
11112 newPoint = new TrackPoint(curPoint);
11113
11114 wxDateTime now = wxDateTime::Now();
11115 newPoint->SetCreateTime(curPoint->GetCreateTime());
11116
11117 newTrack->AddPoint(newPoint);
11118
11119 if (prevPoint)
11120 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11121 newPoint->m_lat, newPoint->m_lon,
11122 prevPoint, newPoint, newTrack);
11123
11124 prevPoint = newPoint;
11125 }
11126
11127 g_TrackList.push_back(newTrack);
11128 // pConfig->AddNewTrack(newTrack);
11129 NavObj_dB::GetInstance().InsertTrack(newTrack);
11130
11131 top_frame::Get()->InvalidateAllGL();
11132 top_frame::Get()->RefreshAllCanvas(false);
11133}
11134
11135bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11136 wxJSONValue v;
11137 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11138 v["CursorPosition_x"] = x;
11139 v["CursorPosition_y"] = y;
11140 // Send a limited set of selection types depending on what is
11141 // found under the mouse point.
11142 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11143 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11144 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11145
11146 wxJSONWriter w;
11147 wxString out;
11148 w.Write(v, out);
11149 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11150
11151 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11152
11153#if 0
11154#define SELTYPE_UNKNOWN 0x0001
11155#define SELTYPE_ROUTEPOINT 0x0002
11156#define SELTYPE_ROUTESEGMENT 0x0004
11157#define SELTYPE_TIDEPOINT 0x0008
11158#define SELTYPE_CURRENTPOINT 0x0010
11159#define SELTYPE_ROUTECREATE 0x0020
11160#define SELTYPE_AISTARGET 0x0040
11161#define SELTYPE_MARKPOINT 0x0080
11162#define SELTYPE_TRACKSEGMENT 0x0100
11163#define SELTYPE_DRAGHANDLE 0x0200
11164#endif
11165
11166 if (g_bhide_context_menus) return true;
11167 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11168 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11169 m_pIDXCandidate, m_nmea_log);
11170
11171 Connect(
11172 wxEVT_COMMAND_MENU_SELECTED,
11173 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11174
11175#ifdef __WXGTK__
11176 // Funny requirement here for gtk, to clear the menu trigger event
11177 // TODO
11178 // Causes a slight "flasH" of the menu,
11179 if (m_inLongPress) {
11180 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11181 m_inLongPress = false;
11182 }
11183#endif
11184
11185 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11186
11187 Disconnect(
11188 wxEVT_COMMAND_MENU_SELECTED,
11189 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11190
11191 delete m_canvasMenu;
11192 m_canvasMenu = NULL;
11193
11194#ifdef __WXQT__
11195 // gFrame->SurfaceToolbar();
11196 // g_MainToolbar->Raise();
11197#endif
11198
11199 return true;
11200}
11201
11202void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11203 // Pass menu events from the canvas to the menu handler
11204 // This is necessarily in ChartCanvas since that is the menu's parent.
11205 if (m_canvasMenu) {
11206 m_canvasMenu->PopupMenuHandler(event);
11207 }
11208 return;
11209}
11210
11211void ChartCanvas::StartRoute() {
11212 // Do not allow more than one canvas to create a route at one time.
11213 if (g_brouteCreating) return;
11214
11215 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11216
11217 g_brouteCreating = true;
11218 m_routeState = 1;
11219 m_bDrawingRoute = false;
11220 SetCursor(*pCursorPencil);
11221 // SetCanvasToolbarItemState(ID_ROUTE, true);
11222 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11223
11224 HideGlobalToolbar();
11225
11226#ifdef __ANDROID__
11227 androidSetRouteAnnunciator(true);
11228#endif
11229}
11230
11231wxString ChartCanvas::FinishRoute() {
11232 m_routeState = 0;
11233 m_prev_pMousePoint = NULL;
11234 m_bDrawingRoute = false;
11235 wxString rv = "";
11236 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11237
11238 // SetCanvasToolbarItemState(ID_ROUTE, false);
11239 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11240#ifdef __ANDROID__
11241 androidSetRouteAnnunciator(false);
11242#endif
11243
11244 SetCursor(*pCursorArrow);
11245
11246 if (m_pMouseRoute) {
11247 if (m_bAppendingRoute) {
11248 // pConfig->UpdateRoute(m_pMouseRoute);
11249 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11250 } else {
11251 if (m_pMouseRoute->GetnPoints() > 1) {
11252 // pConfig->AddNewRoute(m_pMouseRoute);
11253 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11254 } else {
11255 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11256 m_pMouseRoute = NULL;
11257 }
11258 }
11259 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11260
11261 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11262 (pRoutePropDialog->IsShown())) {
11263 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11264 }
11265
11266 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11267 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11268 pRouteManagerDialog->UpdateRouteListCtrl();
11269 }
11270 }
11271 m_bAppendingRoute = false;
11272 m_pMouseRoute = NULL;
11273
11274 m_pSelectedRoute = NULL;
11275
11276 undo->InvalidateUndo();
11277 top_frame::Get()->RefreshAllCanvas(true);
11278
11279 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11280
11281 ShowGlobalToolbar();
11282
11283 g_brouteCreating = false;
11284
11285 return rv;
11286}
11287
11288void ChartCanvas::HideGlobalToolbar() {
11289 if (m_canvasIndex == 0) {
11290 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11291 }
11292}
11293
11294void ChartCanvas::ShowGlobalToolbar() {
11295 if (m_canvasIndex == 0) {
11296 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11297 }
11298}
11299
11300void ChartCanvas::ShowAISTargetList() {
11301 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11302 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11303 }
11304
11305 g_pAISTargetList->UpdateAISTargetList();
11306}
11307
11308void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11309 if (!m_bShowOutlines) return;
11310
11311 if (!ChartData) return;
11312
11313 int nEntry = ChartData->GetChartTableEntries();
11314
11315 for (int i = 0; i < nEntry; i++) {
11316 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11317
11318 // Check to see if the candidate chart is in the currently active group
11319 bool b_group_draw = false;
11320 if (m_groupIndex > 0) {
11321 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11322 int index = pt->GetGroupArray()[ig];
11323 if (m_groupIndex == index) {
11324 b_group_draw = true;
11325 break;
11326 }
11327 }
11328 } else
11329 b_group_draw = true;
11330
11331 if (b_group_draw) RenderChartOutline(dc, i, vp);
11332 }
11333
11334 // On CM93 Composite Charts, draw the outlines of the next smaller
11335 // scale cell
11336 cm93compchart *pcm93 = NULL;
11337 if (VPoint.b_quilt) {
11338 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11339 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11340 pcm93 = (cm93compchart *)pch;
11341 break;
11342 }
11343 } else if (m_singleChart &&
11344 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11345 pcm93 = (cm93compchart *)m_singleChart;
11346
11347 if (pcm93) {
11348 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11349 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11350
11351 if (zoom_factor > 8.0) {
11352 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11353 dc.SetPen(mPen);
11354 } else {
11355 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11356 dc.SetPen(mPen);
11357 }
11358
11359 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11360 }
11361}
11362
11363void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11364#ifdef ocpnUSE_GL
11365 if (g_bopengl && m_glcc) {
11366 /* opengl version specially optimized */
11367 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11368 return;
11369 }
11370#endif
11371
11372 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11373 if (!ChartData->IsChartAvailable(dbIndex)) return;
11374 }
11375
11376 float plylat, plylon;
11377 float plylat1, plylon1;
11378
11379 int pixx, pixy, pixx1, pixy1;
11380
11381 LLBBox box;
11382 ChartData->GetDBBoundingBox(dbIndex, box);
11383
11384 // Don't draw an outline in the case where the chart covers the entire world
11385 // */
11386 if (box.GetLonRange() == 360) return;
11387
11388 double lon_bias = 0;
11389 // chart is outside of viewport lat/lon bounding box
11390 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11391
11392 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11393
11394 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11395 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11396
11397 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11398 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11399
11400 else
11401 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11402
11403 // Are there any aux ply entries?
11404 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11405 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11406 {
11407 wxPoint r, r1;
11408
11409 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11410 plylon += lon_bias;
11411
11412 GetCanvasPointPix(plylat, plylon, &r);
11413 pixx = r.x;
11414 pixy = r.y;
11415
11416 for (int i = 0; i < nPly - 1; i++) {
11417 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11418 plylon1 += lon_bias;
11419
11420 GetCanvasPointPix(plylat1, plylon1, &r1);
11421 pixx1 = r1.x;
11422 pixy1 = r1.y;
11423
11424 int pixxs1 = pixx1;
11425 int pixys1 = pixy1;
11426
11427 bool b_skip = false;
11428
11429 if (vp.chart_scale > 5e7) {
11430 // calculate projected distance between these two points in meters
11431 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11432 pow((double)(pixy1 - pixy), 2)) /
11433 vp.view_scale_ppm;
11434
11435 if (dist > 0.0) {
11436 // calculate GC distance between these two points in meters
11437 double distgc =
11438 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11439
11440 // If the distances are nonsense, it means that the scale is very
11441 // small and the segment wrapped the world So skip it....
11442 // TODO improve this to draw two segments
11443 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11444 b_skip = true;
11445 } else
11446 b_skip = true;
11447 }
11448
11449 ClipResult res = cohen_sutherland_line_clip_i(
11450 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11451 if (res != Invisible && !b_skip)
11452 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11453
11454 plylat = plylat1;
11455 plylon = plylon1;
11456 pixx = pixxs1;
11457 pixy = pixys1;
11458 }
11459
11460 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11461 plylon1 += lon_bias;
11462
11463 GetCanvasPointPix(plylat1, plylon1, &r1);
11464 pixx1 = r1.x;
11465 pixy1 = r1.y;
11466
11467 ClipResult res = cohen_sutherland_line_clip_i(
11468 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11469 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11470 }
11471
11472 else // Use Aux PlyPoints
11473 {
11474 wxPoint r, r1;
11475
11476 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11477 for (int j = 0; j < nAuxPlyEntries; j++) {
11478 int nAuxPly =
11479 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11480 GetCanvasPointPix(plylat, plylon, &r);
11481 pixx = r.x;
11482 pixy = r.y;
11483
11484 for (int i = 0; i < nAuxPly - 1; i++) {
11485 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11486
11487 GetCanvasPointPix(plylat1, plylon1, &r1);
11488 pixx1 = r1.x;
11489 pixy1 = r1.y;
11490
11491 int pixxs1 = pixx1;
11492 int pixys1 = pixy1;
11493
11494 bool b_skip = false;
11495
11496 if (vp.chart_scale > 5e7) {
11497 // calculate projected distance between these two points in meters
11498 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11499 ((pixy1 - pixy) * (pixy1 - pixy))) /
11500 vp.view_scale_ppm;
11501 if (dist > 0.0) {
11502 // calculate GC distance between these two points in meters
11503 double distgc =
11504 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11505
11506 // If the distances are nonsense, it means that the scale is very
11507 // small and the segment wrapped the world So skip it....
11508 // TODO improve this to draw two segments
11509 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11510 b_skip = true;
11511 } else
11512 b_skip = true;
11513 }
11514
11515 ClipResult res = cohen_sutherland_line_clip_i(
11516 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11517 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11518
11519 plylat = plylat1;
11520 plylon = plylon1;
11521 pixx = pixxs1;
11522 pixy = pixys1;
11523 }
11524
11525 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11526 GetCanvasPointPix(plylat1, plylon1, &r1);
11527 pixx1 = r1.x;
11528 pixy1 = r1.y;
11529
11530 ClipResult res = cohen_sutherland_line_clip_i(
11531 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11532 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11533 }
11534 }
11535}
11536
11537static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11538 const wxArrayString &legend) {
11539 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11540
11541 int pointsize = dFont->GetPointSize();
11542 pointsize /= OCPN_GetWinDIPScaleFactor();
11543
11544 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11545 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11546 false, dFont->GetFaceName());
11547
11548 dc.SetFont(*psRLI_font);
11549
11550 int h = 0;
11551 int w = 0;
11552 int hl, wl;
11553
11554 int xp, yp;
11555 int hilite_offset = 3;
11556
11557 for (wxString line : legend) {
11558#ifdef __WXMAC__
11559 wxScreenDC sdc;
11560 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11561#else
11562 dc.GetTextExtent(line, &wl, &hl);
11563 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11564 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11565#endif
11566 h += hl;
11567 w = wxMax(w, wl);
11568 }
11569 w += (hl / 2); // Add a little right pad
11570
11571 xp = ref_point.x - w;
11572 yp = ref_point.y;
11573 yp += hilite_offset;
11574
11575 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11576
11577 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11578 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11579
11580 for (wxString line : legend) {
11581 dc.DrawText(line, xp, yp);
11582 yp += hl;
11583 }
11584}
11585
11586void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11587 if (!g_bAllowShipToActive) return;
11588
11589 Route *rt = g_pRouteMan->GetpActiveRoute();
11590 if (!rt) return;
11591
11592 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11593 wxPoint2DDouble pa, pb;
11595 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11596
11597 // set pen
11598 int width =
11599 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11600 if (rt->m_width != wxPENSTYLE_INVALID)
11601 width = rt->m_width; // set route pen style if any
11602 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11603 g_shipToActiveStyle, 5)]; // get setting pen style
11604 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11605 wxColour color =
11606 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11607 : // set setting route pen color
11608 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11609 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11610
11611 dc.SetPen(*mypen);
11612 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11613
11614 if (!Use_Opengl)
11615 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11616 (int)pb.m_y, GetVP(), true);
11617
11618#ifdef ocpnUSE_GL
11619 else {
11620#ifdef USE_ANDROID_GLES2
11621 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11622#else
11623 if (style != wxPENSTYLE_SOLID) {
11624 if (glChartCanvas::dash_map.find(style) !=
11625 glChartCanvas::dash_map.end()) {
11626 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11627 dc.SetPen(*mypen);
11628 }
11629 }
11630 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11631#endif
11632
11633 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11634 (int)pb.m_x, (int)pb.m_y, GetVP());
11635 }
11636#endif
11637 }
11638}
11639
11640void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11641 Route *route = 0;
11642 if (m_routeState >= 2) route = m_pMouseRoute;
11643 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11644 route = m_pMeasureRoute;
11645
11646 if (!route) return;
11647
11648 // Validate route pointer
11649 if (!g_pRouteMan->IsRouteValid(route)) return;
11650
11651 double render_lat = m_cursor_lat;
11652 double render_lon = m_cursor_lon;
11653
11654 int np = route->GetnPoints();
11655 if (np) {
11656 if (g_btouch && (np > 1)) np--;
11657 RoutePoint rp = route->GetPoint(np);
11658 render_lat = rp.m_lat;
11659 render_lon = rp.m_lon;
11660 }
11661
11662 double rhumbBearing, rhumbDist;
11663 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11664 &rhumbBearing, &rhumbDist);
11665 double brg = rhumbBearing;
11666 double dist = rhumbDist;
11667
11668 // Skip GreatCircle rubberbanding on touch devices.
11669 if (!g_btouch) {
11670 double gcBearing, gcBearing2, gcDist;
11671 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11672 m_cursor_lat, &gcDist, &gcBearing,
11673 &gcBearing2);
11674 double gcDistm = gcDist / 1852.0;
11675
11676 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11677 rhumbBearing = 90.;
11678
11679 wxPoint destPoint, lastPoint;
11680
11681 route->m_NextLegGreatCircle = false;
11682 int milesDiff = rhumbDist - gcDistm;
11683 if (milesDiff > 1) {
11684 brg = gcBearing;
11685 dist = gcDistm;
11686 route->m_NextLegGreatCircle = true;
11687 }
11688
11689 // FIXME (MacOS, the first segment is rendered wrong)
11690 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11691 &lastPoint);
11692
11693 if (route->m_NextLegGreatCircle) {
11694 for (int i = 1; i <= milesDiff; i++) {
11695 double p = (double)i * (1.0 / (double)milesDiff);
11696 double pLat, pLon;
11697 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11698 &pLon, &pLat, &gcBearing2);
11699 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11700 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11701 false);
11702 lastPoint = destPoint;
11703 }
11704 } else {
11705 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11706 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11707 false);
11708 if (m_bMeasure_DistCircle) {
11709 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11710 powf((float)(r_rband.y - lastPoint.y), 2));
11711
11712 dc.SetPen(*g_pRouteMan->GetRoutePen());
11713 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11714 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11715 }
11716 }
11717 }
11718 }
11719
11720 wxString routeInfo;
11721 wxArrayString infoArray;
11722 double varBrg = 0;
11723 if (g_bShowTrue)
11724 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11725 0x00B0);
11726
11727 if (g_bShowMag) {
11728 double latAverage = (m_cursor_lat + render_lat) / 2;
11729 double lonAverage = (m_cursor_lon + render_lon) / 2;
11730 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11731
11732 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11733 (int)varBrg, 0x00B0);
11734 }
11735 routeInfo << " " << FormatDistanceAdaptive(dist);
11736 infoArray.Add(routeInfo);
11737 routeInfo.Clear();
11738
11739 // To make it easier to use a route as a bearing on a charted object add for
11740 // the first leg also the reverse bearing.
11741 if (np == 1) {
11742 routeInfo << "Reverse: ";
11743 if (g_bShowTrue)
11744 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11745 (int)(brg + 180.) % 360, 0x00B0);
11746 if (g_bShowMag)
11747 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11748 (int)(varBrg + 180.) % 360, 0x00B0);
11749 infoArray.Add(routeInfo);
11750 routeInfo.Clear();
11751 }
11752
11753 wxString s0;
11754 if (!route->m_bIsInLayer)
11755 s0.Append(_("Route") + ": ");
11756 else
11757 s0.Append(_("Layer Route: "));
11758
11759 double disp_length = route->m_route_length;
11760 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11761 s0 += FormatDistanceAdaptive(disp_length);
11762
11763 infoArray.Add(s0);
11764 routeInfo.Clear();
11765
11766 RouteLegInfo(dc, r_rband, infoArray);
11767
11768 m_brepaint_piano = true;
11769}
11770
11771void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11772 if (!m_bShowVisibleSectors) return;
11773
11774 if (g_bDeferredInitDone) {
11775 // need to re-evaluate sectors?
11776 double rhumbBearing, rhumbDist;
11777 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11778 &rhumbBearing, &rhumbDist);
11779
11780 if (rhumbDist > 0.05) // miles
11781 {
11782 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11783 m_sectorlegsVisible);
11784 m_sector_glat = gLat;
11785 m_sector_glon = gLon;
11786 }
11787 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11788 }
11789}
11790
11791void ChartCanvas::WarpPointerDeferred(int x, int y) {
11792 warp_x = x;
11793 warp_y = y;
11794 warp_flag = true;
11795}
11796
11797int s_msg;
11798
11799void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11800 if (!ps52plib) return;
11801
11802 if (VPoint.b_quilt) { // quilted
11803 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11804
11805 if (m_pQuilt->IsQuiltVector()) {
11806 if (ps52plib->GetStateHash() != m_s52StateHash) {
11807 UpdateS52State();
11808 m_s52StateHash = ps52plib->GetStateHash();
11809 }
11810 }
11811 } else {
11812 if (ps52plib->GetStateHash() != m_s52StateHash) {
11813 UpdateS52State();
11814 m_s52StateHash = ps52plib->GetStateHash();
11815 }
11816 }
11817
11818 // Plugin charts
11819 bool bSendPlibState = true;
11820 if (VPoint.b_quilt) { // quilted
11821 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11822 }
11823
11824 if (bSendPlibState) {
11825 wxJSONValue v;
11826 v["OpenCPN Version Major"] = VERSION_MAJOR;
11827 v["OpenCPN Version Minor"] = VERSION_MINOR;
11828 v["OpenCPN Version Patch"] = VERSION_PATCH;
11829 v["OpenCPN Version Date"] = VERSION_DATE;
11830 v["OpenCPN Version Full"] = VERSION_FULL;
11831
11832 // S52PLIB state
11833 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11834 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11835 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11836 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11837 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11838 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11839 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11840
11841 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11842
11843 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11844 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11845
11846 // Global S52 options
11847
11848 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11849 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11850 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11851 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11852 ps52plib->m_bShowS57ImportantTextOnly;
11853 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11854 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11855 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11856 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11857 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11858
11859 // Some global GUI parameters, for completeness
11860 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11861 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11862 v["OpenCPN Scale Factor Exp"] =
11863 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11864 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11865
11866 wxJSONWriter w;
11867 wxString out;
11868 w.Write(v, out);
11869
11870 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11871 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11872 g_lastS52PLIBPluginMessage = out;
11873 }
11874 }
11875}
11876int spaint;
11877int s_in_update;
11878void ChartCanvas::OnPaint(wxPaintEvent &event) {
11879 wxPaintDC dc(this);
11880
11881 // GetToolbar()->Show( m_bToolbarEnable );
11882
11883 // Paint updates may have been externally disabled (temporarily, to avoid
11884 // Yield() recursion performance loss) It is important that the wxPaintDC is
11885 // built, even if we elect to not process this paint message. Otherwise, the
11886 // paint message may not be removed from the message queue, esp on Windows.
11887 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11888
11889 if (!m_b_paint_enable) {
11890 return;
11891 }
11892
11893 // If necessary, reconfigure the S52 PLIB
11895
11896#ifdef ocpnUSE_GL
11897 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11898
11899 if (m_glcc && g_bopengl) {
11900 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11901 s_in_update++;
11902 m_glcc->Update();
11903 s_in_update--;
11904 }
11905
11906 return;
11907 }
11908#endif
11909
11910 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11911
11912 wxRegion ru = GetUpdateRegion();
11913
11914 int rx, ry, rwidth, rheight;
11915 ru.GetBox(rx, ry, rwidth, rheight);
11916
11917#ifdef ocpnUSE_DIBSECTION
11918 ocpnMemDC temp_dc;
11919#else
11920 wxMemoryDC temp_dc;
11921#endif
11922
11923 long height = GetVP().pix_height;
11924
11925#ifdef __WXMAC__
11926 // On OS X we have to explicitly extend the region for the piano area
11927 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11928 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11929 height += m_Piano->GetHeight();
11930#endif // __WXMAC__
11931 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11932
11933 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11934 if (pthumbwin) {
11935 int thumbx, thumby, thumbsx, thumbsy;
11936 pthumbwin->GetPosition(&thumbx, &thumby);
11937 pthumbwin->GetSize(&thumbsx, &thumbsy);
11938 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11939
11940 if (pthumbwin->IsShown()) {
11941 rgn_chart.Subtract(rgn_thumbwin);
11942 ru.Subtract(rgn_thumbwin);
11943 }
11944 }
11945
11946 // subtract the chart bar if it isn't transparent, and determine if we need to
11947 // paint it
11948 wxRegion rgn_blit = ru;
11949 if (g_bShowChartBar) {
11950 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11951 GetClientSize().x, m_Piano->GetHeight());
11952
11953 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11954 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11955 if (style->chartStatusWindowTransparent)
11956 m_brepaint_piano = true;
11957 else
11958 ru.Subtract(chart_bar_rect);
11959 }
11960 }
11961
11962 if (m_Compass && m_Compass->IsShown()) {
11963 wxRect compassRect = m_Compass->GetRect();
11964 if (ru.Contains(compassRect) != wxOutRegion) {
11965 ru.Subtract(compassRect);
11966 }
11967 }
11968
11969 if (m_notification_button) {
11970 wxRect noteRect = m_notification_button->GetRect();
11971 if (ru.Contains(noteRect) != wxOutRegion) {
11972 ru.Subtract(noteRect);
11973 }
11974 }
11975
11976 // Is this viewpoint the same as the previously painted one?
11977 bool b_newview = true;
11978
11979 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11980 (m_cache_vp.rotation == VPoint.rotation) &&
11981 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11982 m_cache_vp.IsValid()) {
11983 b_newview = false;
11984 }
11985
11986 // If the ViewPort is skewed or rotated, we may be able to use the cached
11987 // rotated bitmap.
11988 bool b_rcache_ok = false;
11989 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11990 b_rcache_ok = !b_newview;
11991
11992 // Make a special VP
11993 if (VPoint.b_MercatorProjectionOverride)
11994 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11995 ViewPort svp = VPoint;
11996
11997 svp.pix_width = svp.rv_rect.width;
11998 svp.pix_height = svp.rv_rect.height;
11999
12000 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
12001 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
12002 // VPoint.rv_rect.height);
12003
12004 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
12005
12006 // If we are going to use the cached rotated image, there is no need to fetch
12007 // any chart data and this will do it...
12008 if (b_rcache_ok) chart_get_region.Clear();
12009
12010 // Blit pan acceleration
12011 if (VPoint.b_quilt) // quilted
12012 {
12013 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12014
12015 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12016
12017 bool busy = false;
12018 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12019 m_cache_vp.rotation != VPoint.rotation)) {
12020 AbstractPlatform::ShowBusySpinner();
12021 busy = true;
12022 }
12023
12024 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12025 (m_working_bm.GetHeight() != svp.pix_height))
12026 m_working_bm.Create(svp.pix_width, svp.pix_height,
12027 -1); // make sure the target is big enoug
12028
12029 if (fabs(VPoint.rotation) < 0.01) {
12030 bool b_save = true;
12031
12032 if (g_SencThreadManager) {
12033 if (g_SencThreadManager->GetJobCount()) {
12034 b_save = false;
12035 m_cache_vp.Invalidate();
12036 }
12037 }
12038
12039 // If the saved wxBitmap from last OnPaint is useable
12040 // calculate the blit parameters
12041
12042 // We can only do screen blit painting if subsequent ViewPorts differ by
12043 // whole pixels So, in small scale bFollow mode, force the full screen
12044 // render. This seems a hack....There may be better logic here.....
12045
12046 // if(m_bFollow)
12047 // b_save = false;
12048
12049 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12050 if (b_newview) {
12051 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12052 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12053
12054 int dy = c_new.y - c_old.y;
12055 int dx = c_new.x - c_old.x;
12056
12057 // printf("In OnPaint Trying Blit dx: %d
12058 // dy:%d\n\n", dx, dy);
12059
12060 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12061 if (dx || dy) {
12062 // Blit the reuseable portion of the cached wxBitmap to a working
12063 // bitmap
12064 temp_dc.SelectObject(m_working_bm);
12065
12066 wxMemoryDC cache_dc;
12067 cache_dc.SelectObject(m_cached_chart_bm);
12068
12069 if (dy > 0) {
12070 if (dx > 0) {
12071 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12072 VPoint.pix_height - dy, &cache_dc, dx, dy);
12073 } else {
12074 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12075 VPoint.pix_height - dy, &cache_dc, 0, dy);
12076 }
12077
12078 } else {
12079 if (dx > 0) {
12080 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12081 VPoint.pix_height + dy, &cache_dc, dx, 0);
12082 } else {
12083 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12084 VPoint.pix_height + dy, &cache_dc, 0, 0);
12085 }
12086 }
12087
12088 OCPNRegion update_region;
12089 if (dy) {
12090 if (dy > 0)
12091 update_region.Union(
12092 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12093 else
12094 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12095 }
12096
12097 if (dx) {
12098 if (dx > 0)
12099 update_region.Union(
12100 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12101 else
12102 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12103 }
12104
12105 // Render the new region
12106 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12107 update_region);
12108 cache_dc.SelectObject(wxNullBitmap);
12109 } else {
12110 // No sensible (dx, dy) change in the view, so use the cached
12111 // member bitmap
12112 temp_dc.SelectObject(m_cached_chart_bm);
12113 b_save = false;
12114 }
12115 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12116
12117 } else // not blitable
12118 {
12119 temp_dc.SelectObject(m_working_bm);
12120 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12121 chart_get_region);
12122 }
12123 } else {
12124 // No change in the view, so use the cached member bitmap2
12125 temp_dc.SelectObject(m_cached_chart_bm);
12126 b_save = false;
12127 }
12128 } else // cached bitmap is not yet valid
12129 {
12130 temp_dc.SelectObject(m_working_bm);
12131 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12132 chart_get_region);
12133 }
12134
12135 // Save the fully rendered quilt image as a wxBitmap member of this class
12136 if (b_save) {
12137 // if((m_cached_chart_bm.GetWidth() !=
12138 // svp.pix_width) ||
12139 // (m_cached_chart_bm.GetHeight() !=
12140 // svp.pix_height))
12141 // m_cached_chart_bm.Create(svp.pix_width,
12142 // svp.pix_height, -1); // target wxBitmap
12143 // is big enough
12144 wxMemoryDC scratch_dc_0;
12145 scratch_dc_0.SelectObject(m_cached_chart_bm);
12146 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12147
12148 scratch_dc_0.SelectObject(wxNullBitmap);
12149
12150 m_bm_cache_vp =
12151 VPoint; // save the ViewPort associated with the cached wxBitmap
12152 }
12153 }
12154
12155 else // quilted, rotated
12156 {
12157 temp_dc.SelectObject(m_working_bm);
12158 OCPNRegion chart_get_all_region(
12159 wxRect(0, 0, svp.pix_width, svp.pix_height));
12160 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12161 chart_get_all_region);
12162 }
12163
12164 AbstractPlatform::HideBusySpinner();
12165
12166 }
12167
12168 else // not quilted
12169 {
12170 if (!m_singleChart) {
12171 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12172 dc.Clear();
12173 return;
12174 }
12175
12176 if (!chart_get_region.IsEmpty()) {
12177 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12178 }
12179 }
12180
12181 if (temp_dc.IsOk()) {
12182 // Arrange to render the World Chart vector data behind the rendered
12183 // current chart so that uncovered canvas areas show at least the world
12184 // chart.
12185 OCPNRegion chartValidRegion;
12186 if (!VPoint.b_quilt) {
12187 // Make a region covering the current chart on the canvas
12188
12189 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12190 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12191 else {
12192 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12193 // require that the viewport passed here have pix_width and pix_height
12194 // set to the actual display, not the virtual (rv_rect) sizes
12195 // (the vector calculations require the virtual sizes in svp)
12196
12197 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12198 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12199 }
12200 } else
12201 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12202
12203 temp_dc.DestroyClippingRegion();
12204
12205 // Copy current chart region
12206 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12207
12208 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12209
12210 if (!backgroundRegion.IsEmpty()) {
12211 // Draw the Background Chart only in the areas NOT covered by the
12212 // current chart view
12213
12214 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12215 clipping regions with more than 1 rectangle so... */
12216 wxColour water = pWorldBackgroundChart->water;
12217 if (water.IsOk()) {
12218 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12219 temp_dc.SetBrush(wxBrush(water));
12220 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12221 while (upd.HaveRects()) {
12222 wxRect rect = upd.GetRect();
12223 temp_dc.DrawRectangle(rect);
12224 upd.NextRect();
12225 }
12226 }
12227 // Associate with temp_dc
12228 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12229 temp_dc.SetDeviceClippingRegion(*clip_region);
12230 delete clip_region;
12231
12232 ocpnDC bgdc(temp_dc);
12233 double r = VPoint.rotation;
12234 SetVPRotation(VPoint.skew);
12235
12236 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12237 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12238
12239 SetVPRotation(r);
12240 }
12241 } // temp_dc.IsOk();
12242
12243 wxMemoryDC *pChartDC = &temp_dc;
12244 wxMemoryDC rotd_dc;
12245
12246 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12247 // Can we use the current rotated image cache?
12248 if (!b_rcache_ok) {
12249#ifdef __WXMSW__
12250 wxMemoryDC tbase_dc;
12251 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12252 tbase_dc.SelectObject(bm_base);
12253 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12254 tbase_dc.SelectObject(wxNullBitmap);
12255#else
12256 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12257#endif
12258
12259 wxImage base_image;
12260 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12261
12262 // Use a local static image rotator to improve wxWidgets code profile
12263 // Especially, on GTK the wxRound and wxRealPoint functions are very
12264 // expensive.....
12265
12266 double angle = GetVP().skew - GetVP().rotation;
12267 wxImage ri;
12268 bool b_rot_ok = false;
12269 if (base_image.IsOk()) {
12270 ViewPort rot_vp = GetVP();
12271
12272 m_b_rot_hidef = false;
12273
12274 ri = Image_Rotate(
12275 base_image, angle,
12276 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12277 m_b_rot_hidef, &m_roffset);
12278
12279 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12280 (rot_vp.rotation == VPoint.rotation) &&
12281 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12282 rot_vp.IsValid() && (ri.IsOk())) {
12283 b_rot_ok = true;
12284 }
12285 }
12286
12287 if (b_rot_ok) {
12288 delete m_prot_bm;
12289 m_prot_bm = new wxBitmap(ri);
12290 }
12291
12292 m_roffset.x += VPoint.rv_rect.x;
12293 m_roffset.y += VPoint.rv_rect.y;
12294 }
12295
12296 if (m_prot_bm && m_prot_bm->IsOk()) {
12297 rotd_dc.SelectObject(*m_prot_bm);
12298 pChartDC = &rotd_dc;
12299 } else {
12300 pChartDC = &temp_dc;
12301 m_roffset = wxPoint(0, 0);
12302 }
12303 } else { // unrotated
12304 pChartDC = &temp_dc;
12305 m_roffset = wxPoint(0, 0);
12306 }
12307
12308 wxPoint offset = m_roffset;
12309
12310 // Save the PixelCache viewpoint for next time
12311 m_cache_vp = VPoint;
12312
12313 // Set up a scratch DC for overlay objects
12314 wxMemoryDC mscratch_dc;
12315 mscratch_dc.SelectObject(*pscratch_bm);
12316
12317 mscratch_dc.ResetBoundingBox();
12318 mscratch_dc.DestroyClippingRegion();
12319 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12320
12321 // Blit the externally invalidated areas of the chart onto the scratch dc
12322 wxRegionIterator upd(rgn_blit); // get the update rect list
12323 while (upd) {
12324 wxRect rect = upd.GetRect();
12325
12326 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12327 rect.x - offset.x, rect.y - offset.y);
12328 upd++;
12329 }
12330
12331 // If multi-canvas, indicate which canvas has keyboard focus
12332 // by drawing a simple blue bar at the top.
12333 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12334 if (this == wxWindow::FindFocus()) {
12335 g_focusCanvas = this;
12336
12337 wxColour colour = GetGlobalColor("BLUE4");
12338 mscratch_dc.SetPen(wxPen(colour));
12339 mscratch_dc.SetBrush(wxBrush(colour));
12340
12341 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12342 mscratch_dc.DrawRectangle(activeRect);
12343 }
12344 }
12345
12346 // Any MBtiles?
12347 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12348 unsigned int im = stackIndexArray.size();
12349 if (VPoint.b_quilt && im > 0) {
12350 std::vector<int> tiles_to_show;
12351 for (unsigned int is = 0; is < im; is++) {
12352 const ChartTableEntry &cte =
12353 ChartData->GetChartTableEntry(stackIndexArray[is]);
12354 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12355 continue;
12356 }
12357 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12358 tiles_to_show.push_back(stackIndexArray[is]);
12359 }
12360 }
12361
12362 if (tiles_to_show.size())
12363 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12364 }
12365
12366 // May get an unexpected OnPaint call while switching display modes
12367 // Guard for that.
12368 if (!g_bopengl) {
12369 ocpnDC scratch_dc(mscratch_dc);
12370 RenderAlertMessage(mscratch_dc, GetVP());
12371 }
12372
12373#if 0
12374 // quiting?
12375 if (g_bquiting) {
12376#ifdef ocpnUSE_DIBSECTION
12377 ocpnMemDC q_dc;
12378#else
12379 wxMemoryDC q_dc;
12380#endif
12381 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12382 q_dc.SelectObject(qbm);
12383
12384 // Get a copy of the screen
12385 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12386
12387 // Draw a rectangle over the screen with a stipple brush
12388 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12389 q_dc.SetBrush(qbr);
12390 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12391
12392 // Blit back into source
12393 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12394 wxCOPY);
12395
12396 q_dc.SelectObject(wxNullBitmap);
12397 }
12398#endif
12399
12400#if 0
12401 // It is possible that this two-step method may be reuired for some platforms.
12402 // So, retain in the code base to aid recovery if necessary
12403
12404 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12405 if( VPoint.b_quilt ) {
12406 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12407 ChartBase *chart = m_pQuilt->GetRefChart();
12408 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12409
12410 // Clear the text Global declutter list
12411 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12412 if(ChPI)
12413 ChPI->ClearPLIBTextList();
12414 else{
12415 if(ps52plib)
12416 ps52plib->ClearTextList();
12417 }
12418
12419 wxMemoryDC t_dc;
12420 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12421
12422 wxColor maskBackground = wxColour(1,0,0);
12423 t_dc.SelectObject( qbm );
12424 t_dc.SetBackground(wxBrush(maskBackground));
12425 t_dc.Clear();
12426
12427 // Copy the scratch DC into the new bitmap
12428 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12429
12430 // Render the text to the new bitmap
12431 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12432 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12433
12434 // Copy the new bitmap back to the scratch dc
12435 wxRegionIterator upd_final( ru );
12436 while( upd_final ) {
12437 wxRect rect = upd_final.GetRect();
12438 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12439 upd_final++;
12440 }
12441
12442 t_dc.SelectObject( wxNullBitmap );
12443 }
12444 }
12445 }
12446#endif
12447 // Direct rendering model...
12448 if (VPoint.b_quilt) {
12449 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12450 ChartBase *chart = m_pQuilt->GetRefChart();
12451 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12452 // Clear the text Global declutter list
12453 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12454 if (ChPI)
12455 ChPI->ClearPLIBTextList();
12456 else {
12457 if (ps52plib) ps52plib->ClearTextList();
12458 }
12459
12460 // Render the text directly to the scratch bitmap
12461 OCPNRegion chart_all_text_region(
12462 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12463
12464 if (g_bShowChartBar && m_Piano) {
12465 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12466 GetVP().pix_width, m_Piano->GetHeight());
12467
12468 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12469 if (!style->chartStatusWindowTransparent)
12470 chart_all_text_region.Subtract(chart_bar_rect);
12471 }
12472
12473 if (m_Compass && m_Compass->IsShown()) {
12474 wxRect compassRect = m_Compass->GetRect();
12475 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12476 chart_all_text_region.Subtract(compassRect);
12477 }
12478 }
12479
12480 mscratch_dc.DestroyClippingRegion();
12481
12482 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12483 chart_all_text_region);
12484 }
12485 }
12486 }
12487
12488 // Now that charts are fully rendered, apply the overlay objects as decals.
12489 ocpnDC scratch_dc(mscratch_dc);
12490 DrawOverlayObjects(scratch_dc, ru);
12491
12492 // And finally, blit the scratch dc onto the physical dc
12493 wxRegionIterator upd_final(rgn_blit);
12494 while (upd_final) {
12495 wxRect rect = upd_final.GetRect();
12496 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12497 rect.y);
12498 upd_final++;
12499 }
12500
12501 // Deselect the chart bitmap from the temp_dc, so that it will not be
12502 // destroyed in the temp_dc dtor
12503 temp_dc.SelectObject(wxNullBitmap);
12504 // And for the scratch bitmap
12505 mscratch_dc.SelectObject(wxNullBitmap);
12506
12507 dc.DestroyClippingRegion();
12508
12509 PaintCleanup();
12510}
12511
12512void ChartCanvas::PaintCleanup() {
12513 // Handle the current graphic window, if present
12514 if (m_inPinch) return;
12515
12516 if (pCwin) {
12517 pCwin->Show();
12518 if (m_bTCupdate) {
12519 pCwin->Refresh();
12520 pCwin->Update();
12521 }
12522 }
12523
12524 // And set flags for next time
12525 m_bTCupdate = false;
12526
12527 // Handle deferred WarpPointer
12528 if (warp_flag) {
12529 WarpPointer(warp_x, warp_y);
12530 warp_flag = false;
12531 }
12532
12533 // Start movement timers, this runs nearly immediately.
12534 // the reason we cannot simply call it directly is the
12535 // refresh events it emits may be blocked from this paint event
12536 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12537 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12538}
12539
12540#if 0
12541wxColour GetErrorGraphicColor(double val)
12542{
12543 /*
12544 double valm = wxMin(val_max, val);
12545
12546 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12547 unsigned char red = (unsigned char)(255 * (valm/val_max));
12548
12549 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12550
12551 hv.saturation = 1.0;
12552 hv.value = 1.0;
12553
12554 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12555 return wxColour(rv.red, rv.green, rv.blue);
12556 */
12557
12558 // HTML colors taken from NOAA WW3 Web representation
12559 wxColour c;
12560 if((val > 0) && (val < 1)) c.Set("#002ad9");
12561 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12562 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12563 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12564 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12565 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12566 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12567 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12568 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12569 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12570 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12571 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12572 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12573 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12574 else if((val >= 30) && (val < 36)) c.Set("#870000");
12575 else if((val >= 36) && (val < 42)) c.Set("#690000");
12576 else if((val >= 42) && (val < 48)) c.Set("#550000");
12577 else if( val >= 48) c.Set("#410000");
12578
12579 return c;
12580}
12581
12582void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12583{
12584 wxImage gr_image(vp->pix_width, vp->pix_height);
12585 gr_image.InitAlpha();
12586
12587 double maxval = -10000;
12588 double minval = 10000;
12589
12590 double rlat, rlon;
12591 double glat, glon;
12592
12593 GetCanvasPixPoint(0, 0, rlat, rlon);
12594
12595 for(int i=1; i < vp->pix_height-1; i++)
12596 {
12597 for(int j=0; j < vp->pix_width; j++)
12598 {
12599 // Reference mercator value
12600// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12601
12602 // Georef value
12603 GetCanvasPixPoint(j, i, glat, glon);
12604
12605 maxval = wxMax(maxval, (glat - rlat));
12606 minval = wxMin(minval, (glat - rlat));
12607
12608 }
12609 rlat = glat;
12610 }
12611
12612 GetCanvasPixPoint(0, 0, rlat, rlon);
12613 for(int i=1; i < vp->pix_height-1; i++)
12614 {
12615 for(int j=0; j < vp->pix_width; j++)
12616 {
12617 // Reference mercator value
12618// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12619
12620 // Georef value
12621 GetCanvasPixPoint(j, i, glat, glon);
12622
12623 double f = ((glat - rlat)-minval)/(maxval - minval);
12624
12625 double dy = (f * 40);
12626
12627 wxColour c = GetErrorGraphicColor(dy);
12628 unsigned char r = c.Red();
12629 unsigned char g = c.Green();
12630 unsigned char b = c.Blue();
12631
12632 gr_image.SetRGB(j, i, r,g,b);
12633 if((glat - rlat )!= 0)
12634 gr_image.SetAlpha(j, i, 128);
12635 else
12636 gr_image.SetAlpha(j, i, 255);
12637
12638 }
12639 rlat = glat;
12640 }
12641
12642 // Create a Bitmap
12643 wxBitmap *pbm = new wxBitmap(gr_image);
12644 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12645 pbm->SetMask(gr_mask);
12646
12647 pmdc->DrawBitmap(*pbm, 0,0);
12648
12649 delete pbm;
12650
12651}
12652
12653#endif
12654
12655void ChartCanvas::CancelMouseRoute() {
12656 m_routeState = 0;
12657 m_pMouseRoute = NULL;
12658 m_bDrawingRoute = false;
12659}
12660
12661int ChartCanvas::GetNextContextMenuId() {
12662 return CanvasMenuHandler::GetNextContextMenuId();
12663}
12664
12665bool ChartCanvas::SetCursor(const wxCursor &c) {
12666#ifdef ocpnUSE_GL
12667 if (g_bopengl && m_glcc)
12668 return m_glcc->SetCursor(c);
12669 else
12670#endif
12671 return wxWindow::SetCursor(c);
12672}
12673
12674void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12675 if (g_bquiting) return;
12676 // Keep the mouse position members up to date
12677 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12678
12679 // Retrigger the route leg popup timer
12680 // This handles the case when the chart is moving in auto-follow mode,
12681 // but no user mouse input is made. The timer handler may Hide() the
12682 // popup if the chart moved enough n.b. We use slightly longer oneshot
12683 // value to allow this method's Refresh() to complete before potentially
12684 // getting another Refresh() in the popup timer handler.
12685 if (!m_RolloverPopupTimer.IsRunning() &&
12686 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12687 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12688 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12689 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12690
12691#ifdef ocpnUSE_GL
12692 if (m_glcc && g_bopengl) {
12693 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12694 // overlay objects.
12695 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12696
12697 m_glcc->Refresh(eraseBackground,
12698 NULL); // We always are going to render the entire screen
12699 // anyway, so make
12700 // sure that the window managers understand the invalid area
12701 // is actually the entire client area.
12702
12703 // We need to selectively Refresh some child windows, if they are visible.
12704 // Note that some children are refreshed elsewhere on timer ticks, so don't
12705 // need attention here.
12706
12707 // Thumbnail chart
12708 if (pthumbwin && pthumbwin->IsShown()) {
12709 pthumbwin->Raise();
12710 pthumbwin->Refresh(false);
12711 }
12712
12713 // ChartInfo window
12714 if (m_pCIWin && m_pCIWin->IsShown()) {
12715 m_pCIWin->Raise();
12716 m_pCIWin->Refresh(false);
12717 }
12718
12719 // if(g_MainToolbar)
12720 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12721
12722 } else
12723#endif
12724 wxWindow::Refresh(eraseBackground, rect);
12725}
12726
12727void ChartCanvas::Update() {
12728 if (m_glcc && g_bopengl) {
12729#ifdef ocpnUSE_GL
12730 m_glcc->Update();
12731#endif
12732 } else
12733 wxWindow::Update();
12734}
12735
12736void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12737 if (!pemboss) return;
12738 int x = pemboss->x, y = pemboss->y;
12739 const double factor = 200;
12740
12741 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12742 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12743 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12744
12745 // Grab a snipped image out of the chart
12746 wxMemoryDC snip_dc;
12747 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12748 snip_dc.SelectObject(snip_bmp);
12749
12750 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12751 snip_dc.SelectObject(wxNullBitmap);
12752
12753 wxImage snip_img = snip_bmp.ConvertToImage();
12754
12755 // Apply Emboss map to the snip image
12756 unsigned char *pdata = snip_img.GetData();
12757 if (pdata) {
12758 for (int y = 0; y < pemboss->height; y++) {
12759 int map_index = (y * pemboss->width);
12760 for (int x = 0; x < pemboss->width; x++) {
12761 double val = (pemboss->pmap[map_index] * factor) / 256.;
12762
12763 int nred = (int)((*pdata) + val);
12764 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12765 *pdata++ = (unsigned char)nred;
12766
12767 int ngreen = (int)((*pdata) + val);
12768 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12769 *pdata++ = (unsigned char)ngreen;
12770
12771 int nblue = (int)((*pdata) + val);
12772 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12773 *pdata++ = (unsigned char)nblue;
12774
12775 map_index++;
12776 }
12777 }
12778 }
12779
12780 // Convert embossed snip to a bitmap
12781 wxBitmap emb_bmp(snip_img);
12782
12783 // Map to another memoryDC
12784 wxMemoryDC result_dc;
12785 result_dc.SelectObject(emb_bmp);
12786
12787 // Blit to target
12788 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12789
12790 result_dc.SelectObject(wxNullBitmap);
12791}
12792
12793emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12794 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12795
12796 if (GetQuiltMode()) {
12797 // disable Overzoom indicator for MBTiles
12798 int refIndex = GetQuiltRefChartdbIndex();
12799 if (refIndex >= 0) {
12800 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12801 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12802 if (current_type == CHART_TYPE_MBTILES) {
12803 ChartBase *pChart = m_pQuilt->GetRefChart();
12804 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12805 if (ptc) {
12806 zoom_factor = ptc->GetZoomFactor();
12807 }
12808 }
12809 }
12810
12811 if (zoom_factor <= 3.9) return NULL;
12812 } else {
12813 if (m_singleChart) {
12814 if (zoom_factor <= 3.9) return NULL;
12815 } else
12816 return NULL;
12817 }
12818
12819 if (m_pEM_OverZoom) {
12820 m_pEM_OverZoom->x = 4;
12821 m_pEM_OverZoom->y = 0;
12822 if (g_MainToolbar && IsPrimaryCanvas()) {
12823 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12824 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12825 }
12826 }
12827 return m_pEM_OverZoom;
12828}
12829
12830void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12831 GridDraw(dc);
12832
12833 // bool pluginOverlayRender = true;
12834 //
12835 // if(g_canvasConfig > 0){ // Multi canvas
12836 // if(IsPrimaryCanvas())
12837 // pluginOverlayRender = false;
12838 // }
12839
12840 g_overlayCanvas = this;
12841
12842 if (g_pi_manager) {
12843 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12844 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12846 }
12847
12848 AISDrawAreaNotices(dc, GetVP(), this);
12849
12850 wxDC *pdc = dc.GetDC();
12851 if (pdc) {
12852 pdc->DestroyClippingRegion();
12853 wxDCClipper(*pdc, ru);
12854 }
12855
12856 if (m_bShowNavobjects) {
12857 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12858 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12859 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12860 DrawAnchorWatchPoints(dc);
12861 } else {
12862 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12863 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12864 }
12865
12866 AISDraw(dc, GetVP(), this);
12867 ShipDraw(dc);
12868 AlertDraw(dc);
12869
12870 RenderVisibleSectorLights(dc);
12871
12872 RenderAllChartOutlines(dc, GetVP());
12873 RenderRouteLegs(dc);
12874 RenderShipToActive(dc, false);
12875 ScaleBarDraw(dc);
12876 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12877 if (g_pi_manager) {
12878 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12880 }
12881
12882 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12883 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12884
12885 if (g_pi_manager) {
12886 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12888 }
12889
12890 if (m_bShowTide) {
12891 RebuildTideSelectList(GetVP().GetBBox());
12892 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12893 }
12894
12895 if (m_bShowCurrent) {
12896 RebuildCurrentSelectList(GetVP().GetBBox());
12897 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12898 }
12899
12900 if (!g_PrintingInProgress) {
12901 if (IsPrimaryCanvas()) {
12902 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12903 }
12904
12905 if (IsPrimaryCanvas()) {
12906 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12907 }
12908
12909 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12910
12911 if (m_pTrackRolloverWin) {
12912 m_pTrackRolloverWin->Draw(dc);
12913 m_brepaint_piano = true;
12914 }
12915
12916 if (m_pRouteRolloverWin) {
12917 m_pRouteRolloverWin->Draw(dc);
12918 m_brepaint_piano = true;
12919 }
12920
12921 if (m_pAISRolloverWin) {
12922 m_pAISRolloverWin->Draw(dc);
12923 m_brepaint_piano = true;
12924 }
12925 if (m_brepaint_piano && g_bShowChartBar) {
12926 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12927 }
12928
12929 if (m_Compass) m_Compass->Paint(dc);
12930
12931 if (!g_CanvasHideNotificationIcon) {
12932 if (IsPrimaryCanvas()) {
12933 auto &noteman = NotificationManager::GetInstance();
12934 if (m_notification_button) {
12935 if (noteman.GetNotificationCount()) {
12936 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12937 if (m_notification_button->UpdateStatus()) Refresh();
12938 m_notification_button->Show(true);
12939 m_notification_button->Paint(dc);
12940 } else {
12941 m_notification_button->Show(false);
12942 }
12943 }
12944 }
12945 }
12946 }
12947 if (g_pi_manager) {
12948 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12950 }
12951}
12952
12953emboss_data *ChartCanvas::EmbossDepthScale() {
12954 if (!m_bShowDepthUnits) return NULL;
12955
12956 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12957
12958 if (GetQuiltMode()) {
12959 wxString s = m_pQuilt->GetQuiltDepthUnit();
12960 s.MakeUpper();
12961 if (s == "FEET")
12962 depth_unit_type = DEPTH_UNIT_FEET;
12963 else if (s.StartsWith("FATHOMS"))
12964 depth_unit_type = DEPTH_UNIT_FATHOMS;
12965 else if (s.StartsWith("METERS"))
12966 depth_unit_type = DEPTH_UNIT_METERS;
12967 else if (s.StartsWith("METRES"))
12968 depth_unit_type = DEPTH_UNIT_METERS;
12969 else if (s.StartsWith("METRIC"))
12970 depth_unit_type = DEPTH_UNIT_METERS;
12971 else if (s.StartsWith("METER"))
12972 depth_unit_type = DEPTH_UNIT_METERS;
12973
12974 } else {
12975 if (m_singleChart) {
12976 depth_unit_type = m_singleChart->GetDepthUnitType();
12977 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12978 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12979 }
12980 }
12981
12982 emboss_data *ped = NULL;
12983 switch (depth_unit_type) {
12984 case DEPTH_UNIT_FEET:
12985 ped = m_pEM_Feet;
12986 break;
12987 case DEPTH_UNIT_METERS:
12988 ped = m_pEM_Meters;
12989 break;
12990 case DEPTH_UNIT_FATHOMS:
12991 ped = m_pEM_Fathoms;
12992 break;
12993 default:
12994 return NULL;
12995 }
12996
12997 ped->x = (GetVP().pix_width - ped->width);
12998
12999 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
13000 wxRect r = m_Compass->GetRect();
13001 ped->y = r.y + r.height;
13002 } else {
13003 ped->y = 40;
13004 }
13005 return ped;
13006}
13007
13008void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
13009 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13010 wxFont font;
13011 if (style->embossFont == wxEmptyString) {
13012 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13013 font = *dFont;
13014 font.SetPointSize(60);
13015 font.SetWeight(wxFONTWEIGHT_BOLD);
13016 } else
13017 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13018 wxFONTWEIGHT_BOLD, false, style->embossFont);
13019
13020 int emboss_width = 500;
13021 int emboss_height = 200;
13022
13023 // Free any existing emboss maps
13024 delete m_pEM_Feet;
13025 delete m_pEM_Meters;
13026 delete m_pEM_Fathoms;
13027
13028 // Create the 3 DepthUnit emboss map structures
13029 m_pEM_Feet =
13030 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13031 m_pEM_Meters =
13032 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13033 m_pEM_Fathoms =
13034 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13035}
13036
13037#define OVERZOOM_TEXT _("OverZoom")
13038
13039void ChartCanvas::SetOverzoomFont() {
13040 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13041 int w, h;
13042
13043 wxFont font;
13044 if (style->embossFont == wxEmptyString) {
13045 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13046 font = *dFont;
13047 font.SetPointSize(40);
13048 font.SetWeight(wxFONTWEIGHT_BOLD);
13049 } else
13050 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13051 wxFONTWEIGHT_BOLD, false, style->embossFont);
13052
13053 wxClientDC dc(this);
13054 dc.SetFont(font);
13055 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13056
13057 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13058 font.SetPointSize(font.GetPointSize() - 1);
13059 dc.SetFont(font);
13060 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13061 }
13062 m_overzoomFont = font;
13063 m_overzoomTextWidth = w;
13064 m_overzoomTextHeight = h;
13065}
13066
13067void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13068 delete m_pEM_OverZoom;
13069
13070 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13071 m_pEM_OverZoom =
13072 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13073 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13074}
13075
13076emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13077 int height, const wxString &str,
13078 ColorScheme cs) {
13079 int *pmap;
13080
13081 // Create a temporary bitmap
13082 wxBitmap bmp(width, height, -1);
13083
13084 // Create a memory DC
13085 wxMemoryDC temp_dc;
13086 temp_dc.SelectObject(bmp);
13087
13088 // Paint on it
13089 temp_dc.SetBackground(*wxWHITE_BRUSH);
13090 temp_dc.SetTextBackground(*wxWHITE);
13091 temp_dc.SetTextForeground(*wxBLACK);
13092
13093 temp_dc.Clear();
13094
13095 temp_dc.SetFont(font);
13096
13097 int str_w, str_h;
13098 temp_dc.GetTextExtent(str, &str_w, &str_h);
13099 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13100 temp_dc.DrawText(str, 1, 1);
13101
13102 // Deselect the bitmap
13103 temp_dc.SelectObject(wxNullBitmap);
13104
13105 // Convert bitmap the wxImage for manipulation
13106 wxImage img = bmp.ConvertToImage();
13107
13108 int image_width = str_w * 105 / 100;
13109 int image_height = str_h * 105 / 100;
13110 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13111 wxMin(image_height, img.GetHeight()));
13112 wxImage imgs = img.GetSubImage(r);
13113
13114 double val_factor;
13115 switch (cs) {
13116 case GLOBAL_COLOR_SCHEME_DAY:
13117 default:
13118 val_factor = 1;
13119 break;
13120 case GLOBAL_COLOR_SCHEME_DUSK:
13121 val_factor = .5;
13122 break;
13123 case GLOBAL_COLOR_SCHEME_NIGHT:
13124 val_factor = .25;
13125 break;
13126 }
13127
13128 int val;
13129 int index;
13130 const int w = imgs.GetWidth();
13131 const int h = imgs.GetHeight();
13132 pmap = (int *)calloc(w * h * sizeof(int), 1);
13133 // Create emboss map by differentiating the emboss image
13134 // and storing integer results in pmap
13135 // n.b. since the image is B/W, it is sufficient to check
13136 // one channel (i.e. red) only
13137 for (int y = 1; y < h - 1; y++) {
13138 for (int x = 1; x < w - 1; x++) {
13139 val =
13140 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13141 val = (int)(val * val_factor);
13142 index = (y * w) + x;
13143 pmap[index] = val;
13144 }
13145 }
13146
13147 emboss_data *pret = new emboss_data;
13148 pret->pmap = pmap;
13149 pret->width = w;
13150 pret->height = h;
13151
13152 return pret;
13153}
13154
13155void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13156 Track *active_track = NULL;
13157 for (Track *pTrackDraw : g_TrackList) {
13158 if (g_pActiveTrack == pTrackDraw) {
13159 active_track = pTrackDraw;
13160 continue;
13161 }
13162
13163 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13164 }
13165
13166 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13167}
13168
13169void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13170 Track *active_track = NULL;
13171 for (Track *pTrackDraw : g_TrackList) {
13172 if (g_pActiveTrack == pTrackDraw) {
13173 active_track = pTrackDraw;
13174 break;
13175 }
13176 }
13177 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13178}
13179
13180void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13181 Route *active_route = NULL;
13182 for (Route *pRouteDraw : *pRouteList) {
13183 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13184 active_route = pRouteDraw;
13185 continue;
13186 }
13187
13188 // if(m_canvasIndex == 1)
13189 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13190 }
13191
13192 // Draw any active or selected route (or track) last, so that is is always on
13193 // top
13194 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13195}
13196
13197void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13198 Route *active_route = NULL;
13199
13200 for (Route *pRouteDraw : *pRouteList) {
13201 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13202 active_route = pRouteDraw;
13203 break;
13204 }
13205 }
13206 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13207}
13208
13209void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13210 if (!pWayPointMan) return;
13211
13212 auto node = pWayPointMan->GetWaypointList()->begin();
13213
13214 while (node != pWayPointMan->GetWaypointList()->end()) {
13215 RoutePoint *pWP = *node;
13216 if (pWP) {
13217 if (pWP->m_bIsInRoute) {
13218 ++node;
13219 continue;
13220 }
13221
13222 /* technically incorrect... waypoint has bounding box */
13223 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13224 RoutePointGui(*pWP).Draw(dc, this, NULL);
13225 else {
13226 // Are Range Rings enabled?
13227 if (pWP->GetShowWaypointRangeRings() &&
13228 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13229 double factor = 1.00;
13230 if (pWP->GetWaypointRangeRingsStepUnits() ==
13231 1) // convert kilometers to NMi
13232 factor = 1 / 1.852;
13233
13234 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13235 pWP->GetWaypointRangeRingsStep() / 60.;
13236 radius *= 2; // Fudge factor
13237
13238 LLBBox radar_box;
13239 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13240 pWP->m_lat + radius, pWP->m_lon + radius);
13241 if (!BltBBox.IntersectOut(radar_box)) {
13242 RoutePointGui(*pWP).Draw(dc, this, NULL);
13243 }
13244 }
13245 }
13246 }
13247
13248 ++node;
13249 }
13250}
13251
13252void ChartCanvas::DrawBlinkObjects() {
13253 // All RoutePoints
13254 wxRect update_rect;
13255
13256 if (!pWayPointMan) return;
13257
13258 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13259 if (pWP) {
13260 if (pWP->m_bBlink) {
13261 update_rect.Union(pWP->CurrentRect_in_DC);
13262 }
13263 }
13264 }
13265 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13266}
13267
13268void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13269 // draw anchor watch rings, if activated
13270
13272 wxPoint r1, r2;
13273 wxPoint lAnchorPoint1, lAnchorPoint2;
13274 double lpp1 = 0.0;
13275 double lpp2 = 0.0;
13276 if (pAnchorWatchPoint1) {
13277 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13279 &lAnchorPoint1);
13280 }
13281 if (pAnchorWatchPoint2) {
13282 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13284 &lAnchorPoint2);
13285 }
13286
13287 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13288 wxPen ppPenr(GetGlobalColor("URED"), 2);
13289
13290 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13291 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13292 dc.SetBrush(*ppBrush);
13293
13294 if (lpp1 > 0) {
13295 dc.SetPen(ppPeng);
13296 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13297 }
13298
13299 if (lpp2 > 0) {
13300 dc.SetPen(ppPeng);
13301 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13302 }
13303
13304 if (lpp1 < 0) {
13305 dc.SetPen(ppPenr);
13306 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13307 }
13308
13309 if (lpp2 < 0) {
13310 dc.SetPen(ppPenr);
13311 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13312 }
13313 }
13314}
13315
13316double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13317 double lpp = 0.;
13318 wxPoint r1;
13319 wxPoint lAnchorPoint;
13320 double d1 = 0.0;
13321 double dabs;
13322 double tlat1, tlon1;
13323
13324 if (pAnchorWatchPoint) {
13325 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13326 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13327 dabs = fabs(d1 / 1852.);
13328 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13329 &tlat1, &tlon1);
13330 GetCanvasPointPix(tlat1, tlon1, &r1);
13331 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13332 &lAnchorPoint);
13333 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13334 pow((double)(lAnchorPoint.y - r1.y), 2));
13335
13336 // This is an entry watch
13337 if (d1 < 0) lpp = -lpp;
13338 }
13339 return lpp;
13340}
13341
13342//------------------------------------------------------------------------------------------
13343// Tides Support
13344//------------------------------------------------------------------------------------------
13345void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13346 if (!ptcmgr) return;
13347 if (this != wxWindow::FindFocus()) return;
13348
13349 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13350
13351 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13352 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13353 double lon = pIDX->IDX_lon;
13354 double lat = pIDX->IDX_lat;
13355
13356 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13357 if ((type == 't') || (type == 'T')) {
13358 if (BBox.Contains(lat, lon)) {
13359 // Manage the point selection list
13360 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13361 }
13362 }
13363 }
13364}
13365
13366void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13367 if (!ptcmgr) return;
13368
13369 wxDateTime this_now = gTimeSource;
13370 bool cur_time = !gTimeSource.IsValid();
13371 if (cur_time) this_now = wxDateTime::Now();
13372 time_t t_this_now = this_now.GetTicks();
13373
13374 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13375 wxPENSTYLE_SOLID);
13376 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13377 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13378 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13379 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13380
13381 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13382 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13383 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13384 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13385 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13386 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13387
13388 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13389 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13390 int font_size = wxMax(10, dFont->GetPointSize());
13391 font_size /= g_Platform->GetDisplayDIPMult(this);
13392 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13393 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13394 false, dFont->GetFaceName());
13395
13396 dc.SetPen(*pblack_pen);
13397 dc.SetBrush(*pgreen_brush);
13398
13399 wxBitmap bm;
13400 switch (m_cs) {
13401 case GLOBAL_COLOR_SCHEME_DAY:
13402 bm = m_bmTideDay;
13403 break;
13404 case GLOBAL_COLOR_SCHEME_DUSK:
13405 bm = m_bmTideDusk;
13406 break;
13407 case GLOBAL_COLOR_SCHEME_NIGHT:
13408 bm = m_bmTideNight;
13409 break;
13410 default:
13411 bm = m_bmTideDay;
13412 break;
13413 }
13414
13415 int bmw = bm.GetWidth();
13416 int bmh = bm.GetHeight();
13417
13418 float scale_factor = 1.0;
13419
13420 // Set the onscreen size of the symbol
13421 // Compensate for various display resolutions
13422 float icon_pixelRefDim = 45;
13423
13424 // Tidal report graphic is scaled by the text size of the label in use
13425 wxScreenDC sdc;
13426 int height;
13427 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13428 height *= g_Platform->GetDisplayDIPMult(this);
13429 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13430
13431 scale_factor *= pix_factor;
13432
13433 float user_scale_factor = g_ChartScaleFactorExp;
13434 if (g_ChartScaleFactorExp > 1.0)
13435 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13436 1.2; // soften the scale factor a bit
13437
13438 scale_factor *= user_scale_factor;
13439 scale_factor *= GetContentScaleFactor();
13440
13441 {
13442 double marge = 0.05;
13443 std::vector<LLBBox> drawn_boxes;
13444 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13445 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13446
13447 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13448 if ((type == 't') || (type == 'T')) // only Tides
13449 {
13450 double lon = pIDX->IDX_lon;
13451 double lat = pIDX->IDX_lat;
13452
13453 if (BBox.ContainsMarge(lat, lon, marge)) {
13454 // Avoid drawing detailed graphic for duplicate tide stations
13455 if (GetVP().chart_scale < 500000) {
13456 bool bdrawn = false;
13457 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13458 if (drawn_boxes[i].Contains(lat, lon)) {
13459 bdrawn = true;
13460 break;
13461 }
13462 }
13463 if (bdrawn) continue; // the station loop
13464
13465 LLBBox this_box;
13466 this_box.Set(lat, lon, lat, lon);
13467 this_box.EnLarge(.005);
13468 drawn_boxes.push_back(this_box);
13469 }
13470
13471 wxPoint r;
13472 GetCanvasPointPix(lat, lon, &r);
13473 // draw standard icons
13474 if (GetVP().chart_scale > 500000) {
13475 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13476 }
13477 // draw "extended" icons
13478 else {
13479 dc.SetFont(*plabelFont);
13480 {
13481 {
13482 float val, nowlev;
13483 float ltleve = 0.;
13484 float htleve = 0.;
13485 time_t tctime;
13486 time_t lttime = 0;
13487 time_t httime = 0;
13488 bool wt;
13489 // define if flood or ebb in the last ten minutes and verify if
13490 // data are useable
13491 if (ptcmgr->GetTideFlowSens(
13492 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13493 pIDX->IDX_rec_num, nowlev, val, wt)) {
13494 // search forward the first HW or LW near "now" ( starting at
13495 // "now" - ten minutes )
13496 ptcmgr->GetHightOrLowTide(
13497 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13498 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13499 wt, pIDX->IDX_rec_num, val, tctime);
13500 if (wt) {
13501 httime = tctime;
13502 htleve = val;
13503 } else {
13504 lttime = tctime;
13505 ltleve = val;
13506 }
13507 wt = !wt;
13508
13509 // then search opposite tide near "now"
13510 if (tctime > t_this_now) // search backward
13511 ptcmgr->GetHightOrLowTide(
13512 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13513 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13514 pIDX->IDX_rec_num, val, tctime);
13515 else
13516 // or search forward
13517 ptcmgr->GetHightOrLowTide(
13518 t_this_now, FORWARD_TEN_MINUTES_STEP,
13519 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13520 val, tctime);
13521 if (wt) {
13522 httime = tctime;
13523 htleve = val;
13524 } else {
13525 lttime = tctime;
13526 ltleve = val;
13527 }
13528
13529 // draw the tide rectangle:
13530
13531 // tide icon rectangle has default pre-scaled width = 12 ,
13532 // height = 45
13533 int width = (int)(12 * scale_factor + 0.5);
13534 int height = (int)(45 * scale_factor + 0.5);
13535 int linew = wxMax(1, (int)(scale_factor));
13536 int xDraw = r.x - (width / 2);
13537 int yDraw = r.y - (height / 2);
13538
13539 // process tide state ( %height and flow sens )
13540 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13541 int hs = (httime > lttime) ? -4 : 4;
13542 hs *= (int)(scale_factor + 0.5);
13543 if (ts > 0.995 || ts < 0.005) hs = 0;
13544 int ht_y = (int)(height * ts);
13545
13546 // draw yellow tide rectangle outlined in black
13547 pblack_pen->SetWidth(linew);
13548 dc.SetPen(*pblack_pen);
13549 dc.SetBrush(*pyelo_brush);
13550 dc.DrawRectangle(xDraw, yDraw, width, height);
13551
13552 // draw blue rectangle as water height, smaller in width than
13553 // yellow rectangle
13554 dc.SetPen(*pblue_pen);
13555 dc.SetBrush(*pblue_brush);
13556 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13557 (width - (4 * linew)), height - ht_y);
13558
13559 // draw sens arrows (ensure they are not "under-drawn" by top
13560 // line of blue rectangle )
13561 int hl;
13562 wxPoint arrow[3];
13563 arrow[0].x = xDraw + 2 * linew;
13564 arrow[1].x = xDraw + width / 2;
13565 arrow[2].x = xDraw + width - 2 * linew;
13566 pyelo_pen->SetWidth(linew);
13567 pblue_pen->SetWidth(linew);
13568 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13569 {
13570 hl = (int)(height * 0.25) + yDraw;
13571 arrow[0].y = hl;
13572 arrow[1].y = hl + hs;
13573 arrow[2].y = hl;
13574 if (ts < 0.15)
13575 dc.SetPen(*pyelo_pen);
13576 else
13577 dc.SetPen(*pblue_pen);
13578 dc.DrawLines(3, arrow);
13579 }
13580 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13581 {
13582 hl = (int)(height * 0.5) + yDraw;
13583 arrow[0].y = hl;
13584 arrow[1].y = hl + hs;
13585 arrow[2].y = hl;
13586 if (ts < 0.40)
13587 dc.SetPen(*pyelo_pen);
13588 else
13589 dc.SetPen(*pblue_pen);
13590 dc.DrawLines(3, arrow);
13591 }
13592 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13593 {
13594 hl = (int)(height * 0.75) + yDraw;
13595 arrow[0].y = hl;
13596 arrow[1].y = hl + hs;
13597 arrow[2].y = hl;
13598 if (ts < 0.65)
13599 dc.SetPen(*pyelo_pen);
13600 else
13601 dc.SetPen(*pblue_pen);
13602 dc.DrawLines(3, arrow);
13603 }
13604 // Convert nowlev to preferred height units (it comes from
13605 // GetHightOrLowTide in station units)
13606 double nowlev_converted = nowlev;
13607 Station_Data *pmsd = pIDX->pref_sta_data;
13608 if (pmsd) {
13609 // Convert from station units to meters first
13610 int unit_c = TCDataFactory::findunit(pmsd->unit);
13611 if (unit_c >= 0) {
13612 nowlev_converted =
13613 nowlev_converted *
13614 TCDataFactory::known_units[unit_c].conv_factor;
13615 }
13616 // Now convert from meters to preferred height units
13617 nowlev_converted = toUsrHeight(nowlev_converted);
13618 }
13619 // draw tide level text
13620 wxString s;
13621 s.Printf("%3.1f", nowlev_converted);
13622 // Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13623 if (pmsd) s.Append(getUsrHeightUnit());
13624 int wx1;
13625 dc.GetTextExtent(s, &wx1, NULL);
13626 wx1 *= g_Platform->GetDisplayDIPMult(this);
13627 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13628 }
13629 }
13630 }
13631 }
13632 }
13633 }
13634 }
13635 }
13636}
13637
13638//------------------------------------------------------------------------------------------
13639// Currents Support
13640//------------------------------------------------------------------------------------------
13641
13642void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13643 if (!ptcmgr) return;
13644
13645 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13646
13647 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13648 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13649 double lon = pIDX->IDX_lon;
13650 double lat = pIDX->IDX_lat;
13651
13652 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13653 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13654 if ((BBox.Contains(lat, lon))) {
13655 // Manage the point selection list
13656 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13657 }
13658 }
13659 }
13660}
13661
13662void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13663 if (!ptcmgr) return;
13664
13665 float tcvalue, dir;
13666 bool bnew_val;
13667 char sbuf[20];
13668 wxFont *pTCFont;
13669 double lon_last = 0.;
13670 double lat_last = 0.;
13671 // arrow size for Raz Blanchard : 12 knots north
13672 double marge = 0.2;
13673 bool cur_time = !gTimeSource.IsValid();
13674
13675 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13676 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13677
13678 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13679 wxPENSTYLE_SOLID);
13680 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13681 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13682 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13683 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13684 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13685 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13686 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13687 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13688
13689 double skew_angle = GetVPRotation();
13690
13691 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13692 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13693 int font_size = wxMax(10, dFont->GetPointSize());
13694 font_size /= g_Platform->GetDisplayDIPMult(this);
13695 pTCFont = FontMgr::Get().FindOrCreateFont(
13696 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13697 false, dFont->GetFaceName());
13698
13699 float scale_factor = 1.0;
13700
13701 // Set the onscreen size of the symbol
13702 // Current report graphic is scaled by the text size of the label in use
13703 wxScreenDC sdc;
13704 int height;
13705 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13706 height *= g_Platform->GetDisplayDIPMult(this);
13707 float nominal_icon_size_pixels = 15;
13708 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13709
13710 scale_factor *= pix_factor;
13711
13712 float user_scale_factor = g_ChartScaleFactorExp;
13713 if (g_ChartScaleFactorExp > 1.0)
13714 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13715 1.2; // soften the scale factor a bit
13716
13717 scale_factor *= user_scale_factor;
13718
13719 scale_factor *= GetContentScaleFactor();
13720
13721 {
13722 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13723 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13724 double lon = pIDX->IDX_lon;
13725 double lat = pIDX->IDX_lat;
13726
13727 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13728 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13729 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13730 wxPoint r;
13731 GetCanvasPointPix(lat, lon, &r);
13732
13733 wxPoint d[4]; // points of a diamond at the current station location
13734 int dd = (int)(5.0 * scale_factor + 0.5);
13735 d[0].x = r.x;
13736 d[0].y = r.y + dd;
13737 d[1].x = r.x + dd;
13738 d[1].y = r.y;
13739 d[2].x = r.x;
13740 d[2].y = r.y - dd;
13741 d[3].x = r.x - dd;
13742 d[3].y = r.y;
13743
13744 if (1) {
13745 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13746 dc.SetPen(*pblack_pen);
13747 dc.SetBrush(*porange_brush);
13748 dc.DrawPolygon(4, d);
13749
13750 if (type == 'C') {
13751 dc.SetBrush(*pblack_brush);
13752 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13753 }
13754
13755 if (GetVP().chart_scale < 1000000) {
13756 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13757 continue;
13758 } else
13759 continue;
13760
13761 if (1 /*type == 'c'*/) {
13762 {
13763 // Get the display pixel location of the current station
13764 int pixxc, pixyc;
13765 pixxc = r.x;
13766 pixyc = r.y;
13767
13768 // Adjust drawing size using logarithmic scale. tcvalue is
13769 // current in knots
13770 double a1 = fabs(tcvalue) * 10.;
13771 // Current values <= 0.1 knot will have no arrow
13772 a1 = wxMax(1.0, a1);
13773 double a2 = log10(a1);
13774
13775 float cscale = scale_factor * a2 * 0.3;
13776
13777 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13778 dc.SetPen(*porange_pen);
13779 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13780 cscale);
13781 // Draw text, if enabled
13782
13783 if (bDrawCurrentValues) {
13784 dc.SetFont(*pTCFont);
13785 tcvalue = toUsrSpeed(tcvalue);
13786 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13787 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13788 }
13789 }
13790 } // scale
13791 }
13792 /* This is useful for debugging the TC database
13793 else
13794 {
13795 dc.SetPen ( *porange_pen );
13796 dc.SetBrush ( *pgray_brush );
13797 dc.DrawPolygon ( 4, d );
13798 }
13799 */
13800 }
13801 lon_last = lon;
13802 lat_last = lat;
13803 }
13804 }
13805 }
13806}
13807
13808void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13809 ShowSingleTideDialog(x, y, pvIDX);
13810}
13811
13812void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13813 if (!pvIDX) return; // Validate input
13814
13815 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13816
13817 // Check if a tide dialog is already open and visible
13818 if (pCwin && pCwin->IsShown()) {
13819 // Same tide station: bring existing dialog to front (preserves user
13820 // context)
13821 if (pCwin->GetCurrentIDX() == pNewIDX) {
13822 pCwin->Raise();
13823 pCwin->SetFocus();
13824
13825 // Provide subtle visual feedback that dialog is already open
13826 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13827 return;
13828 }
13829
13830 // Different tide station: close current dialog before opening new one
13831 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13832 }
13833
13834 if (pCwin) {
13835 // This shouldn't happen but ensures clean state
13836 pCwin->Destroy();
13837 pCwin = NULL;
13838 }
13839
13840 // Create and display new tide dialog
13841 pCwin = new TCWin(this, x, y, pvIDX);
13842
13843 // Ensure the dialog is properly shown and focused
13844 if (pCwin) {
13845 pCwin->Show();
13846 pCwin->Raise();
13847 pCwin->SetFocus();
13848 }
13849}
13850
13851bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13852
13854 if (pCwin) {
13855 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13856 }
13857}
13858
13859#define NUM_CURRENT_ARROW_POINTS 9
13860static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13861 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13862 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13863 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13864
13865void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13866 double scale) {
13867 if (scale > 1e-2) {
13868 float sin_rot = sin(rot_angle * PI / 180.);
13869 float cos_rot = cos(rot_angle * PI / 180.);
13870
13871 // Move to the first point
13872
13873 float xt = CurrentArrowArray[0].x;
13874 float yt = CurrentArrowArray[0].y;
13875
13876 float xp = (xt * cos_rot) - (yt * sin_rot);
13877 float yp = (xt * sin_rot) + (yt * cos_rot);
13878 int x1 = (int)(xp * scale);
13879 int y1 = (int)(yp * scale);
13880
13881 // Walk thru the point list
13882 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13883 xt = CurrentArrowArray[ip].x;
13884 yt = CurrentArrowArray[ip].y;
13885
13886 float xp = (xt * cos_rot) - (yt * sin_rot);
13887 float yp = (xt * sin_rot) + (yt * cos_rot);
13888 int x2 = (int)(xp * scale);
13889 int y2 = (int)(yp * scale);
13890
13891 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13892
13893 x1 = x2;
13894 y1 = y2;
13895 }
13896 }
13897}
13898
13899wxString ChartCanvas::FindValidUploadPort() {
13900 wxString port;
13901 // Try to use the saved persistent upload port first
13902 if (!g_uploadConnection.IsEmpty() &&
13903 g_uploadConnection.StartsWith("Serial")) {
13904 port = g_uploadConnection;
13905 }
13906
13907 else {
13908 // If there is no persistent upload port recorded (yet)
13909 // then use the first available serial connection which has output defined.
13910 for (auto *cp : TheConnectionParams()) {
13911 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13912 port << "Serial:" << cp->Port;
13913 }
13914 }
13915 return port;
13916}
13917
13918void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13919 if (!win) return;
13920
13921 if (NULL == g_pais_query_dialog_active) {
13922 int pos_x = g_ais_query_dialog_x;
13923 int pos_y = g_ais_query_dialog_y;
13924
13925 if (g_pais_query_dialog_active) {
13926 g_pais_query_dialog_active->Destroy();
13927 g_pais_query_dialog_active = new AISTargetQueryDialog();
13928 } else {
13929 g_pais_query_dialog_active = new AISTargetQueryDialog();
13930 }
13931
13932 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13933 wxPoint(pos_x, pos_y));
13934
13935 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13936 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13937 g_pais_query_dialog_active->SetMMSI(mmsi);
13938 g_pais_query_dialog_active->UpdateText();
13939 wxSize sz = g_pais_query_dialog_active->GetSize();
13940
13941 bool b_reset_pos = false;
13942#ifdef __WXMSW__
13943 // Support MultiMonitor setups which an allow negative window positions.
13944 // If the requested window title bar does not intersect any installed
13945 // monitor, then default to simple primary monitor positioning.
13946 RECT frame_title_rect;
13947 frame_title_rect.left = pos_x;
13948 frame_title_rect.top = pos_y;
13949 frame_title_rect.right = pos_x + sz.x;
13950 frame_title_rect.bottom = pos_y + 30;
13951
13952 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13953 b_reset_pos = true;
13954#else
13955
13956 // Make sure drag bar (title bar) of window intersects wxClient Area of
13957 // screen, with a little slop...
13958 wxRect window_title_rect; // conservative estimate
13959 window_title_rect.x = pos_x;
13960 window_title_rect.y = pos_y;
13961 window_title_rect.width = sz.x;
13962 window_title_rect.height = 30;
13963
13964 wxRect ClientRect = wxGetClientDisplayRect();
13965 ClientRect.Deflate(
13966 60, 60); // Prevent the new window from being too close to the edge
13967 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13968
13969#endif
13970
13971 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13972
13973 } else {
13974 g_pais_query_dialog_active->SetMMSI(mmsi);
13975 g_pais_query_dialog_active->UpdateText();
13976 }
13977
13978 g_pais_query_dialog_active->Show();
13979}
13980
13981void ChartCanvas::ToggleCanvasQuiltMode() {
13982 bool cur_mode = GetQuiltMode();
13983
13984 if (!GetQuiltMode())
13985 SetQuiltMode(true);
13986 else if (GetQuiltMode()) {
13987 SetQuiltMode(false);
13988 g_sticky_chart = GetQuiltReferenceChartIndex();
13989 }
13990
13991 if (cur_mode != GetQuiltMode()) {
13992 SetupCanvasQuiltMode();
13993 DoCanvasUpdate();
13994 InvalidateGL();
13995 Refresh();
13996 }
13997 // TODO What to do about this?
13998 // g_bQuiltEnable = GetQuiltMode();
13999
14000 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
14001 if (ps52plib) ps52plib->GenerateStateHash();
14002
14003 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14004 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14005}
14006
14007void ChartCanvas::DoCanvasStackDelta(int direction) {
14008 if (!GetQuiltMode()) {
14009 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
14010 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
14011 if ((current_stack_index + direction) < 0) return;
14012
14013 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
14014 int new_dbIndex =
14015 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
14016
14017 if (IsChartQuiltableRef(new_dbIndex)) {
14018 ToggleCanvasQuiltMode();
14019 SelectQuiltRefdbChart(new_dbIndex);
14020 m_bpersistent_quilt = false;
14021 }
14022 } else {
14023 SelectChartFromStack(current_stack_index + direction);
14024 }
14025 } else {
14026 std::vector<int> piano_chart_index_array =
14027 GetQuiltExtendedStackdbIndexArray();
14028 int refdb = GetQuiltRefChartdbIndex();
14029
14030 // Find the ref chart in the stack
14031 int current_index = -1;
14032 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14033 if (refdb == piano_chart_index_array[i]) {
14034 current_index = i;
14035 break;
14036 }
14037 }
14038 if (current_index == -1) return;
14039
14040 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14041 int target_family = ctet.GetChartFamily();
14042
14043 int new_index = -1;
14044 int check_index = current_index + direction;
14045 bool found = false;
14046 int check_dbIndex = -1;
14047 int new_dbIndex = -1;
14048
14049 // When quilted. switch within the same chart family
14050 while (!found &&
14051 (unsigned int)check_index < piano_chart_index_array.size() &&
14052 (check_index >= 0)) {
14053 check_dbIndex = piano_chart_index_array[check_index];
14054 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14055 if (target_family == cte.GetChartFamily()) {
14056 found = true;
14057 new_index = check_index;
14058 new_dbIndex = check_dbIndex;
14059 break;
14060 }
14061
14062 check_index += direction;
14063 }
14064
14065 if (!found) return;
14066
14067 if (!IsChartQuiltableRef(new_dbIndex)) {
14068 ToggleCanvasQuiltMode();
14069 SelectdbChart(new_dbIndex);
14070 m_bpersistent_quilt = true;
14071 } else {
14072 SelectQuiltRefChart(new_index);
14073 }
14074 }
14075
14076 // update the state of the menu items (checkmarks etc)
14077 top_frame::Get()->UpdateGlobalMenuItems();
14078 SetQuiltChartHiLiteIndex(-1);
14079
14080 ReloadVP();
14081}
14082
14083//--------------------------------------------------------------------------------------------------------
14084//
14085// Toolbar support
14086//
14087//--------------------------------------------------------------------------------------------------------
14088
14089void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14090 // Handle the per-canvas toolbar clicks here
14091
14092 switch (event.GetId()) {
14093 case ID_ZOOMIN: {
14094 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14095 break;
14096 }
14097
14098 case ID_ZOOMOUT: {
14099 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14100 break;
14101 }
14102
14103 case ID_STKUP:
14104 DoCanvasStackDelta(1);
14105 DoCanvasUpdate();
14106 break;
14107
14108 case ID_STKDN:
14109 DoCanvasStackDelta(-1);
14110 DoCanvasUpdate();
14111 break;
14112
14113 case ID_FOLLOW: {
14114 TogglebFollow();
14115 break;
14116 }
14117
14118 case ID_CURRENT: {
14119 ShowCurrents(!GetbShowCurrent());
14120 ReloadVP();
14121 Refresh(false);
14122 break;
14123 }
14124
14125 case ID_TIDE: {
14126 ShowTides(!GetbShowTide());
14127 ReloadVP();
14128 Refresh(false);
14129 break;
14130 }
14131
14132 case ID_ROUTE: {
14133 if (0 == m_routeState) {
14134 StartRoute();
14135 } else {
14136 FinishRoute();
14137 }
14138
14139#ifdef __ANDROID__
14140 androidSetRouteAnnunciator(m_routeState == 1);
14141#endif
14142 break;
14143 }
14144
14145 case ID_AIS: {
14146 SetAISCanvasDisplayStyle(-1);
14147 break;
14148 }
14149
14150 default:
14151 break;
14152 }
14153
14154 // And then let gFrame handle the rest....
14155 event.Skip();
14156}
14157
14158void ChartCanvas::SetShowAIS(bool show) {
14159 m_bShowAIS = show;
14160 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14161 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14162}
14163
14164void ChartCanvas::SetAttenAIS(bool show) {
14165 m_bShowAISScaled = show;
14166 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14167 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14168}
14169
14170void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14171 // make some arrays to hold the dfferences between cycle steps
14172 // show all, scaled, hide all
14173 bool bShowAIS_Array[3] = {true, true, false};
14174 bool bShowScaled_Array[3] = {false, true, true};
14175 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14176 _("Attenuate less critical AIS targets"),
14177 _("Hide AIS Targets")};
14178 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14179 int ArraySize = 3;
14180 int AIS_Toolbar_Switch = 0;
14181 if (StyleIndx == -1) { // -1 means coming from toolbar button
14182 // find current state of switch
14183 for (int i = 1; i < ArraySize; i++) {
14184 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14185 (bShowScaled_Array[i] == m_bShowAISScaled))
14186 AIS_Toolbar_Switch = i;
14187 }
14188 AIS_Toolbar_Switch++; // we did click so continu with next item
14189 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14190 AIS_Toolbar_Switch++;
14191
14192 } else { // coming from menu bar.
14193 AIS_Toolbar_Switch = StyleIndx;
14194 }
14195 // make sure we are not above array
14196 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14197
14198 int AIS_Toolbar_Switch_Next =
14199 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14200 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14201 AIS_Toolbar_Switch_Next++;
14202 if (AIS_Toolbar_Switch_Next >= ArraySize)
14203 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14204
14205 // Set found values to global and member variables
14206 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14207 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14208}
14209
14210void ChartCanvas::TouchAISToolActive() {}
14211
14212void ChartCanvas::UpdateAISTBTool() {}
14213
14214//---------------------------------------------------------------------------------
14215//
14216// Compass/GPS status icon support
14217//
14218//---------------------------------------------------------------------------------
14219
14220void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14221 // Look for change in overlap or positions
14222 bool b_update = false;
14223 int cc1_edge_comp = 2;
14224 wxRect rect = m_Compass->GetRect();
14225 wxSize parent_size = GetSize();
14226
14227 parent_size *= m_displayScale;
14228
14229 // check to see if it would overlap if it was in its home position (upper
14230 // right)
14231 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14232 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14233 wxRect compass_rect(compass_pt, rect.GetSize());
14234
14235 m_Compass->Move(compass_pt);
14236
14237 if (m_Compass && m_Compass->IsShown())
14238 m_Compass->UpdateStatus(b_force_new | b_update);
14239
14240 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14241 scaler = wxMax(scaler, 1.0);
14242 wxPoint note_point = wxPoint(
14243 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14244 if (m_notification_button) {
14245 m_notification_button->Move(note_point);
14246 m_notification_button->UpdateStatus();
14247 }
14248
14249 if (b_force_new | b_update) Refresh();
14250}
14251
14252void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14253 ChartTypeEnum New_Type,
14254 ChartFamilyEnum New_Family) {
14255 if (!GetpCurrentStack()) return;
14256 if (!ChartData) return;
14257
14258 if (index < GetpCurrentStack()->nEntry) {
14259 // Open the new chart
14260 ChartBase *pTentative_Chart;
14261 pTentative_Chart = ChartData->OpenStackChartConditional(
14262 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14263
14264 if (pTentative_Chart) {
14265 if (m_singleChart) m_singleChart->Deactivate();
14266
14267 m_singleChart = pTentative_Chart;
14268 m_singleChart->Activate();
14269
14270 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14271 GetpCurrentStack(), m_singleChart->GetFullPath());
14272 }
14273
14274 // Setup the view
14275 double zLat, zLon;
14276 if (m_bFollow) {
14277 zLat = gLat;
14278 zLon = gLon;
14279 } else {
14280 zLat = m_vLat;
14281 zLon = m_vLon;
14282 }
14283
14284 double best_scale_ppm = GetBestVPScale(m_singleChart);
14285 double rotation = GetVPRotation();
14286 double oldskew = GetVPSkew();
14287 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14288
14289 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14290 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14291 if (fabs(newskew) > 0.0001) rotation = newskew;
14292 }
14293
14294 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14295
14296 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14297 }
14298
14299 // refresh Piano
14300 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14301 if (idx < 0) return;
14302
14303 std::vector<int> piano_active_chart_index_array;
14304 piano_active_chart_index_array.push_back(
14305 GetpCurrentStack()->GetCurrentEntrydbIndex());
14306 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14307}
14308
14309void ChartCanvas::SelectdbChart(int dbindex) {
14310 if (!GetpCurrentStack()) return;
14311 if (!ChartData) return;
14312
14313 if (dbindex >= 0) {
14314 // Open the new chart
14315 ChartBase *pTentative_Chart;
14316 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14317
14318 if (pTentative_Chart) {
14319 if (m_singleChart) m_singleChart->Deactivate();
14320
14321 m_singleChart = pTentative_Chart;
14322 m_singleChart->Activate();
14323
14324 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14325 GetpCurrentStack(), m_singleChart->GetFullPath());
14326 }
14327
14328 // Setup the view
14329 double zLat, zLon;
14330 if (m_bFollow) {
14331 zLat = gLat;
14332 zLon = gLon;
14333 } else {
14334 zLat = m_vLat;
14335 zLon = m_vLon;
14336 }
14337
14338 double best_scale_ppm = GetBestVPScale(m_singleChart);
14339
14340 if (m_singleChart)
14341 SetViewPoint(zLat, zLon, best_scale_ppm,
14342 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14343
14344 // SetChartUpdatePeriod( );
14345
14346 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14347 }
14348
14349 // TODO refresh_Piano();
14350}
14351
14352void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14353 double target_scale = GetVP().view_scale_ppm;
14354
14355 if (!GetQuiltMode()) {
14356 if (GetpCurrentStack()) {
14357 int stack_index = -1;
14358 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14359 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14360 if (check_dbIndex < 0) continue;
14361 const ChartTableEntry &cte =
14362 ChartData->GetChartTableEntry(check_dbIndex);
14363 if (type == cte.GetChartType()) {
14364 stack_index = i;
14365 break;
14366 } else if (family == cte.GetChartFamily()) {
14367 stack_index = i;
14368 break;
14369 }
14370 }
14371
14372 if (stack_index >= 0) {
14373 SelectChartFromStack(stack_index);
14374 }
14375 }
14376 } else {
14377 int sel_dbIndex = -1;
14378 std::vector<int> piano_chart_index_array =
14379 GetQuiltExtendedStackdbIndexArray();
14380 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14381 int check_dbIndex = piano_chart_index_array[i];
14382 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14383 if (type == cte.GetChartType()) {
14384 if (IsChartQuiltableRef(check_dbIndex)) {
14385 sel_dbIndex = check_dbIndex;
14386 break;
14387 }
14388 } else if (family == cte.GetChartFamily()) {
14389 if (IsChartQuiltableRef(check_dbIndex)) {
14390 sel_dbIndex = check_dbIndex;
14391 break;
14392 }
14393 }
14394 }
14395
14396 if (sel_dbIndex >= 0) {
14397 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14398 // Re-qualify the quilt reference chart selection
14399 AdjustQuiltRefChart();
14400 }
14401
14402 // Now reset the scale to the target...
14403 SetVPScale(target_scale);
14404 }
14405
14406 SetQuiltChartHiLiteIndex(-1);
14407
14408 ReloadVP();
14409}
14410
14411bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14412 return std::find(m_tile_yesshow_index_array.begin(),
14413 m_tile_yesshow_index_array.end(),
14414 index) != m_tile_yesshow_index_array.end();
14415}
14416
14417bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14418 return std::find(m_tile_noshow_index_array.begin(),
14419 m_tile_noshow_index_array.end(),
14420 index) != m_tile_noshow_index_array.end();
14421}
14422
14423void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14424 if (std::find(m_tile_noshow_index_array.begin(),
14425 m_tile_noshow_index_array.end(),
14426 index) == m_tile_noshow_index_array.end()) {
14427 m_tile_noshow_index_array.push_back(index);
14428 }
14429}
14430
14431//-------------------------------------------------------------------------------------------------------
14432//
14433// Piano support
14434//
14435//-------------------------------------------------------------------------------------------------------
14436
14437void ChartCanvas::HandlePianoClick(
14438 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14439 if (g_options && g_options->IsShown())
14440 return; // Piano might be invalid due to chartset updates.
14441 if (!m_pCurrentStack) return;
14442 if (!ChartData) return;
14443
14444 // stop movement or on slow computer we may get something like :
14445 // zoom out with the wheel (timer is set)
14446 // quickly click and display a chart, which may zoom in
14447 // but the delayed timer fires first and it zooms out again!
14448 StopMovement();
14449
14450 // When switching by piano key click, we may appoint the new target chart to
14451 // be any chart in the composite array.
14452 // As an improvement to UX, find the chart that is "closest" to the current
14453 // vp,
14454 // and select that chart. This will cause a jump to the centroid of that
14455 // chart
14456
14457 double distance = 25000; // RTW
14458 int closest_index = -1;
14459 for (int chart_index : selected_dbIndex_array) {
14460 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14461 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14462 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14463
14464 // measure distance as Manhattan style
14465 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14466 if (test_distance < distance) {
14467 distance = test_distance;
14468 closest_index = chart_index;
14469 }
14470 }
14471
14472 int selected_dbIndex = selected_dbIndex_array[0];
14473 if (closest_index >= 0) selected_dbIndex = closest_index;
14474
14475 if (!GetQuiltMode()) {
14476 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14477 if (IsChartQuiltableRef(selected_dbIndex)) {
14478 ToggleCanvasQuiltMode();
14479 SelectQuiltRefdbChart(selected_dbIndex);
14480 m_bpersistent_quilt = false;
14481 } else {
14482 SelectChartFromStack(selected_index);
14483 }
14484 } else {
14485 SelectChartFromStack(selected_index);
14486 g_sticky_chart = selected_dbIndex;
14487 }
14488
14489 if (m_singleChart)
14490 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14491 } else {
14492 // Handle MBTiles overlays first
14493 // Left click simply toggles the noshow array index entry
14494 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14495 bool bfound = false;
14496 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14497 if (m_tile_noshow_index_array[i] ==
14498 selected_dbIndex) { // chart is in the noshow list
14499 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14500 i); // erase it
14501 bfound = true;
14502 break;
14503 }
14504 }
14505 if (!bfound) {
14506 m_tile_noshow_index_array.push_back(selected_dbIndex);
14507 }
14508
14509 // If not already present, add this tileset to the "yes_show" array.
14510 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14511 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14512 }
14513
14514 else {
14515 if (IsChartQuiltableRef(selected_dbIndex)) {
14516 // if( ChartData ) ChartData->PurgeCache();
14517
14518 // If the chart is a vector chart, and of very large scale,
14519 // then we had better set the new scale directly to avoid excessive
14520 // underzoom on, eg, Inland ENCs
14521 bool set_scale = false;
14522 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14523 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14524 set_scale = true;
14525 }
14526 }
14527
14528 if (!set_scale) {
14529 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14530 } else {
14531 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14532
14533 // Adjust scale so that the selected chart is underzoomed/overzoomed
14534 // by a controlled amount
14535 ChartBase *pc =
14536 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14537 if (pc) {
14538 double proposed_scale_onscreen =
14540
14541 if (g_bPreserveScaleOnX) {
14542 proposed_scale_onscreen =
14543 wxMin(proposed_scale_onscreen,
14544 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14545 GetCanvasWidth()));
14546 } else {
14547 proposed_scale_onscreen =
14548 wxMin(proposed_scale_onscreen,
14549 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14550 GetCanvasWidth()));
14551
14552 proposed_scale_onscreen =
14553 wxMax(proposed_scale_onscreen,
14554 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14556 }
14557
14558 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14559 }
14560 }
14561 } else {
14562 ToggleCanvasQuiltMode();
14563 SelectdbChart(selected_dbIndex);
14564 m_bpersistent_quilt = true;
14565 }
14566 }
14567 }
14568
14569 SetQuiltChartHiLiteIndex(-1);
14570 // update the state of the menu items (checkmarks etc)
14571 top_frame::Get()->UpdateGlobalMenuItems();
14572 HideChartInfoWindow();
14573 DoCanvasUpdate();
14574 ReloadVP(); // Pick up the new selections
14575}
14576
14577void ChartCanvas::HandlePianoRClick(
14578 int x, int y, int selected_index,
14579 const std::vector<int> &selected_dbIndex_array) {
14580 if (g_options && g_options->IsShown())
14581 return; // Piano might be invalid due to chartset updates.
14582 if (!GetpCurrentStack()) return;
14583
14584 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14585 UpdateCanvasControlBar();
14586
14587 SetQuiltChartHiLiteIndex(-1);
14588}
14589
14590void ChartCanvas::HandlePianoRollover(
14591 int selected_index, const std::vector<int> &selected_dbIndex_array,
14592 int n_charts, int scale) {
14593 if (g_options && g_options->IsShown())
14594 return; // Piano might be invalid due to chartset updates.
14595 if (!GetpCurrentStack()) return;
14596 if (!ChartData) return;
14597
14598 if (ChartData->IsBusy()) return;
14599
14600 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14601
14602 if (!GetQuiltMode()) {
14603 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14604 } else {
14605 // Select the correct vector
14606 std::vector<int> piano_chart_index_array;
14607 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14608 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14609 if ((GetpCurrentStack()->nEntry > 1) ||
14610 (piano_chart_index_array.size() >= 1)) {
14611 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14612
14613 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14614 ReloadVP(false); // no VP adjustment allowed
14615 } else if (GetpCurrentStack()->nEntry == 1) {
14616 const ChartTableEntry &cte =
14617 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14618 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14619 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14620 ReloadVP(false);
14621 } else if ((-1 == selected_index) &&
14622 (0 == selected_dbIndex_array.size())) {
14623 ShowChartInfoWindow(key_location.x, -1);
14624 }
14625 }
14626 } else {
14627 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14628
14629 if ((GetpCurrentStack()->nEntry > 1) ||
14630 (piano_chart_index_array.size() >= 1)) {
14631 if (n_charts > 1)
14632 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14633 selected_dbIndex_array);
14634 else if (n_charts == 1)
14635 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14636
14637 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14638 ReloadVP(false); // no VP adjustment allowed
14639 }
14640 }
14641 }
14642}
14643
14644void ChartCanvas::ClearPianoRollover() {
14645 ClearQuiltChartHiLiteIndexArray();
14646 ShowChartInfoWindow(0, -1);
14647 std::vector<int> vec;
14648 ShowCompositeInfoWindow(0, 0, 0, vec);
14649 ReloadVP(false);
14650}
14651
14652void ChartCanvas::UpdateCanvasControlBar() {
14653 if (m_pianoFrozen) return;
14654
14655 if (!GetpCurrentStack()) return;
14656 if (!ChartData) return;
14657 if (!g_bShowChartBar) return;
14658
14659 int sel_type = -1;
14660 int sel_family = -1;
14661
14662 std::vector<int> piano_chart_index_array;
14663 std::vector<int> empty_piano_chart_index_array;
14664
14665 wxString old_hash = m_Piano->GetStoredHash();
14666
14667 if (GetQuiltMode()) {
14668 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14669 GetQuiltFullScreendbIndexArray());
14670
14671 std::vector<int> piano_active_chart_index_array =
14672 GetQuiltCandidatedbIndexArray();
14673 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14674
14675 std::vector<int> piano_eclipsed_chart_index_array =
14676 GetQuiltEclipsedStackdbIndexArray();
14677 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14678
14679 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14680 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14681
14682 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14683 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14684 } else {
14685 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14686 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14687 // TODO refresh_Piano();
14688
14689 if (m_singleChart) {
14690 sel_type = m_singleChart->GetChartType();
14691 sel_family = m_singleChart->GetChartFamily();
14692 }
14693 }
14694
14695 // Set up the TMerc and Skew arrays
14696 std::vector<int> piano_skew_chart_index_array;
14697 std::vector<int> piano_tmerc_chart_index_array;
14698 std::vector<int> piano_poly_chart_index_array;
14699
14700 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14701 const ChartTableEntry &ctei =
14702 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14703 double skew_norm = ctei.GetChartSkew();
14704 if (skew_norm > 180.) skew_norm -= 360.;
14705
14706 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14707 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14708
14709 // Polyconic skewed charts should show as skewed
14710 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14711 if (fabs(skew_norm) > 1.)
14712 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14713 else
14714 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14715 } else if (fabs(skew_norm) > 1.)
14716 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14717 }
14718 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14719 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14720 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14721
14722 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14723 if (new_hash != old_hash) {
14724 m_Piano->FormatKeys();
14725 HideChartInfoWindow();
14726 m_Piano->ResetRollover();
14727 SetQuiltChartHiLiteIndex(-1);
14728 m_brepaint_piano = true;
14729 }
14730
14731 // Create a bitmask int that describes what Family/Type of charts are shown in
14732 // the bar, and notify the platform.
14733 int mask = 0;
14734 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14735 const ChartTableEntry &ctei =
14736 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14737 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14738 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14739 if (e == CHART_FAMILY_RASTER) mask |= 1;
14740 if (e == CHART_FAMILY_VECTOR) {
14741 if (t == CHART_TYPE_CM93COMP)
14742 mask |= 4;
14743 else
14744 mask |= 2;
14745 }
14746 }
14747
14748 wxString s_indicated;
14749 if (sel_type == CHART_TYPE_CM93COMP)
14750 s_indicated = "cm93";
14751 else {
14752 if (sel_family == CHART_FAMILY_RASTER)
14753 s_indicated = "raster";
14754 else if (sel_family == CHART_FAMILY_VECTOR)
14755 s_indicated = "vector";
14756 }
14757
14758 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14759}
14760
14761void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14762
14763void ChartCanvas::PianoPopupMenu(
14764 int x, int y, int selected_index,
14765 const std::vector<int> &selected_dbIndex_array) {
14766 if (!GetpCurrentStack()) return;
14767
14768 // No context menu if quilting is disabled
14769 if (!GetQuiltMode()) return;
14770
14771 m_piano_ctx_menu = new wxMenu();
14772
14773 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14774 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14775 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14776 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14777 } else {
14778 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14779 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14780 // wxEVT_COMMAND_MENU_SELECTED,
14781 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14782
14783 menu_selected_dbIndex = selected_dbIndex_array[0];
14784 menu_selected_index = selected_index;
14785
14786 // Search the no-show array
14787 bool b_is_in_noshow = false;
14788 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14789 if (m_quilt_noshow_index_array[i] ==
14790 menu_selected_dbIndex) // chart is in the noshow list
14791 {
14792 b_is_in_noshow = true;
14793 break;
14794 }
14795 }
14796
14797 if (b_is_in_noshow) {
14798 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14799 _("Show This Chart"));
14800 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14801 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14802 } else if (GetpCurrentStack()->nEntry > 1) {
14803 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14804 _("Hide This Chart"));
14805 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14806 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14807 }
14808 }
14809
14810 wxPoint pos = wxPoint(x, y - 30);
14811
14812 // Invoke the drop-down menu
14813 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14814 PopupMenu(m_piano_ctx_menu, pos);
14815
14816 delete m_piano_ctx_menu;
14817 m_piano_ctx_menu = NULL;
14818
14819 HideChartInfoWindow();
14820 m_Piano->ResetRollover();
14821
14822 SetQuiltChartHiLiteIndex(-1);
14823 ClearQuiltChartHiLiteIndexArray();
14824
14825 ReloadVP();
14826}
14827
14828void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14829 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14830 if (m_quilt_noshow_index_array[i] ==
14831 menu_selected_dbIndex) // chart is in the noshow list
14832 {
14833 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14834 break;
14835 }
14836 }
14837}
14838
14839void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14840 if (!GetpCurrentStack()) return;
14841 if (!ChartData) return;
14842
14843 RemoveChartFromQuilt(menu_selected_dbIndex);
14844
14845 // It could happen that the chart being disabled is the reference
14846 // chart....
14847 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14848 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14849
14850 int i = menu_selected_index + 1; // select next smaller scale chart
14851 bool b_success = false;
14852 while (i < GetpCurrentStack()->nEntry - 1) {
14853 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14854 if (type == ChartData->GetDBChartType(dbIndex)) {
14855 SelectQuiltRefChart(i);
14856 b_success = true;
14857 break;
14858 }
14859 i++;
14860 }
14861
14862 // If that did not work, try to select the next larger scale compatible
14863 // chart
14864 if (!b_success) {
14865 i = menu_selected_index - 1;
14866 while (i > 0) {
14867 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14868 if (type == ChartData->GetDBChartType(dbIndex)) {
14869 SelectQuiltRefChart(i);
14870 b_success = true;
14871 break;
14872 }
14873 i--;
14874 }
14875 }
14876 }
14877}
14878
14879void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14880 // Remove the item from the list (if it appears) to avoid multiple addition
14881 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14882 if (m_quilt_noshow_index_array[i] ==
14883 dbIndex) // chart is already in the noshow list
14884 {
14885 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14886 break;
14887 }
14888 }
14889
14890 m_quilt_noshow_index_array.push_back(dbIndex);
14891}
14892
14893bool ChartCanvas::UpdateS52State() {
14894 bool retval = false;
14895
14896 if (ps52plib) {
14897 ps52plib->SetShowS57Text(m_encShowText);
14898 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14899 ps52plib->m_bShowSoundg = m_encShowDepth;
14900 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14901 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14902
14903 // Lights
14904 if (!m_encShowLights) // On, going off
14905 ps52plib->AddObjNoshow("LIGHTS");
14906 else // Off, going on
14907 ps52plib->RemoveObjNoshow("LIGHTS");
14908 ps52plib->SetLightsOff(!m_encShowLights);
14909 ps52plib->m_bExtendLightSectors = true;
14910
14911 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14912 ps52plib->SetAnchorOn(m_encShowAnchor);
14913 ps52plib->SetQualityOfData(m_encShowDataQual);
14914 }
14915
14916 return retval;
14917}
14918
14919void ChartCanvas::SetShowENCDataQual(bool show) {
14920 m_encShowDataQual = show;
14921 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14922 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14923
14924 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14925}
14926
14927void ChartCanvas::SetShowENCText(bool show) {
14928 m_encShowText = show;
14929 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14930 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14931
14932 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14933}
14934
14935void ChartCanvas::SetENCDisplayCategory(int category) {
14936 m_encDisplayCategory = category;
14937 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14938}
14939
14940void ChartCanvas::SetShowENCDepth(bool show) {
14941 m_encShowDepth = show;
14942 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14943 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14944
14945 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14946}
14947
14948void ChartCanvas::SetShowENCLightDesc(bool show) {
14949 m_encShowLightDesc = show;
14950 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14951 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14952
14953 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14954}
14955
14956void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14957 m_encShowBuoyLabels = show;
14958 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14959}
14960
14961void ChartCanvas::SetShowENCLights(bool show) {
14962 m_encShowLights = show;
14963 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14964 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14965
14966 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14967}
14968
14969void ChartCanvas::SetShowENCAnchor(bool show) {
14970 m_encShowAnchor = show;
14971 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14972 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14973
14974 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14975}
14976
14977wxRect ChartCanvas::GetMUIBarRect() {
14978 wxRect rv;
14979 if (m_muiBar) {
14980 rv = m_muiBar->GetRect();
14981 }
14982
14983 return rv;
14984}
14985
14986void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14987 if (!GetAlertString().IsEmpty()) {
14988 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14989 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14990
14991 dc.SetFont(*pfont);
14992 dc.SetPen(*wxTRANSPARENT_PEN);
14993
14994 dc.SetBrush(wxColour(243, 229, 47));
14995 int w, h;
14996 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14997 h += 2;
14998 // int yp = vp.pix_height - 20 - h;
14999
15000 wxRect sbr = GetScaleBarRect();
15001 int xp = sbr.x + sbr.width + 10;
15002 int yp = (sbr.y + sbr.height) - h;
15003
15004 int wdraw = w + 10;
15005 dc.DrawRectangle(xp, yp, wdraw, h);
15006 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
15007 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
15008 }
15009}
15010
15011//--------------------------------------------------------------------------------------------------------
15012// Screen Brightness Control Support Routines
15013//
15014//--------------------------------------------------------------------------------------------------------
15015
15016#ifdef __UNIX__
15017#define BRIGHT_XCALIB
15018#define __OPCPN_USEICC__
15019#endif
15020
15021#ifdef __OPCPN_USEICC__
15022int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15023 double co_green, double co_blue);
15024
15025wxString temp_file_name;
15026#endif
15027
15028#if 0
15029class ocpnCurtain: public wxDialog
15030{
15031 DECLARE_CLASS( ocpnCurtain )
15032 DECLARE_EVENT_TABLE()
15033
15034public:
15035 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15036 ~ocpnCurtain( );
15037 bool ProcessEvent(wxEvent& event);
15038
15039};
15040
15041IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15042
15043BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15044END_EVENT_TABLE()
15045
15046ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15047{
15048 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15049}
15050
15051ocpnCurtain::~ocpnCurtain()
15052{
15053}
15054
15055bool ocpnCurtain::ProcessEvent(wxEvent& event)
15056{
15057 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15058 return GetParent()->GetEventHandler()->ProcessEvent(event);
15059}
15060#endif
15061
15062#ifdef _WIN32
15063#include <windows.h>
15064
15065HMODULE hGDI32DLL;
15066typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15067typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15068SetDeviceGammaRamp_ptr_type
15069 g_pSetDeviceGammaRamp; // the API entry points in the dll
15070GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15071
15072WORD *g_pSavedGammaMap;
15073
15074#endif
15075
15076int InitScreenBrightness() {
15077#ifdef _WIN32
15078#ifdef ocpnUSE_GL
15079 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15080 HDC hDC;
15081 BOOL bbr;
15082
15083 if (NULL == hGDI32DLL) {
15084 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15085
15086 if (NULL != hGDI32DLL) {
15087 // Get the entry points of the required functions
15088 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15089 hGDI32DLL, "SetDeviceGammaRamp");
15090 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15091 hGDI32DLL, "GetDeviceGammaRamp");
15092
15093 // If the functions are not found, unload the DLL and return false
15094 if ((NULL == g_pSetDeviceGammaRamp) ||
15095 (NULL == g_pGetDeviceGammaRamp)) {
15096 FreeLibrary(hGDI32DLL);
15097 hGDI32DLL = NULL;
15098 return 0;
15099 }
15100 }
15101 }
15102
15103 // Interface is ready, so....
15104 // Get some storage
15105 if (!g_pSavedGammaMap) {
15106 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15107
15108 hDC = GetDC(NULL); // Get the full screen DC
15109 bbr = g_pGetDeviceGammaRamp(
15110 hDC, g_pSavedGammaMap); // Get the existing ramp table
15111 ReleaseDC(NULL, hDC); // Release the DC
15112 }
15113
15114 // On Windows hosts, try to adjust the registry to allow full range
15115 // setting of Gamma table This is an undocumented Windows hack.....
15116 wxRegKey *pRegKey = new wxRegKey(
15117 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15118 "NT\\CurrentVersion\\ICM");
15119 if (!pRegKey->Exists()) pRegKey->Create();
15120 pRegKey->SetValue("GdiIcmGammaRange", 256);
15121
15122 g_brightness_init = true;
15123 return 1;
15124 }
15125#endif
15126
15127 {
15128 if (NULL == g_pcurtain) {
15129 if (top_frame::Get()->CanSetTransparent()) {
15130 // Build the curtain window
15131 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15132 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15133 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15134 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15135
15136 // g_pcurtain = new ocpnCurtain(gFrame,
15137 // wxPoint(0,0),::wxGetDisplaySize(),
15138 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15139 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15140
15141 g_pcurtain->Hide();
15142
15143 HWND hWnd = GetHwndOf(g_pcurtain);
15144 SetWindowLong(hWnd, GWL_EXSTYLE,
15145 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15146 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15147 g_pcurtain->SetTransparent(0);
15148
15149 g_pcurtain->Maximize();
15150 g_pcurtain->Show();
15151
15152 // All of this is obtuse, but necessary for Windows...
15153 g_pcurtain->Enable();
15154 g_pcurtain->Disable();
15155
15156 top_frame::Get()->Disable();
15157 top_frame::Get()->Enable();
15158 // SetFocus();
15159 }
15160 }
15161 g_brightness_init = true;
15162
15163 return 1;
15164 }
15165#else
15166 // Look for "xcalib" application
15167 wxString cmd("xcalib -version");
15168
15169 wxArrayString output;
15170 long r = wxExecute(cmd, output);
15171 if (0 != r)
15172 wxLogMessage(
15173 " External application \"xcalib\" not found. Screen brightness "
15174 "not changed.");
15175
15176 g_brightness_init = true;
15177 return 0;
15178#endif
15179}
15180
15181int RestoreScreenBrightness() {
15182#ifdef _WIN32
15183
15184 if (g_pSavedGammaMap) {
15185 HDC hDC = GetDC(NULL); // Get the full screen DC
15186 g_pSetDeviceGammaRamp(hDC,
15187 g_pSavedGammaMap); // Restore the saved ramp table
15188 ReleaseDC(NULL, hDC); // Release the DC
15189
15190 free(g_pSavedGammaMap);
15191 g_pSavedGammaMap = NULL;
15192 }
15193
15194 if (g_pcurtain) {
15195 g_pcurtain->Close();
15196 g_pcurtain->Destroy();
15197 g_pcurtain = NULL;
15198 }
15199
15200 g_brightness_init = false;
15201 return 1;
15202
15203#endif
15204
15205#ifdef BRIGHT_XCALIB
15206 if (g_brightness_init) {
15207 wxString cmd;
15208 cmd = "xcalib -clear";
15209 wxExecute(cmd, wxEXEC_ASYNC);
15210 g_brightness_init = false;
15211 }
15212
15213 return 1;
15214#endif
15215
15216 return 0;
15217}
15218
15219// Set brightness. [0..100]
15220int SetScreenBrightness(int brightness) {
15221#ifdef _WIN32
15222
15223 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15224 // some (most modern?) versions of gdi32.dll Load the required library dll,
15225 // if not already in place
15226#ifdef ocpnUSE_GL
15227 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15228 if (g_pcurtain) {
15229 g_pcurtain->Close();
15230 g_pcurtain->Destroy();
15231 g_pcurtain = NULL;
15232 }
15233
15234 InitScreenBrightness();
15235
15236 if (NULL == hGDI32DLL) {
15237 // Unicode stuff.....
15238 wchar_t wdll_name[80];
15239 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15240 LPCWSTR cstr = wdll_name;
15241
15242 hGDI32DLL = LoadLibrary(cstr);
15243
15244 if (NULL != hGDI32DLL) {
15245 // Get the entry points of the required functions
15246 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15247 hGDI32DLL, "SetDeviceGammaRamp");
15248 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15249 hGDI32DLL, "GetDeviceGammaRamp");
15250
15251 // If the functions are not found, unload the DLL and return false
15252 if ((NULL == g_pSetDeviceGammaRamp) ||
15253 (NULL == g_pGetDeviceGammaRamp)) {
15254 FreeLibrary(hGDI32DLL);
15255 hGDI32DLL = NULL;
15256 return 0;
15257 }
15258 }
15259 }
15260
15261 HDC hDC = GetDC(NULL); // Get the full screen DC
15262
15263 /*
15264 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15265 if (cmcap != CM_GAMMA_RAMP)
15266 {
15267 wxLogMessage(" Video hardware does not support brightness control by
15268 gamma ramp adjustment."); return false;
15269 }
15270 */
15271
15272 int increment = brightness * 256 / 100;
15273
15274 // Build the Gamma Ramp table
15275 WORD GammaTable[3][256];
15276
15277 int table_val = 0;
15278 for (int i = 0; i < 256; i++) {
15279 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15280 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15281 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15282
15283 table_val += increment;
15284
15285 if (table_val > 65535) table_val = 65535;
15286 }
15287
15288 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15289 ReleaseDC(NULL, hDC); // Release the DC
15290
15291 return 1;
15292 }
15293#endif
15294
15295 {
15296 if (g_pSavedGammaMap) {
15297 HDC hDC = GetDC(NULL); // Get the full screen DC
15298 g_pSetDeviceGammaRamp(hDC,
15299 g_pSavedGammaMap); // Restore the saved ramp table
15300 ReleaseDC(NULL, hDC); // Release the DC
15301 }
15302
15303 if (brightness < 100) {
15304 if (NULL == g_pcurtain) InitScreenBrightness();
15305
15306 if (g_pcurtain) {
15307 int sbrite = wxMax(1, brightness);
15308 sbrite = wxMin(100, sbrite);
15309
15310 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15311 }
15312 } else {
15313 if (g_pcurtain) {
15314 g_pcurtain->Close();
15315 g_pcurtain->Destroy();
15316 g_pcurtain = NULL;
15317 }
15318 }
15319
15320 return 1;
15321 }
15322
15323#endif
15324
15325#ifdef BRIGHT_XCALIB
15326
15327 if (!g_brightness_init) {
15328 last_brightness = 100;
15329 g_brightness_init = true;
15330 temp_file_name = wxFileName::CreateTempFileName("");
15331 InitScreenBrightness();
15332 }
15333
15334#ifdef __OPCPN_USEICC__
15335 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15336 // desired, and then activate this temporary profile using xcalib <filename>
15337 if (!CreateSimpleICCProfileFile(
15338 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15339 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15340 wxString cmd("xcalib ");
15341 cmd += temp_file_name;
15342
15343 wxExecute(cmd, wxEXEC_ASYNC);
15344 }
15345
15346#else
15347 // Or, use "xcalib -co" to set overall contrast value
15348 // This is not as nice, since the -co parameter wants to be a fraction of
15349 // the current contrast, and values greater than 100 are not allowed. As a
15350 // result, increases of contrast must do a "-clear" step first, which
15351 // produces objectionable flashing.
15352 if (brightness > last_brightness) {
15353 wxString cmd;
15354 cmd = "xcalib -clear";
15355 wxExecute(cmd, wxEXEC_ASYNC);
15356
15357 ::wxMilliSleep(10);
15358
15359 int brite_adj = wxMax(1, brightness);
15360 cmd.Printf("xcalib -co %2d -a", brite_adj);
15361 wxExecute(cmd, wxEXEC_ASYNC);
15362 } else {
15363 int brite_adj = wxMax(1, brightness);
15364 int factor = (brite_adj * 100) / last_brightness;
15365 factor = wxMax(1, factor);
15366 wxString cmd;
15367 cmd.Printf("xcalib -co %2d -a", factor);
15368 wxExecute(cmd, wxEXEC_ASYNC);
15369 }
15370
15371#endif
15372
15373 last_brightness = brightness;
15374
15375#endif
15376
15377 return 0;
15378}
15379
15380#ifdef __OPCPN_USEICC__
15381
15382#define MLUT_TAG 0x6d4c5554L
15383#define VCGT_TAG 0x76636774L
15384
15385int GetIntEndian(unsigned char *s) {
15386 int ret;
15387 unsigned char *p;
15388 int i;
15389
15390 p = (unsigned char *)&ret;
15391
15392 if (1)
15393 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15394 else
15395 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15396
15397 return ret;
15398}
15399
15400unsigned short GetShortEndian(unsigned char *s) {
15401 unsigned short ret;
15402 unsigned char *p;
15403 int i;
15404
15405 p = (unsigned char *)&ret;
15406
15407 if (1)
15408 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15409 else
15410 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15411
15412 return ret;
15413}
15414
15415// Create a very simple Gamma correction file readable by xcalib
15416int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15417 double co_green, double co_blue) {
15418 FILE *fp;
15419
15420 if (file_name) {
15421 fp = fopen(file_name, "wb");
15422 if (!fp) return -1; /* file can not be created */
15423 } else
15424 return -1; /* filename char pointer not valid */
15425
15426 // Write header
15427 char header[128];
15428 for (int i = 0; i < 128; i++) header[i] = 0;
15429
15430 fwrite(header, 128, 1, fp);
15431
15432 // Num tags
15433 int numTags0 = 1;
15434 int numTags = GetIntEndian((unsigned char *)&numTags0);
15435 fwrite(&numTags, 1, 4, fp);
15436
15437 int tagName0 = VCGT_TAG;
15438 int tagName = GetIntEndian((unsigned char *)&tagName0);
15439 fwrite(&tagName, 1, 4, fp);
15440
15441 int tagOffset0 = 128 + 4 * sizeof(int);
15442 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15443 fwrite(&tagOffset, 1, 4, fp);
15444
15445 int tagSize0 = 1;
15446 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15447 fwrite(&tagSize, 1, 4, fp);
15448
15449 fwrite(&tagName, 1, 4, fp); // another copy of tag
15450
15451 fwrite(&tagName, 1, 4, fp); // dummy
15452
15453 // Table type
15454
15455 /* VideoCardGammaTable (The simplest type) */
15456 int gammatype0 = 0;
15457 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15458 fwrite(&gammatype, 1, 4, fp);
15459
15460 int numChannels0 = 3;
15461 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15462 fwrite(&numChannels, 1, 2, fp);
15463
15464 int numEntries0 = 256;
15465 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15466 fwrite(&numEntries, 1, 2, fp);
15467
15468 int entrySize0 = 1;
15469 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15470 fwrite(&entrySize, 1, 2, fp);
15471
15472 unsigned char ramp[256];
15473
15474 // Red ramp
15475 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15476 fwrite(ramp, 256, 1, fp);
15477
15478 // Green ramp
15479 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15480 fwrite(ramp, 256, 1, fp);
15481
15482 // Blue ramp
15483 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15484 fwrite(ramp, 256, 1, fp);
15485
15486 fclose(fp);
15487
15488 return 0;
15489}
15490#endif // __OPCPN_USEICC__
Select * pSelectAIS
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
AIS target definitions.
Class AISTargetQueryDialog.
std::unique_ptr< HostApi > GetHostApi()
HostApi factory,.
Definition api_121.cpp:871
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:71
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:61
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1316
arrayofCanvasPtr g_canvasArray
Global instance.
Definition chcanv.cpp:173
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1315
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1316
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1315
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
Minimal ChartCAnvas interface with very little dependencies.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:45
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:126
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:173
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13853
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:4583
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11878
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4579
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3675
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13812
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:4529
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:807
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:511
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:542
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2360
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:8035
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7844
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5111
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:898
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4660
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5391
float GetVPScale() override
Return ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:183
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:791
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4604
bool IsTideDialogOpen() const
Definition chcanv.cpp:13851
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:4666
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13808
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4524
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5410
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10287
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:486
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
char IDX_station_name[MAXNAMELEN]
Name of the tidal or current station.
Definition idx_entry.h:62
int station_tz_offset
Offset in seconds to convert from harmonic data (epochs) to the station time zone.
Definition idx_entry.h:93
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:60
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:201
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:59
Data for a loaded plugin, including dl-loaded library.
int m_cap_flag
PlugIn Capabilities descriptor.
PluginLoader is a backend module without any direct GUI functionality.
const ArrayOfPlugIns * GetPlugInArray()
Return list of currently loaded plugins.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1794
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:765
Dialog for displaying query results of S57 objects.
Definition tc_win.h:46
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:77
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:36
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:809
double view_scale_ppm
Requested view scale in physical pixels per meter (ppm), before applying projections.
Definition viewport.h:204
double ref_scale
The nominal scale of the "reference chart" for this view.
Definition viewport.h:221
int pix_height
Height of the viewport in physical pixels.
Definition viewport.h:233
double rotation
Rotation angle of the viewport in radians.
Definition viewport.h:214
void SetPixelScale(double scale)
Set the physical to logical pixel ratio for the display.
Definition viewport.cpp:121
int pix_width
Width of the viewport in physical pixels.
Definition viewport.h:231
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
Definition viewport.cpp:133
double tilt
Tilt angle for perspective view in radians.
Definition viewport.h:216
double skew
Angular distortion (shear transform) applied to the viewport in radians.
Definition viewport.h:212
void GetLLFromPix(const wxPoint &p, double *lat, double *lon)
Convert physical pixel coordinates on the ViewPort to latitude and longitude.
Definition viewport.h:80
double clon
Center longitude of the viewport in degrees.
Definition viewport.h:199
double clat
Center latitude of the viewport in degrees.
Definition viewport.h:197
wxPoint GetPixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates.
Definition viewport.cpp:124
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:473
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:55
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Display utilities.
Font list manager.
OpenGL chart rendering canvas.
Platform independent GL includes.
glTextureManager * g_glTextureManager
Global instance.
OpenGL texture container.
GoToPositionDialog * pGoToPositionDialog
Global instance.
Go to position dialog...
GSHHS Chart Object (Global Self-consistent, Hierarchical, High-resolution Shoreline) Derived from htt...
Hooks into gui available in model.
size_t g_current_monitor
Current monitor displaying main application frame.
Definition gui_vars.cpp:75
double g_current_monitor_dip_px_ratio
ratio to convert between DIP and physical pixels.
Definition gui_vars.cpp:69
bool g_b_overzoom_x
Allow high overzoom.
Definition gui_vars.cpp:52
Miscellaneous globals primarely used by gui layer, not persisted in configuration file.
Hotheys help dialog (the '?' button).
GUI constant definitions.
iENCToolbar * g_iENCToolbar
Global instance.
iENC specific chart operations floating toolbar extension
Read and write KML Format.
MarkInfoDlg * g_pMarkInfoDialog
global instance
Definition mark_info.cpp:76
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
double AnchorDistFix(double const d, double const AnchorPointMinDist, double const AnchorPointMaxDist)
Return constrained value of d so that AnchorPointMinDist < abs(d) < AnchorPointMaxDist.
MySQL based storage for routes, tracks, etc.
Utility functions.
wxString getUsrHeightUnit(int unit)
Get the abbreviation for the preferred height unit.
double toUsrHeight(double m_height, int unit)
Convert height from meters to preferred height units.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
Optimized wxBitmap Object.
ChartFamilyEnumPI
Enumeration of chart families (broad categories).
#define OVERLAY_LEGACY
Overlay rendering priorities determine the layering order of plugin graphics.
#define OVERLAY_OVER_UI
Highest priority for overlays above all UI elements.
#define OVERLAY_OVER_EMBOSS
Priority for overlays above embossed chart features.
#define OVERLAY_OVER_SHIPS
Priority for overlays that should appear above ship symbols.
int GetCanvasCount()
Gets total number of chart canvases.
PI_DisCat GetENCDisplayCategory(int CanvasIndex)
Gets current ENC display category.
int GetCanvasIndexUnderMouse()
Gets index of chart canvas under mouse cursor.
int GetChartbarHeight()
Gets height of chart bar in pixels.
double OCPN_GetWinDIPScaleFactor()
Gets Windows-specific DPI scaling factor.
OpenCPN region handling.
Miscellaneous utilities, many of which string related.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:181
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
RouteManagerDialog * pRouteManagerDialog
Global instance.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
S57 Chart Object.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:187
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:66
OpenCPN Toolbar.
Abstract gFrame/MyFrame interface.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.