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 = false;
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) {
7728 const bool wasReplaying = m_isReplayingClickEvent;
7729 m_isReplayingClickEvent = true;
7730 MouseEvent(singleClickEvent);
7731 m_isReplayingClickEvent = wasReplaying;
7732 }
7733
7734 singleClickEventIsValid = false;
7735 m_DoubleClickTimer->Stop();
7736}
7737
7738bool leftIsDown;
7739
7740bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7741 if (!m_bChartDragging && !m_bDrawingRoute) {
7742 /*
7743 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7744 * mouse event coordinates are in logical pixels.
7745 */
7746 if (m_Compass && m_Compass->IsShown()) {
7747 wxRect logicalRect = m_Compass->GetLogicalRect();
7748 bool isInCompass = logicalRect.Contains(event.GetPosition());
7749 if (isInCompass || m_mouseWasInCompass) {
7750 if (m_Compass->MouseEvent(event)) {
7751 cursor_region = CENTER;
7752 if (!g_btouch) SetCanvasCursor(event);
7753 m_mouseWasInCompass = isInCompass;
7754 return true;
7755 }
7756 }
7757 m_mouseWasInCompass = isInCompass;
7758 }
7759
7760 if (m_notification_button && m_notification_button->IsShown()) {
7761 wxRect logicalRect = m_notification_button->GetLogicalRect();
7762 bool isinButton = logicalRect.Contains(event.GetPosition());
7763 if (isinButton) {
7764 SetCursor(*pCursorArrow);
7765 if (event.LeftDown()) HandleNotificationMouseClick();
7766 return true;
7767 }
7768 }
7769
7770 if (MouseEventToolbar(event)) return true;
7771
7772 if (MouseEventChartBar(event)) return true;
7773
7774 if (MouseEventMUIBar(event)) return true;
7775
7776 if (MouseEventIENCBar(event)) return true;
7777 }
7778 return false;
7779}
7780
7781void ChartCanvas::HandleNotificationMouseClick() {
7782 if (!m_NotificationsList) {
7783 m_NotificationsList = new NotificationsList(this);
7784
7785 // calculate best size for Notification list
7786 m_NotificationsList->RecalculateSize();
7787 m_NotificationsList->Hide();
7788 }
7789
7790 if (m_NotificationsList->IsShown()) {
7791 m_NotificationsList->Hide();
7792 } else {
7793 m_NotificationsList->RecalculateSize();
7794 m_NotificationsList->ReloadNotificationList();
7795 m_NotificationsList->Show();
7796 }
7797}
7798bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7799 if (!g_bShowChartBar) return false;
7800
7801 if (!m_Piano->MouseEvent(event)) return false;
7802
7803 cursor_region = CENTER;
7804 if (!g_btouch) SetCanvasCursor(event);
7805 return true;
7806}
7807
7808bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7809 if (!IsPrimaryCanvas()) return false;
7810
7811 if (g_MainToolbar) {
7812 if (!g_MainToolbar->MouseEvent(event))
7813 return false;
7814 else
7815 g_MainToolbar->RefreshToolbar();
7816 }
7817
7818 cursor_region = CENTER;
7819 if (!g_btouch) SetCanvasCursor(event);
7820 return true;
7821}
7822
7823bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7824 if (!IsPrimaryCanvas()) return false;
7825
7826 if (g_iENCToolbar) {
7827 if (!g_iENCToolbar->MouseEvent(event))
7828 return false;
7829 else {
7830 g_iENCToolbar->RefreshToolbar();
7831 return true;
7832 }
7833 }
7834 return false;
7835}
7836
7837bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7838 if (m_muiBar) {
7839 if (!m_muiBar->MouseEvent(event)) return false;
7840 }
7841
7842 cursor_region = CENTER;
7843 if (!g_btouch) SetCanvasCursor(event);
7844 if (m_muiBar)
7845 return true;
7846 else
7847 return false;
7848}
7849
7850bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7851 int x, y;
7852
7853 bool bret = false;
7854
7855 event.GetPosition(&x, &y);
7856
7857 x *= m_displayScale;
7858 y *= m_displayScale;
7859
7860 m_MouseDragging = event.Dragging();
7861
7862 // Some systems produce null drag events, where the pointer position has not
7863 // changed from the previous value. Detect this case, and abort further
7864 // processing (FS#1748)
7865#ifdef __WXMSW__
7866 if (event.Dragging()) {
7867 if ((x == mouse_x) && (y == mouse_y)) return true;
7868 }
7869#endif
7870
7871 mouse_x = x;
7872 mouse_y = y;
7873 mouse_leftisdown = event.LeftDown();
7875
7876 // Establish the event region
7877 cursor_region = CENTER;
7878
7879 int chartbar_height = GetChartbarHeight();
7880
7881 if (m_Compass && m_Compass->IsShown() &&
7882 m_Compass->GetRect().Contains(event.GetPosition())) {
7883 cursor_region = CENTER;
7884 } else if (x > xr_margin) {
7885 cursor_region = MID_RIGHT;
7886 } else if (x < xl_margin) {
7887 cursor_region = MID_LEFT;
7888 } else if (y > yb_margin - chartbar_height &&
7889 y < m_canvas_height - chartbar_height) {
7890 cursor_region = MID_TOP;
7891 } else if (y < yt_margin) {
7892 cursor_region = MID_BOT;
7893 } else {
7894 cursor_region = CENTER;
7895 }
7896
7897 if (!g_btouch) SetCanvasCursor(event);
7898
7899 // Protect from leftUp's coming from event handlers in child
7900 // windows who return focus to the canvas.
7901 leftIsDown = event.LeftDown();
7902
7903#ifndef __WXOSX__
7904 if (event.LeftDown()) {
7905 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7906 // The menu bar is temporarily visible due to alt having been pressed.
7907 // Clicking will hide it, and do nothing else.
7908 g_bTempShowMenuBar = false;
7909 top_frame::Get()->ApplyGlobalSettings(false);
7910 return (true);
7911 }
7912 }
7913#endif
7914
7915 // Update modifiers here; some window managers never send the key event
7916 m_modkeys = 0;
7917 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7918 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7919
7920#ifdef __WXMSW__
7921 // TODO Test carefully in other platforms, remove ifdef....
7922 // Avoid capture release on delayed/replayed timer events.
7923 if (!m_isReplayingClickEvent) {
7924 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7925 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7926 }
7927#endif
7928
7929 event.SetEventObject(this);
7930 if (SendMouseEventToPlugins(event))
7931 return (true); // PlugIn did something, and does not want the canvas to
7932 // do anything else
7933
7934 // Capture LeftUp's and time them, unless it already came from the timer.
7935
7936 // Detect end of chart dragging
7937 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7938 StartChartDragInertia();
7939 }
7940
7941 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7942 !singleClickEventIsValid) {
7943 // Ignore the second LeftUp after the DClick.
7944 if (m_DoubleClickTimer->IsRunning()) {
7945 m_DoubleClickTimer->Stop();
7946 return (true);
7947 }
7948
7949 // Save the event for later running if there is no DClick.
7950 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7951 singleClickEvent = event;
7952 singleClickEventIsValid = true;
7953 return (true);
7954 }
7955
7956 // This logic is necessary on MSW to handle the case where
7957 // a context (right-click) menu is dismissed without action
7958 // by clicking on the chart surface.
7959 // We need to avoid an unintentional pan by eating some clicks...
7960#ifdef __WXMSW__
7961 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7962 if (g_click_stop > 0) {
7963 g_click_stop--;
7964 return (true);
7965 }
7966 }
7967#endif
7968
7969 // Kick off the Rotation control timer
7970 if (GetUpMode() == COURSE_UP_MODE) {
7971 m_b_rot_hidef = false;
7972 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7973 } else
7974 pRotDefTimer->Stop();
7975
7976 // Retrigger the route leg / AIS target popup timer
7977 bool bRoll = !g_btouch;
7978#ifdef __ANDROID__
7979 bRoll = g_bRollover;
7980#endif
7981 if (bRoll) {
7982 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7983 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7984 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7985 m_RolloverPopupTimer.Start(
7986 10,
7987 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7988 else
7989 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7990 }
7991
7992 // Retrigger the cursor tracking timer
7993 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7994
7995// Show cursor position on Status Bar, if present
7996// except for GTK, under which status bar updates are very slow
7997// due to Update() call.
7998// In this case, as a workaround, update the status window
7999// after an interval timer (pCurTrackTimer) pops, which will happen
8000// whenever the mouse has stopped moving for specified interval.
8001// See the method OnCursorTrackTimerEvent()
8002#if !defined(__WXGTK__) && !defined(__WXQT__)
8003 SetCursorStatus(m_cursor_lat, m_cursor_lon);
8004#endif
8005
8006 // Send the current cursor lat/lon to all PlugIns requesting it
8007 if (g_pi_manager) {
8008 // Occasionally, MSW will produce nonsense events on right click....
8009 // This results in an error in cursor geo position, so we skip this case
8010 if ((x >= 0) && (y >= 0))
8011 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
8012 }
8013
8014 if (!g_btouch) {
8015 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
8016 wxPoint p = ClientToScreen(wxPoint(x, y));
8017 }
8018 }
8019
8020 if (1 ) {
8021 // Route Creation Rubber Banding
8022 if (m_routeState >= 2) {
8023 r_rband.x = x;
8024 r_rband.y = y;
8025 m_bDrawingRoute = true;
8026
8027 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8028 Refresh(false);
8029 }
8030
8031 // Measure Tool Rubber Banding
8032 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
8033 r_rband.x = x;
8034 r_rband.y = y;
8035 m_bDrawingRoute = true;
8036
8037 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
8038 Refresh(false);
8039 }
8040 }
8041 return bret;
8042}
8043
8044int ChartCanvas::PrepareContextSelections(double lat, double lon) {
8045 // On general Right Click
8046 // Look for selectable objects
8047 double slat = lat;
8048 double slon = lon;
8049
8050#if defined(__WXMAC__) || defined(__ANDROID__)
8051 wxScreenDC sdc;
8052 ocpnDC dc(sdc);
8053#else
8054 wxClientDC cdc(GetParent());
8055 ocpnDC dc(cdc);
8056#endif
8057
8058 SelectItem *pFindAIS;
8059 SelectItem *pFindRP;
8060 SelectItem *pFindRouteSeg;
8061 SelectItem *pFindTrackSeg;
8062 SelectItem *pFindCurrent = NULL;
8063 SelectItem *pFindTide = NULL;
8064
8065 // Deselect any current objects
8066 if (m_pSelectedRoute) {
8067 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
8068 m_pSelectedRoute->DeSelectRoute();
8069#ifdef ocpnUSE_GL
8070 if (g_bopengl && m_glcc) {
8071 InvalidateGL();
8072 Update();
8073 } else
8074#endif
8075 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8076 }
8077
8078 if (m_pFoundRoutePoint) {
8079 m_pFoundRoutePoint->m_bPtIsSelected = false;
8080 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8081 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8082 }
8083
8086 if (g_btouch && m_pRoutePointEditTarget) {
8087 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8088 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8089 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8090 }
8091
8092 // Get all the selectable things at the cursor
8093 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8094 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8095 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8096 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8097 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8098
8099 if (m_bShowCurrent)
8100 pFindCurrent =
8101 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8102
8103 if (m_bShowTide) // look for tide stations
8104 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8105
8106 int seltype = 0;
8107
8108 // Try for AIS targets first
8109 if (pFindAIS) {
8110 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8111
8112 // Make sure the target data is available
8113 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8114 seltype |= SELTYPE_AISTARGET;
8115 }
8116
8117 // Now examine the various Route parts
8118
8119 m_pFoundRoutePoint = NULL;
8120 if (pFindRP) {
8121 RoutePoint *pFirstVizPoint = NULL;
8122 RoutePoint *pFoundActiveRoutePoint = NULL;
8123 RoutePoint *pFoundVizRoutePoint = NULL;
8124 Route *pSelectedActiveRoute = NULL;
8125 Route *pSelectedVizRoute = NULL;
8126
8127 // There is at least one routepoint, so get the whole list
8128 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8129 SelectableItemList SelList =
8130 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8131 for (SelectItem *pFindSel : SelList) {
8132 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8133
8134 // Get an array of all routes using this point
8135 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8136
8137 // Use route array (if any) to determine actual visibility for this point
8138 bool brp_viz = false;
8139 if (proute_array) {
8140 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8141 Route *pr = (Route *)proute_array->Item(ir);
8142 if (pr->IsVisible()) {
8143 brp_viz = true;
8144 break;
8145 }
8146 }
8147 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8148 // but still exists as a waypoint
8149 brp_viz = prp->IsVisible(); // so treat as isolated point
8150
8151 } else
8152 brp_viz = prp->IsVisible(); // isolated point
8153
8154 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8155
8156 // Use route array to choose the appropriate route
8157 // Give preference to any active route, otherwise select the first visible
8158 // route in the array for this point
8159 m_pSelectedRoute = NULL;
8160 if (proute_array) {
8161 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8162 Route *pr = (Route *)proute_array->Item(ir);
8163 if (pr->m_bRtIsActive) {
8164 pSelectedActiveRoute = pr;
8165 pFoundActiveRoutePoint = prp;
8166 break;
8167 }
8168 }
8169
8170 if (NULL == pSelectedVizRoute) {
8171 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8172 Route *pr = (Route *)proute_array->Item(ir);
8173 if (pr->IsVisible()) {
8174 pSelectedVizRoute = pr;
8175 pFoundVizRoutePoint = prp;
8176 break;
8177 }
8178 }
8179 }
8180
8181 delete proute_array;
8182 }
8183 }
8184
8185 // Now choose the "best" selections
8186 if (pFoundActiveRoutePoint) {
8187 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8188 m_pSelectedRoute = pSelectedActiveRoute;
8189 } else if (pFoundVizRoutePoint) {
8190 m_pFoundRoutePoint = pFoundVizRoutePoint;
8191 m_pSelectedRoute = pSelectedVizRoute;
8192 } else
8193 // default is first visible point in list
8194 m_pFoundRoutePoint = pFirstVizPoint;
8195
8196 if (m_pSelectedRoute) {
8197 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8198 } else if (m_pFoundRoutePoint) {
8199 seltype |= SELTYPE_MARKPOINT;
8200 }
8201
8202 // Highlight the selected point, to verify the proper right click selection
8203 if (m_pFoundRoutePoint) {
8204 m_pFoundRoutePoint->m_bPtIsSelected = true;
8205 wxRect wp_rect;
8206 RoutePointGui(*m_pFoundRoutePoint)
8207 .CalculateDCRect(m_dc_route, this, &wp_rect);
8208 RefreshRect(wp_rect, true);
8209 }
8210 }
8211
8212 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8213 // routes But call the popup handler with identifier appropriate to the type
8214 if (pFindRouteSeg) // there is at least one select item
8215 {
8216 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8217 SelectableItemList SelList =
8218 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8219
8220 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8221 {
8222 // Choose the first visible route containing segment in the list
8223 for (SelectItem *pFindSel : SelList) {
8224 Route *pr = (Route *)pFindSel->m_pData3;
8225 if (pr->IsVisible()) {
8226 m_pSelectedRoute = pr;
8227 break;
8228 }
8229 }
8230 }
8231
8232 if (m_pSelectedRoute) {
8233 if (NULL == m_pFoundRoutePoint)
8234 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8235
8236 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8237 if (m_pSelectedRoute->m_bRtIsSelected) {
8238#ifdef ocpnUSE_GL
8239 if (g_bopengl && m_glcc) {
8240 InvalidateGL();
8241 Update();
8242 } else
8243#endif
8244 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8245 }
8246 seltype |= SELTYPE_ROUTESEGMENT;
8247 }
8248 }
8249
8250 if (pFindTrackSeg) {
8251 m_pSelectedTrack = NULL;
8252 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8253 SelectableItemList SelList =
8254 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8255
8256 // Choose the first visible track containing segment in the list
8257 for (SelectItem *pFindSel : SelList) {
8258 Track *pt = (Track *)pFindSel->m_pData3;
8259 if (pt->IsVisible()) {
8260 m_pSelectedTrack = pt;
8261 break;
8262 }
8263 }
8264 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8265 }
8266
8267#if 0 // disable tide and current graph on right click
8268 {
8269 if (pFindCurrent) {
8270 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8271 seltype |= SELTYPE_CURRENTPOINT;
8272 }
8273
8274 else if (pFindTide) {
8275 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8276 seltype |= SELTYPE_TIDEPOINT;
8277 }
8278 }
8279#endif
8280
8281 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8282
8283 return seltype;
8284}
8285
8286IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8287 // There may be multiple current entries at the same point.
8288 // For example, there often is a current substation (with directions
8289 // specified) co-located with its master. We want to select the
8290 // substation, so that the direction will be properly indicated on the
8291 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8292 // substation)
8293 IDX_entry *pIDX_best_candidate;
8294
8295 SelectItem *pFind = NULL;
8296 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8297 SelectableItemList SelList =
8298 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8299
8300 // Default is first entry
8301 pFind = *SelList.begin();
8302 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8303
8304 auto node = SelList.begin();
8305 if (SelList.size() > 1) {
8306 for (++node; node != SelList.end(); ++node) {
8307 pFind = *node;
8308 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8309 if (pIDX_candidate->IDX_type == 'c') {
8310 pIDX_best_candidate = pIDX_candidate;
8311 break;
8312 }
8313 } // while (node)
8314 } else {
8315 pFind = *SelList.begin();
8316 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8317 }
8318
8319 return pIDX_best_candidate;
8320}
8321void ChartCanvas::CallPopupMenu(int x, int y) {
8322 last_drag.x = x;
8323 last_drag.y = y;
8324 if (m_routeState) { // creating route?
8325 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8326 return;
8327 }
8328
8330
8331 // If tide or current point is selected, then show the TC dialog immediately
8332 // without context menu
8333 if (SELTYPE_CURRENTPOINT == seltype) {
8334 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8335 Refresh(false);
8336 return;
8337 }
8338
8339 if (SELTYPE_TIDEPOINT == seltype) {
8340 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8341 Refresh(false);
8342 return;
8343 }
8344
8345 InvokeCanvasMenu(x, y, seltype);
8346
8347 // Clean up if not deleted in InvokeCanvasMenu
8348 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8349 m_pSelectedRoute->m_bRtIsSelected = false;
8350 }
8351
8352 m_pSelectedRoute = NULL;
8353
8354 if (m_pFoundRoutePoint) {
8355 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8356 m_pFoundRoutePoint->m_bPtIsSelected = false;
8357 }
8358 m_pFoundRoutePoint = NULL;
8359
8360 Refresh(true);
8361 // Refresh(false); // needed for MSW, not GTK Why??
8362}
8363
8364bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8365 // For now just bail out completely if the point clicked is not on the chart
8366 if (std::isnan(m_cursor_lat)) return false;
8367
8368 // Mouse Clicks
8369 bool ret = false; // return true if processed
8370
8371 int x, y, mx, my;
8372 event.GetPosition(&x, &y);
8373 mx = x;
8374 my = y;
8375
8376 // Calculate meaningful SelectRadius
8377 float SelectRadius;
8378 SelectRadius = g_Platform->GetSelectRadiusPix() /
8379 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8380
8382 // We start with Double Click processing. The first left click just starts a
8383 // timer and is remembered, then we actually do something if there is a
8384 // LeftDClick. If there is, the two single clicks are ignored.
8385
8386 if (event.LeftDClick() && (cursor_region == CENTER)) {
8387 m_DoubleClickTimer->Start();
8388 singleClickEventIsValid = false;
8389
8390 double zlat, zlon;
8392 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8393
8394 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8395 if (m_bShowAIS) {
8396 SelectItem *pFindAIS;
8397 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8398
8399 if (pFindAIS) {
8400 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8401 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8402 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8403 }
8404 return true;
8405 }
8406 }
8407
8408 SelectableItemList rpSelList =
8409 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8410 bool b_onRPtarget = false;
8411 for (SelectItem *pFind : rpSelList) {
8412 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8413 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8414 b_onRPtarget = true;
8415 break;
8416 }
8417 }
8418
8419 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8420
8421 // Get and honor the plugin API ContextMenuMask
8422 std::unique_ptr<HostApi> host_api = GetHostApi();
8423 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8424
8425 if (m_pRoutePointEditTarget) {
8426 if (b_onRPtarget) {
8427 if ((api_121->GetContextMenuMask() &
8428 api_121->kContextMenuDisableWaypoint))
8429 return true;
8430 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8431 return true;
8432 } else {
8433 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8434 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8435 if (g_btouch)
8436 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8437 wxRect wp_rect;
8438 RoutePointGui(*m_pRoutePointEditTarget)
8439 .CalculateDCRect(m_dc_route, this, &wp_rect);
8440 m_pRoutePointEditTarget = NULL; // cancel selection
8441 RefreshRect(wp_rect, true);
8442 return true;
8443 }
8444 } else {
8445 auto node = rpSelList.begin();
8446 if (node != rpSelList.end()) {
8447 SelectItem *pFind = *node;
8448 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8449 if (frp) {
8450 wxArrayPtrVoid *proute_array =
8452
8453 // Use route array (if any) to determine actual visibility for this
8454 // point
8455 bool brp_viz = false;
8456 if (proute_array) {
8457 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8458 Route *pr = (Route *)proute_array->Item(ir);
8459 if (pr->IsVisible()) {
8460 brp_viz = true;
8461 break;
8462 }
8463 }
8464 delete proute_array;
8465 if (!brp_viz &&
8466 frp->IsShared()) // is not visible as part of route, but
8467 // still exists as a waypoint
8468 brp_viz = frp->IsVisible(); // so treat as isolated point
8469 } else
8470 brp_viz = frp->IsVisible(); // isolated point
8471
8472 if (brp_viz) {
8473 if ((api_121->GetContextMenuMask() &
8474 api_121->kContextMenuDisableWaypoint))
8475 return true;
8476
8477 ShowMarkPropertiesDialog(frp);
8478 return true;
8479 }
8480 }
8481 }
8482 }
8483
8484 SelectItem *cursorItem;
8485
8486 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8487 if (cursorItem) {
8488 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8489 return true;
8490 Route *pr = (Route *)cursorItem->m_pData3;
8491 if (pr->IsVisible()) {
8492 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8493 return true;
8494 }
8495 }
8496
8497 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8498 if (cursorItem) {
8499 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8500 return true;
8501 Track *pt = (Track *)cursorItem->m_pData3;
8502 if (pt->IsVisible()) {
8503 ShowTrackPropertiesDialog(pt);
8504 return true;
8505 }
8506 }
8507
8508 // Tide and current points
8509 SelectItem *pFindCurrent = NULL;
8510 SelectItem *pFindTide = NULL;
8511
8512 if (m_bShowCurrent) { // look for current stations
8513 pFindCurrent =
8514 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8515 if (pFindCurrent) {
8516 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8517 // Check for plugin graphic override
8518 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8519 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8520 PlugInContainer *pic = plugin_array->Item(i);
8521 if (pic->m_enabled && pic->m_init_state &&
8522 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8523 if (ptcmgr) {
8524 TCClickInfo info;
8525 if (m_pIDXCandidate) {
8526 info.point_type = CURRENT_STATION;
8527 info.index = m_pIDXCandidate->IDX_rec_num;
8528 info.name = m_pIDXCandidate->IDX_station_name;
8529 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8530 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8531 return ptcmgr->GetTideOrCurrentMeters(t, idx, value, dir);
8532 };
8533 auto plugin =
8534 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8535 if (plugin) plugin->OnTideCurrentClick(info);
8536 return true;
8537 }
8538 }
8539 }
8540 }
8541
8542 // Default action
8543 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8544 Refresh(false);
8545 return true;
8546 }
8547 }
8548
8549 if (m_bShowTide) { // look for tide stations
8550 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8551 if (pFindTide) {
8552 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8553 // Check for plugin graphic override
8554 auto plugin_array = PluginLoader::GetInstance()->GetPlugInArray();
8555 for (unsigned int i = 0; i < plugin_array->GetCount(); i++) {
8556 PlugInContainer *pic = plugin_array->Item(i);
8557 if (pic->m_enabled && pic->m_init_state &&
8558 (pic->m_cap_flag & WANTS_TIDECURRENT_CLICK)) {
8559 if (ptcmgr) {
8560 TCClickInfo info;
8561 if (m_pIDXCandidate) {
8562 info.point_type = TIDE_STATION;
8563 info.index = m_pIDXCandidate->IDX_rec_num;
8564 info.name = m_pIDXCandidate->IDX_station_name;
8565 info.tz_offset_minutes = m_pIDXCandidate->station_tz_offset;
8566 info.getTide = [](time_t t, int idx, float &value, float &dir) {
8567 return ptcmgr->GetTideOrCurrent(t, idx, value, dir);
8568 };
8569 auto plugin =
8570 dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
8571 if (plugin) plugin->OnTideCurrentClick(info);
8572 return true;
8573 }
8574 }
8575 }
8576 }
8577
8578 // Default action
8579 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8580 Refresh(false);
8581 return true;
8582 }
8583 }
8584
8585 // Found no object to act on, so show chart info.
8586 ShowObjectQueryWindow(x, y, zlat, zlon);
8587 return true;
8588 }
8589
8591 if (event.LeftDown()) {
8592 // This really should not be needed, but....
8593 // on Windows, when using wxAUIManager, sometimes the focus is lost
8594 // when clicking into another pane, e.g.the AIS target list, and then back
8595 // to this pane. Oddly, some mouse events are not lost, however. Like this
8596 // one....
8597 SetFocus();
8598
8599 last_drag.x = mx;
8600 last_drag.y = my;
8601 leftIsDown = true;
8602
8603 if (!g_btouch) {
8604 if (m_routeState) // creating route?
8605 {
8606 double rlat, rlon;
8607 bool appending = false;
8608 bool inserting = false;
8609 Route *tail = 0;
8610
8611 SetCursor(*pCursorPencil);
8612 rlat = m_cursor_lat;
8613 rlon = m_cursor_lon;
8614
8615 m_bRouteEditing = true;
8616
8617 if (m_routeState == 1) {
8618 m_pMouseRoute = new Route();
8619 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8620 pRouteList->push_back(m_pMouseRoute);
8621 r_rband.x = x;
8622 r_rband.y = y;
8623 }
8624
8625 // Check to see if there is a nearby point which may be reused
8626 RoutePoint *pMousePoint = NULL;
8627
8628 // Calculate meaningful SelectRadius
8629 double nearby_radius_meters =
8630 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8631
8632 RoutePoint *pNearbyPoint =
8633 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8634 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8635 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8636 wxArrayPtrVoid *proute_array =
8637 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8638
8639 // Use route array (if any) to determine actual visibility for this
8640 // point
8641 bool brp_viz = false;
8642 if (proute_array) {
8643 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8644 Route *pr = (Route *)proute_array->Item(ir);
8645 if (pr->IsVisible()) {
8646 brp_viz = true;
8647 break;
8648 }
8649 }
8650 delete proute_array;
8651 if (!brp_viz &&
8652 pNearbyPoint->IsShared()) // is not visible as part of route,
8653 // but still exists as a waypoint
8654 brp_viz =
8655 pNearbyPoint->IsVisible(); // so treat as isolated point
8656 } else
8657 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8658
8659 if (brp_viz) {
8660 wxString msg = _("Use nearby waypoint?");
8661 // Don't add a mark without name to the route. Name it if needed
8662 const bool noname(pNearbyPoint->GetName() == "");
8663 if (noname) {
8664 msg =
8665 _("Use nearby nameless waypoint and name it M with"
8666 " a unique number?");
8667 }
8668 // Avoid route finish on focus change for message dialog
8669 m_FinishRouteOnKillFocus = false;
8670 int dlg_return =
8671 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8672 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8673 m_FinishRouteOnKillFocus = true;
8674 if (dlg_return == wxID_YES) {
8675 if (noname) {
8676 if (m_pMouseRoute) {
8677 int last_wp_num = m_pMouseRoute->GetnPoints();
8678 // AP-ECRMB will truncate to 6 characters
8679 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8680 wxString wp_name = wxString::Format(
8681 "M%002i-%s", last_wp_num + 1, guid_short);
8682 pNearbyPoint->SetName(wp_name);
8683 } else
8684 pNearbyPoint->SetName("WPXX");
8685 }
8686 pMousePoint = pNearbyPoint;
8687
8688 // Using existing waypoint, so nothing to delete for undo.
8689 if (m_routeState > 1)
8690 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8691 Undo_HasParent, NULL);
8692
8693 tail =
8694 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8695 bool procede = false;
8696 if (tail) {
8697 procede = true;
8698 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8699 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8700 procede = false;
8701 }
8702
8703 if (procede) {
8704 int dlg_return;
8705 m_FinishRouteOnKillFocus = false;
8706 if (m_routeState ==
8707 1) { // first point in new route, preceeding route to be
8708 // added? Not touch case
8709
8710 wxString dmsg =
8711 _("Insert first part of this route in the new route?");
8712 if (tail->GetIndexOf(pMousePoint) ==
8713 tail->GetnPoints()) // Starting on last point of another
8714 // route?
8715 dmsg = _("Insert this route in the new route?");
8716
8717 if (tail->GetIndexOf(pMousePoint) > 0) { // Anything to do?
8718 dlg_return = OCPNMessageBox(
8719 this, dmsg, _("OpenCPN Route Create"),
8720 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8721 m_FinishRouteOnKillFocus = true;
8722
8723 if (dlg_return == wxID_YES) {
8724 inserting = true; // part of the other route will be
8725 // preceeding the new route
8726 }
8727 }
8728 } else {
8729 wxString dmsg =
8730 _("Append last part of this route to the new route?");
8731 if (tail->GetIndexOf(pMousePoint) == 1)
8732 dmsg = _(
8733 "Append this route to the new route?"); // Picking the
8734 // first point
8735 // of another
8736 // route?
8737
8738 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8739 dlg_return = OCPNMessageBox(
8740 this, dmsg, _("OpenCPN Route Create"),
8741 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8742 m_FinishRouteOnKillFocus = true;
8743
8744 if (dlg_return == wxID_YES) {
8745 appending = true; // part of the other route will be
8746 // appended to the new route
8747 }
8748 }
8749 }
8750 }
8751
8752 // check all other routes to see if this point appears in any
8753 // other route If it appears in NO other route, then it should e
8754 // considered an isolated mark
8755 if (!FindRouteContainingWaypoint(pMousePoint))
8756 pMousePoint->SetShared(true);
8757 }
8758 }
8759 }
8760
8761 if (NULL == pMousePoint) { // need a new point
8762 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8763 "", wxEmptyString);
8764 pMousePoint->SetNameShown(false);
8765
8766 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8767
8768 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8769
8770 if (m_routeState > 1)
8771 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8772 Undo_IsOrphanded, NULL);
8773 }
8774
8775 if (m_pMouseRoute) {
8776 if (m_routeState == 1) {
8777 // First point in the new route.
8778 m_pMouseRoute->AddPoint(pMousePoint);
8779 } else {
8780 if (m_pMouseRoute->m_NextLegGreatCircle) {
8781 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8782 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8783 &rhumbBearing, &rhumbDist);
8784 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8785 rlat, &gcDist, &gcBearing, NULL);
8786 double gcDistNM = gcDist / 1852.0;
8787
8788 // Empirically found expression to get reasonable route segments.
8789 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8790 pow(rhumbDist - gcDistNM - 1, 0.5);
8791
8792 wxString msg;
8793 msg << _("For this leg the Great Circle route is ")
8794 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8795 << _(" shorter than rhumbline.\n\n")
8796 << _("Would you like include the Great Circle routing points "
8797 "for this leg?");
8798
8799 m_FinishRouteOnKillFocus = false;
8800 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8801 // does not fully capture mouse
8802
8803 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8804 wxYES_NO | wxNO_DEFAULT);
8805
8806 m_disable_edge_pan = false;
8807 m_FinishRouteOnKillFocus = true;
8808
8809 if (answer == wxID_YES) {
8810 RoutePoint *gcPoint;
8811 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8812 wxRealPoint gcCoord;
8813
8814 for (int i = 1; i <= segmentCount; i++) {
8815 double fraction = (double)i * (1.0 / (double)segmentCount);
8816 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8817 gcDist * fraction, gcBearing,
8818 &gcCoord.x, &gcCoord.y, NULL);
8819
8820 if (i < segmentCount) {
8821 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8822 wxEmptyString);
8823 gcPoint->SetNameShown(false);
8824 // pConfig->AddNewWayPoint(gcPoint, -1);
8825 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8826
8827 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8828 gcPoint);
8829 } else {
8830 gcPoint = pMousePoint; // Last point, previously exsisting!
8831 }
8832
8833 m_pMouseRoute->AddPoint(gcPoint);
8834 pSelect->AddSelectableRouteSegment(
8835 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8836 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8837 prevGcPoint = gcPoint;
8838 }
8839
8840 undo->CancelUndoableAction(true);
8841
8842 } else {
8843 m_pMouseRoute->AddPoint(pMousePoint);
8844 pSelect->AddSelectableRouteSegment(
8845 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8846 pMousePoint, m_pMouseRoute);
8847 undo->AfterUndoableAction(m_pMouseRoute);
8848 }
8849 } else {
8850 // Ordinary rhumblinesegment.
8851 m_pMouseRoute->AddPoint(pMousePoint);
8852 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8853 rlon, m_prev_pMousePoint,
8854 pMousePoint, m_pMouseRoute);
8855 undo->AfterUndoableAction(m_pMouseRoute);
8856 }
8857 }
8858 }
8859 m_prev_rlat = rlat;
8860 m_prev_rlon = rlon;
8861 m_prev_pMousePoint = pMousePoint;
8862 if (m_pMouseRoute)
8863 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8864
8865 m_routeState++;
8866
8867 if (appending ||
8868 inserting) { // Appending a route or making a new route
8869 int connect = tail->GetIndexOf(pMousePoint);
8870 if (connect == 0) {
8871 inserting = false; // there is nothing to insert
8872 appending = true; // so append
8873 }
8874 int length = tail->GetnPoints();
8875
8876 int i;
8877 int start, stop;
8878 if (appending) {
8879 start = connect + 1;
8880 stop = length;
8881 } else { // inserting
8882 start = 1;
8883 stop = connect + 1;
8884 // Remove the first and only point of the new route
8885 m_pMouseRoute->RemovePoint(m_pMouseRoute->GetLastPoint());
8886 }
8887 for (i = start; i <= stop; i++) {
8888 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8889 if (m_pMouseRoute)
8890 m_pMouseRoute->m_lastMousePointIndex =
8891 m_pMouseRoute->GetnPoints();
8892 m_routeState++;
8893 top_frame::Get()->RefreshAllCanvas();
8894 ret = true;
8895 }
8896 m_prev_rlat =
8897 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8898 m_prev_rlon =
8899 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8900 m_pMouseRoute->FinalizeForRendering();
8901 }
8902 top_frame::Get()->RefreshAllCanvas();
8903 ret = true;
8904 }
8905
8906 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8907 {
8908 SetCursor(*pCursorPencil);
8909
8910 if (!m_pMeasureRoute) {
8911 m_pMeasureRoute = new Route();
8912 pRouteList->push_back(m_pMeasureRoute);
8913 }
8914
8915 if (m_nMeasureState == 1) {
8916 r_rband.x = x;
8917 r_rband.y = y;
8918 }
8919
8920 RoutePoint *pMousePoint =
8921 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8922 wxEmptyString, wxEmptyString);
8923 pMousePoint->m_bShowName = false;
8924 pMousePoint->SetShowWaypointRangeRings(false);
8925
8926 m_pMeasureRoute->AddPoint(pMousePoint);
8927
8928 m_prev_rlat = m_cursor_lat;
8929 m_prev_rlon = m_cursor_lon;
8930 m_prev_pMousePoint = pMousePoint;
8931 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8932
8933 m_nMeasureState++;
8934 top_frame::Get()->RefreshAllCanvas();
8935 ret = true;
8936 }
8937
8938 else {
8939 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8940 }
8941 } // !g_btouch
8942 else { // g_btouch
8943 m_last_touch_down_pos = event.GetPosition();
8944
8945 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8946 // if near screen edge, pan with injection
8947 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8948 // return;
8949 // }
8950 }
8951 }
8952
8953 if (ret) return true;
8954 }
8955
8956 if (event.Dragging()) {
8957 // in touch screen mode ensure the finger/cursor is on the selected point's
8958 // radius to allow dragging
8959 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8960 if (g_btouch) {
8961 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8962 SelectItem *pFind = NULL;
8963 SelectableItemList SelList = pSelect->FindSelectionList(
8964 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8965 for (SelectItem *pFind : SelList) {
8966 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8967 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8968 }
8969 }
8970
8971 // Check for use of dragHandle
8972 if (m_pRoutePointEditTarget &&
8973 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8974 SelectItem *pFind = NULL;
8975 SelectableItemList SelList = pSelect->FindSelectionList(
8976 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8977 for (SelectItem *pFind : SelList) {
8978 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8979 if (m_pRoutePointEditTarget == frp) {
8980 m_bIsInRadius = true;
8981 break;
8982 }
8983 }
8984
8985 if (!m_dragoffsetSet) {
8986 RoutePointGui(*m_pRoutePointEditTarget)
8987 .PresetDragOffset(this, mouse_x, mouse_y);
8988 m_dragoffsetSet = true;
8989 }
8990 }
8991 }
8992
8993 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8994 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8995
8996 if (NULL == g_pMarkInfoDialog) {
8997 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8998 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8999 DraggingAllowed = false;
9000
9001 if (m_pRoutePointEditTarget &&
9002 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9003 DraggingAllowed = false;
9004
9005 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9006
9007 if (DraggingAllowed) {
9008 if (!undo->InUndoableAction()) {
9009 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9010 Undo_NeedsCopy, m_pFoundPoint);
9011 }
9012
9013 // Get the update rectangle for the union of the un-edited routes
9014 wxRect pre_rect;
9015
9016 if (!g_bopengl && m_pEditRouteArray) {
9017 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9018 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9019 // Need to validate route pointer
9020 // Route may be gone due to drgging close to ownship with
9021 // "Delete On Arrival" state set, as in the case of
9022 // navigating to an isolated waypoint on a temporary route
9023 if (g_pRouteMan->IsRouteValid(pr)) {
9024 wxRect route_rect;
9025 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9026 pre_rect.Union(route_rect);
9027 }
9028 }
9029 }
9030
9031 double new_cursor_lat = m_cursor_lat;
9032 double new_cursor_lon = m_cursor_lon;
9033
9034 if (CheckEdgePan(x, y, true, 5, 2))
9035 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
9036
9037 // update the point itself
9038 if (g_btouch) {
9039 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9040 // new_cursor_lat, new_cursor_lon);
9041 RoutePointGui(*m_pRoutePointEditTarget)
9042 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9043 // update the Drag Handle entry in the pSelect list
9044 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
9045 m_pRoutePointEditTarget,
9046 SELTYPE_DRAGHANDLE);
9047 m_pFoundPoint->m_slat =
9048 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9049 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9050 } else {
9051 m_pRoutePointEditTarget->m_lat =
9052 new_cursor_lat; // update the RoutePoint entry
9053 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
9054 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9055 m_pFoundPoint->m_slat =
9056 new_cursor_lat; // update the SelectList entry
9057 m_pFoundPoint->m_slon = new_cursor_lon;
9058 }
9059
9060 // Update the MarkProperties Dialog, if currently shown
9061 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9062 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9063 g_pMarkInfoDialog->UpdateProperties(true);
9064 }
9065
9066 if (g_bopengl) {
9067 // InvalidateGL();
9068 Refresh(false);
9069 } else {
9070 // Get the update rectangle for the edited route
9071 wxRect post_rect;
9072
9073 if (m_pEditRouteArray) {
9074 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9075 ir++) {
9076 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9077 if (g_pRouteMan->IsRouteValid(pr)) {
9078 wxRect route_rect;
9079 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9080 post_rect.Union(route_rect);
9081 }
9082 }
9083 }
9084
9085 // Invalidate the union region
9086 pre_rect.Union(post_rect);
9087 RefreshRect(pre_rect, false);
9088 }
9089 top_frame::Get()->RefreshCanvasOther(this);
9090 m_bRoutePoinDragging = true;
9091 }
9092 ret = true;
9093 } // if Route Editing
9094
9095 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
9096 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
9097
9098 if (NULL == g_pMarkInfoDialog) {
9099 if (g_bWayPointPreventDragging) DraggingAllowed = false;
9100 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9101 DraggingAllowed = false;
9102
9103 if (m_pRoutePointEditTarget &&
9104 (m_pRoutePointEditTarget->GetIconName() == "mob"))
9105 DraggingAllowed = false;
9106
9107 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
9108
9109 if (DraggingAllowed) {
9110 if (!undo->InUndoableAction()) {
9111 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
9112 Undo_NeedsCopy, m_pFoundPoint);
9113 }
9114
9115 // The mark may be an anchorwatch
9116 double lpp1 = 0.;
9117 double lpp2 = 0.;
9118 double lppmax;
9119
9120 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
9121 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
9122 }
9123 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9124 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9125 }
9126 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9127
9128 // Get the update rectangle for the un-edited mark
9129 wxRect pre_rect;
9130 if (!g_bopengl) {
9131 RoutePointGui(*m_pRoutePointEditTarget)
9132 .CalculateDCRect(m_dc_route, this, &pre_rect);
9133 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9134 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9135 (int)(lppmax - (pre_rect.height / 2)));
9136 }
9137
9138 // update the point itself
9139 if (g_btouch) {
9140 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9141 // m_cursor_lat, m_cursor_lon);
9142 RoutePointGui(*m_pRoutePointEditTarget)
9143 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9144 // update the Drag Handle entry in the pSelect list
9145 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9146 m_pRoutePointEditTarget,
9147 SELTYPE_DRAGHANDLE);
9148 m_pFoundPoint->m_slat =
9149 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9150 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9151 } else {
9152 m_pRoutePointEditTarget->m_lat =
9153 m_cursor_lat; // update the RoutePoint entry
9154 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9155 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9156 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9157 m_pFoundPoint->m_slon = m_cursor_lon;
9158 }
9159
9160 // Update the MarkProperties Dialog, if currently shown
9161 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9162 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9163 g_pMarkInfoDialog->UpdateProperties(true);
9164 }
9165
9166 // Invalidate the union region
9167 if (g_bopengl) {
9168 if (!g_btouch) InvalidateGL();
9169 Refresh(false);
9170 } else {
9171 // Get the update rectangle for the edited mark
9172 wxRect post_rect;
9173 RoutePointGui(*m_pRoutePointEditTarget)
9174 .CalculateDCRect(m_dc_route, this, &post_rect);
9175 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9176 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9177 (int)(lppmax - (post_rect.height / 2)));
9178
9179 // Invalidate the union region
9180 pre_rect.Union(post_rect);
9181 RefreshRect(pre_rect, false);
9182 }
9183 top_frame::Get()->RefreshCanvasOther(this);
9184 m_bRoutePoinDragging = true;
9185 }
9186 ret = g_btouch ? m_bRoutePoinDragging : true;
9187 }
9188
9189 if (ret) return true;
9190 } // dragging
9191
9192 if (event.LeftUp()) {
9193 bool b_startedit_route = false;
9194 m_dragoffsetSet = false;
9195
9196 if (g_btouch) {
9197 m_bChartDragging = false;
9198 m_bIsInRadius = false;
9199
9200 if (m_routeState) // creating route?
9201 {
9202 // Check displacement from touchdown to distinguish taps from pans.
9203 // On touchscreens, even a "tap" can have several pixels of jitter,
9204 // so use a generous threshold (20px) to avoid false positives.
9205 int dx = x - m_touchdownPos.x;
9206 int dy = y - m_touchdownPos.y;
9207 int dist2 = dx * dx + dy * dy;
9208 bool wasPan = (dist2 > 20 * 20);
9209
9210 if (wasPan) {
9211 return false;
9212 }
9213
9214 if (m_bedge_pan) {
9215 m_bedge_pan = false;
9216 return false;
9217 }
9218
9219 double rlat, rlon;
9220 bool appending = false;
9221 bool inserting = false;
9222 Route *tail = 0;
9223
9224 rlat = m_cursor_lat;
9225 rlon = m_cursor_lon;
9226
9227 if (m_pRoutePointEditTarget) {
9228 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9229 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9230 if (!g_bopengl) {
9231 wxRect wp_rect;
9232 RoutePointGui(*m_pRoutePointEditTarget)
9233 .CalculateDCRect(m_dc_route, this, &wp_rect);
9234 RefreshRect(wp_rect, true);
9235 }
9236 m_pRoutePointEditTarget = NULL;
9237 }
9238 m_bRouteEditing = true;
9239
9240 if (m_routeState == 1) {
9241 m_pMouseRoute = new Route();
9242 m_pMouseRoute->SetHiLite(50);
9243 pRouteList->push_back(m_pMouseRoute);
9244 r_rband.x = x;
9245 r_rband.y = y;
9246 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9247 }
9248
9249 // Check to see if there is a nearby point which may be reused
9250 RoutePoint *pMousePoint = NULL;
9251
9252 // Calculate meaningful SelectRadius
9253 double nearby_radius_meters =
9254 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9255
9256 RoutePoint *pNearbyPoint =
9257 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9258 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9259 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9260 int dlg_return;
9261#ifndef __WXOSX__
9262 m_FinishRouteOnKillFocus =
9263 false; // Avoid route finish on focus change for message dialog
9264 dlg_return = OCPNMessageBox(
9265 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9266 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9267 m_FinishRouteOnKillFocus = true;
9268#else
9269 dlg_return = wxID_YES;
9270#endif
9271 if (dlg_return == wxID_YES) {
9272 pMousePoint = pNearbyPoint;
9273
9274 // Using existing waypoint, so nothing to delete for undo.
9275 if (m_routeState > 1)
9276 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9277 Undo_HasParent, NULL);
9278 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9279
9280 bool procede = false;
9281 if (tail) {
9282 procede = true;
9283 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9284 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9285 procede = false;
9286 }
9287
9288 if (procede) {
9289 int dlg_return;
9290 m_FinishRouteOnKillFocus = false;
9291 if (m_routeState == 1) { // first point in new route, preceeding
9292 // route to be added? touch case
9293
9294 wxString dmsg =
9295 _("Insert first part of this route in the new route?");
9296 if (tail->GetIndexOf(pMousePoint) ==
9297 tail->GetnPoints()) // Starting on last point of another
9298 // route?
9299 dmsg = _("Insert this route in the new route?");
9300
9301 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9302 dlg_return =
9303 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9304 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9305 m_FinishRouteOnKillFocus = true;
9306
9307 if (dlg_return == wxID_YES) {
9308 inserting = true; // part of the other route will be
9309 // preceeding the new route
9310 }
9311 }
9312 } else {
9313 wxString dmsg =
9314 _("Append last part of this route to the new route?");
9315 if (tail->GetIndexOf(pMousePoint) == 1)
9316 dmsg = _(
9317 "Append this route to the new route?"); // Picking the
9318 // first point of
9319 // another route?
9320
9321 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9322 dlg_return =
9323 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9324 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9325 m_FinishRouteOnKillFocus = true;
9326
9327 if (dlg_return == wxID_YES) {
9328 appending = true; // part of the other route will be
9329 // appended to the new route
9330 }
9331 }
9332 }
9333 }
9334
9335 // check all other routes to see if this point appears in any other
9336 // route If it appears in NO other route, then it should e
9337 // considered an isolated mark
9338 if (!FindRouteContainingWaypoint(pMousePoint))
9339 pMousePoint->SetShared(true);
9340 }
9341 }
9342
9343 if (NULL == pMousePoint) { // need a new point
9344 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9345 "", wxEmptyString);
9346 pMousePoint->SetNameShown(false);
9347
9348 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9349
9350 if (m_routeState > 1)
9351 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9352 Undo_IsOrphanded, NULL);
9353 }
9354
9355 if (m_routeState == 1) {
9356 // First point in the route.
9357 m_pMouseRoute->AddPoint(pMousePoint);
9358 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9359
9360 } else {
9361 if (m_pMouseRoute->m_NextLegGreatCircle) {
9362 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9363 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9364 &rhumbBearing, &rhumbDist);
9365 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9366 &gcDist, &gcBearing, NULL);
9367 double gcDistNM = gcDist / 1852.0;
9368
9369 // Empirically found expression to get reasonable route segments.
9370 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9371 pow(rhumbDist - gcDistNM - 1, 0.5);
9372
9373 wxString msg;
9374 msg << _("For this leg the Great Circle route is ")
9375 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9376 << _(" shorter than rhumbline.\n\n")
9377 << _("Would you like include the Great Circle routing points "
9378 "for this leg?");
9379
9380#ifndef __WXOSX__
9381 m_FinishRouteOnKillFocus = false;
9382 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9383 wxYES_NO | wxNO_DEFAULT);
9384 m_FinishRouteOnKillFocus = true;
9385#else
9386 int answer = wxID_NO;
9387#endif
9388
9389 if (answer == wxID_YES) {
9390 RoutePoint *gcPoint;
9391 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9392 wxRealPoint gcCoord;
9393
9394 for (int i = 1; i <= segmentCount; i++) {
9395 double fraction = (double)i * (1.0 / (double)segmentCount);
9396 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9397 gcDist * fraction, gcBearing,
9398 &gcCoord.x, &gcCoord.y, NULL);
9399
9400 if (i < segmentCount) {
9401 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9402 wxEmptyString);
9403 gcPoint->SetNameShown(false);
9404 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9405 gcPoint);
9406 } else {
9407 gcPoint = pMousePoint; // Last point, previously exsisting!
9408 }
9409
9410 m_pMouseRoute->AddPoint(gcPoint);
9411 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9412
9413 pSelect->AddSelectableRouteSegment(
9414 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9415 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9416 prevGcPoint = gcPoint;
9417 }
9418
9419 undo->CancelUndoableAction(true);
9420
9421 } else {
9422 m_pMouseRoute->AddPoint(pMousePoint);
9423 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9424 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9425 rlon, m_prev_pMousePoint,
9426 pMousePoint, m_pMouseRoute);
9427 undo->AfterUndoableAction(m_pMouseRoute);
9428 }
9429 } else {
9430 // Ordinary rhumblinesegment.
9431 m_pMouseRoute->AddPoint(pMousePoint);
9432 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9433
9434 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9435 rlon, m_prev_pMousePoint,
9436 pMousePoint, m_pMouseRoute);
9437 undo->AfterUndoableAction(m_pMouseRoute);
9438 }
9439 }
9440
9441 m_prev_rlat = rlat;
9442 m_prev_rlon = rlon;
9443 m_prev_pMousePoint = pMousePoint;
9444 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9445
9446 m_routeState++;
9447
9448 if (appending ||
9449 inserting) { // Appending a route or making a new route
9450 int connect = tail->GetIndexOf(pMousePoint);
9451 if (connect == 1) {
9452 inserting = false; // there is nothing to insert
9453 appending = true; // so append
9454 }
9455 int length = tail->GetnPoints();
9456
9457 int i;
9458 int start, stop;
9459 if (appending) {
9460 start = connect + 1;
9461 stop = length;
9462 } else { // inserting
9463 start = 1;
9464 stop = connect;
9465 m_pMouseRoute->RemovePoint(
9466 m_pMouseRoute
9467 ->GetLastPoint()); // Remove the first and only point
9468 }
9469 for (i = start; i <= stop; i++) {
9470 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9471 if (m_pMouseRoute)
9472 m_pMouseRoute->m_lastMousePointIndex =
9473 m_pMouseRoute->GetnPoints();
9474 m_routeState++;
9475 top_frame::Get()->RefreshAllCanvas();
9476 ret = true;
9477 }
9478 m_prev_rlat =
9479 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9480 m_prev_rlon =
9481 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9482 m_pMouseRoute->FinalizeForRendering();
9483 }
9484
9485 Refresh(true);
9486 ret = true;
9487 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9488 {
9489 if (m_bedge_pan) {
9490 m_bedge_pan = false;
9491 return false;
9492 }
9493
9494 // Skip if user panned (same logic as route handler above)
9495 {
9496 int mdx = x - m_touchdownPos.x;
9497 int mdy = y - m_touchdownPos.y;
9498 if (mdx * mdx + mdy * mdy > 20 * 20) return false;
9499 }
9500
9501 if (m_nMeasureState == 1) {
9502 m_pMeasureRoute = new Route();
9503 pRouteList->push_back(m_pMeasureRoute);
9504 r_rband.x = x;
9505 r_rband.y = y;
9506 }
9507
9508 if (m_pMeasureRoute) {
9509 RoutePoint *pMousePoint =
9510 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9511 wxEmptyString, wxEmptyString);
9512 pMousePoint->m_bShowName = false;
9513
9514 m_pMeasureRoute->AddPoint(pMousePoint);
9515
9516 m_prev_rlat = m_cursor_lat;
9517 m_prev_rlon = m_cursor_lon;
9518 m_prev_pMousePoint = pMousePoint;
9519 m_pMeasureRoute->m_lastMousePointIndex =
9520 m_pMeasureRoute->GetnPoints();
9521
9522 m_nMeasureState++;
9523 } else {
9524 CancelMeasureRoute();
9525 }
9526
9527 Refresh(true);
9528 ret = true;
9529 } else {
9530 bool bSelectAllowed = true;
9531 if (NULL == g_pMarkInfoDialog) {
9532 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9533 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9534 bSelectAllowed = false;
9535
9536 // Avoid accidental selection of routepoint if last touchdown started
9537 // a significant chart drag operation
9538 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9539 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9540 significant_drag) ||
9541 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9542 significant_drag)) {
9543 bSelectAllowed = false;
9544 }
9545
9546 /*if this left up happens at the end of a route point dragging and if
9547 the cursor/thumb is on the draghandle icon, not on the point iself a new
9548 selection will select nothing and the drag will never be ended, so the
9549 legs around this point never selectable. At this step we don't need a
9550 new selection, just keep the previoulsly selected and dragged point */
9551 if (m_bRoutePoinDragging) bSelectAllowed = false;
9552
9553 if (bSelectAllowed) {
9554 bool b_was_editing_mark = m_bMarkEditing;
9555 bool b_was_editing_route = m_bRouteEditing;
9556 FindRoutePointsAtCursor(SelectRadius,
9557 true); // Possibly selecting a point in a
9558 // route for later dragging
9559
9560 /*route and a mark points in layer can't be dragged so should't be
9561 * selected and no draghandle icon*/
9562 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9563 m_pRoutePointEditTarget = NULL;
9564
9565 if (!b_was_editing_route) {
9566 if (m_pEditRouteArray) {
9567 b_startedit_route = true;
9568
9569 // Hide the track and route rollover during route point edit, not
9570 // needed, and may be confusing
9571 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9572 m_pTrackRolloverWin->IsActive(false);
9573 }
9574 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9575 m_pRouteRolloverWin->IsActive(false);
9576 }
9577
9578 wxRect pre_rect;
9579 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9580 ir++) {
9581 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9582 // Need to validate route pointer
9583 // Route may be gone due to drgging close to ownship with
9584 // "Delete On Arrival" state set, as in the case of
9585 // navigating to an isolated waypoint on a temporary route
9586 if (g_pRouteMan->IsRouteValid(pr)) {
9587 // pr->SetHiLite(50);
9588 wxRect route_rect;
9589 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9590 pre_rect.Union(route_rect);
9591 }
9592 }
9593 RefreshRect(pre_rect, true);
9594 }
9595 } else {
9596 b_startedit_route = false;
9597 }
9598
9599 // Mark editing in touch mode, left-up event.
9600 if (m_pRoutePointEditTarget) {
9601 if (b_was_editing_mark ||
9602 b_was_editing_route) { // kill previous hilight
9603 if (m_lastRoutePointEditTarget) {
9604 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9605 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9606 RoutePointGui(*m_lastRoutePointEditTarget)
9607 .EnableDragHandle(false);
9608 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9609 SELTYPE_DRAGHANDLE);
9610 }
9611 }
9612
9613 if (m_pRoutePointEditTarget) {
9614 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9615 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9616 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9617 wxPoint2DDouble dragHandlePoint =
9618 RoutePointGui(*m_pRoutePointEditTarget)
9619 .GetDragHandlePoint(this);
9620 pSelect->AddSelectablePoint(
9621 dragHandlePoint.m_y, dragHandlePoint.m_x,
9622 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9623 }
9624 } else { // Deselect everything
9625 if (m_lastRoutePointEditTarget) {
9626 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9627 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9628 RoutePointGui(*m_lastRoutePointEditTarget)
9629 .EnableDragHandle(false);
9630 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9631 SELTYPE_DRAGHANDLE);
9632
9633 // Clear any routes being edited, probably orphans
9634 wxArrayPtrVoid *lastEditRouteArray =
9636 m_lastRoutePointEditTarget);
9637 if (lastEditRouteArray) {
9638 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9639 ir++) {
9640 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9641 if (g_pRouteMan->IsRouteValid(pr)) {
9642 pr->m_bIsBeingEdited = false;
9643 }
9644 }
9645 delete lastEditRouteArray;
9646 }
9647 }
9648 }
9649
9650 // Do the refresh
9651
9652 if (g_bopengl) {
9653 InvalidateGL();
9654 Refresh(false);
9655 } else {
9656 if (m_lastRoutePointEditTarget) {
9657 wxRect wp_rect;
9658 RoutePointGui(*m_lastRoutePointEditTarget)
9659 .CalculateDCRect(m_dc_route, this, &wp_rect);
9660 RefreshRect(wp_rect, true);
9661 }
9662
9663 if (m_pRoutePointEditTarget) {
9664 wxRect wp_rect;
9665 RoutePointGui(*m_pRoutePointEditTarget)
9666 .CalculateDCRect(m_dc_route, this, &wp_rect);
9667 RefreshRect(wp_rect, true);
9668 }
9669 }
9670 }
9671 } // bSelectAllowed
9672
9673 // Check to see if there is a route or AIS target under the cursor
9674 // If so, start the rollover timer which creates the popup
9675 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9676 bool b_start_rollover = false;
9677 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9678 SelectItem *pFind = pSelectAIS->FindSelection(
9679 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9680 if (pFind) b_start_rollover = true;
9681 }
9682
9683 if (!b_start_rollover && !b_startedit_route) {
9684 SelectableItemList SelList = pSelect->FindSelectionList(
9685 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9686 for (SelectItem *pFindSel : SelList) {
9687 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9688 if (pr && pr->IsVisible()) {
9689 b_start_rollover = true;
9690 break;
9691 }
9692 } // while
9693 }
9694
9695 if (!b_start_rollover && !b_startedit_route) {
9696 SelectableItemList SelList = pSelect->FindSelectionList(
9697 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9698 for (SelectItem *pFindSel : SelList) {
9699 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9700 if (tr && tr->IsVisible()) {
9701 b_start_rollover = true;
9702 break;
9703 }
9704 } // while
9705 }
9706
9707 if (b_start_rollover)
9708 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9709 wxTIMER_ONE_SHOT);
9710 Route *tail = 0;
9711 Route *current = 0;
9712 bool appending = false;
9713 bool inserting = false;
9714 int connect = 0;
9715 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9716 // drag
9717 if (m_pRoutePointEditTarget) {
9718 // Check to see if there is a nearby point which may replace the
9719 // dragged one
9720 RoutePoint *pMousePoint = NULL;
9721
9722 int index_last;
9723 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9724 double nearby_radius_meters =
9725 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9726 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9727 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9728 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9729 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9730 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9731 bool duplicate =
9732 false; // ensure we won't create duplicate point in routes
9733 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9734 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9735 ir++) {
9736 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9737 if (pr && pr->pRoutePointList) {
9738 auto *list = pr->pRoutePointList;
9739 auto pos =
9740 std::find(list->begin(), list->end(), pNearbyPoint);
9741 if (pos != list->end()) {
9742 duplicate = true;
9743 break;
9744 }
9745 }
9746 }
9747 }
9748
9749 // Special case:
9750 // Allow "re-use" of a route's waypoints iff it is a simple
9751 // isolated route. This allows, for instance, creation of a closed
9752 // polygon route
9753 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9754
9755 if (!duplicate) {
9756 int dlg_return;
9757 dlg_return =
9758 OCPNMessageBox(this,
9759 _("Replace this RoutePoint by the nearby "
9760 "Waypoint?"),
9761 _("OpenCPN RoutePoint change"),
9762 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9763 if (dlg_return == wxID_YES) {
9764 /*double confirmation if the dragged point has been manually
9765 * created which can be important and could be deleted
9766 * unintentionally*/
9767
9768 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9769 pNearbyPoint);
9770 current =
9771 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9772
9773 if (tail && current && (tail != current)) {
9774 int dlg_return1;
9775 connect = tail->GetIndexOf(pNearbyPoint);
9776 int index_current_route =
9777 current->GetIndexOf(m_pRoutePointEditTarget);
9778 index_last = current->GetIndexOf(current->GetLastPoint());
9779 dlg_return1 = wxID_NO;
9780 if (index_last ==
9781 index_current_route) { // we are dragging the last
9782 // point of the route
9783 if (connect != tail->GetnPoints()) { // anything to do?
9784
9785 wxString dmsg(
9786 _("Last part of route to be appended to dragged "
9787 "route?"));
9788 if (connect == 1)
9789 dmsg =
9790 _("Full route to be appended to dragged route?");
9791
9792 dlg_return1 = OCPNMessageBox(
9793 this, dmsg, _("OpenCPN Route Create"),
9794 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9795 if (dlg_return1 == wxID_YES) {
9796 appending = true;
9797 }
9798 }
9799 } else if (index_current_route ==
9800 1) { // dragging the first point of the route
9801 if (connect != 1) { // anything to do?
9802
9803 wxString dmsg(
9804 _("First part of route to be inserted into dragged "
9805 "route?"));
9806 if (connect == tail->GetnPoints())
9807 dmsg = _(
9808 "Full route to be inserted into dragged route?");
9809
9810 dlg_return1 = OCPNMessageBox(
9811 this, dmsg, _("OpenCPN Route Create"),
9812 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9813 if (dlg_return1 == wxID_YES) {
9814 inserting = true;
9815 }
9816 }
9817 }
9818 }
9819
9820 if (m_pRoutePointEditTarget->IsShared()) {
9821 // dlg_return = wxID_NO;
9822 dlg_return = OCPNMessageBox(
9823 this,
9824 _("Do you really want to delete and replace this "
9825 "WayPoint") +
9826 "\n" + _("which has been created manually?"),
9827 ("OpenCPN RoutePoint warning"),
9828 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9829 }
9830 }
9831 if (dlg_return == wxID_YES) {
9832 pMousePoint = pNearbyPoint;
9833 if (pMousePoint->m_bIsolatedMark) {
9834 pMousePoint->SetShared(true);
9835 }
9836 pMousePoint->m_bIsolatedMark =
9837 false; // definitely no longer isolated
9838 pMousePoint->m_bIsInRoute = true;
9839 }
9840 }
9841 }
9842 }
9843 if (!pMousePoint)
9844 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9845
9846 if (m_pEditRouteArray) {
9847 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9848 ir++) {
9849 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9850 if (g_pRouteMan->IsRouteValid(pr)) {
9851 if (pMousePoint) { // remove the dragged point and insert the
9852 // nearby
9853 auto *list = pr->pRoutePointList;
9854 auto pos = std::find(list->begin(), list->end(),
9855 m_pRoutePointEditTarget);
9856
9857 pSelect->DeleteAllSelectableRoutePoints(pr);
9858 pSelect->DeleteAllSelectableRouteSegments(pr);
9859
9860 pr->pRoutePointList->insert(pos, pMousePoint);
9861 pos = std::find(list->begin(), list->end(),
9862 m_pRoutePointEditTarget);
9863 pr->pRoutePointList->erase(pos);
9864
9865 pSelect->AddAllSelectableRouteSegments(pr);
9866 pSelect->AddAllSelectableRoutePoints(pr);
9867 }
9868 pr->FinalizeForRendering();
9869 pr->UpdateSegmentDistances();
9870 if (m_bRoutePoinDragging) {
9871 // pConfig->UpdateRoute(pr);
9872 NavObj_dB::GetInstance().UpdateRoute(pr);
9873 }
9874 }
9875 }
9876 }
9877
9878 // Update the RouteProperties Dialog, if currently shown
9879 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9880 if (m_pEditRouteArray) {
9881 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9882 ir++) {
9883 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9884 if (g_pRouteMan->IsRouteValid(pr)) {
9885 if (pRoutePropDialog->GetRoute() == pr) {
9886 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9887 }
9888 /* cannot edit track points anyway
9889 else if ( ( NULL !=
9890 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9891 pTrackPropDialog->m_pTrack == pr ) {
9892 pTrackPropDialog->SetTrackAndUpdate(
9893 pr );
9894 }
9895 */
9896 }
9897 }
9898 }
9899 }
9900 if (pMousePoint) { // clear all about the dragged point
9901 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9902 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9903 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9904 // Hide mark properties dialog if open on the replaced point
9905 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9906 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9907 g_pMarkInfoDialog->Hide();
9908
9909 delete m_pRoutePointEditTarget;
9910 m_lastRoutePointEditTarget = NULL;
9911 m_pRoutePointEditTarget = NULL;
9912 undo->AfterUndoableAction(pMousePoint);
9913 undo->InvalidateUndo();
9914 }
9915 }
9916 }
9917
9918 else if (m_bMarkEditing) { // End of way point drag
9919 if (m_pRoutePointEditTarget)
9920 if (m_bRoutePoinDragging) {
9921 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9922 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9923 }
9924 }
9925
9926 if (m_pRoutePointEditTarget)
9927 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9928
9929 if (!m_pRoutePointEditTarget) {
9930 delete m_pEditRouteArray;
9931 m_pEditRouteArray = NULL;
9932 m_bRouteEditing = false;
9933 }
9934 m_bRoutePoinDragging = false;
9935
9936 if (appending) { // Appending to the route of which the last point is
9937 // dragged onto another route
9938
9939 // copy tail from connect until length to end of current after dragging
9940
9941 int length = tail->GetnPoints();
9942 for (int i = connect + 1; i <= length; i++) {
9943 current->AddPointAndSegment(tail->GetPoint(i), false);
9944 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9945 m_routeState++;
9946 top_frame::Get()->RefreshAllCanvas();
9947 ret = true;
9948 }
9949 current->FinalizeForRendering();
9950 current->m_bIsBeingEdited = false;
9951 FinishRoute();
9952 g_pRouteMan->DeleteRoute(tail);
9953 }
9954 if (inserting) {
9955 pSelect->DeleteAllSelectableRoutePoints(current);
9956 pSelect->DeleteAllSelectableRouteSegments(current);
9957 for (int i = 1; i < connect; i++) { // numbering in the tail route
9958 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9959 }
9960 pSelect->AddAllSelectableRouteSegments(current);
9961 pSelect->AddAllSelectableRoutePoints(current);
9962 current->FinalizeForRendering();
9963 current->m_bIsBeingEdited = false;
9964 g_pRouteMan->DeleteRoute(tail);
9965 }
9966
9967 // Update the RouteProperties Dialog, if currently shown
9968 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9969 if (m_pEditRouteArray) {
9970 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9971 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9972 if (g_pRouteMan->IsRouteValid(pr)) {
9973 if (pRoutePropDialog->GetRoute() == pr) {
9974 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9975 }
9976 }
9977 }
9978 }
9979 }
9980
9981 } // g_btouch
9982
9983 else { // !g_btouch
9984 if (m_bRouteEditing) { // End of RoutePoint drag
9985 Route *tail = 0;
9986 Route *current = 0;
9987 bool appending = false;
9988 bool inserting = false;
9989 int connect = 0;
9990 int index_last;
9991 if (m_pRoutePointEditTarget) {
9992 m_pRoutePointEditTarget->m_bBlink = false;
9993 // Check to see if there is a nearby point which may replace the
9994 // dragged one
9995 RoutePoint *pMousePoint = NULL;
9996 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9997 double nearby_radius_meters =
9998 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9999 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
10000 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
10001 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
10002 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
10003 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
10004 bool duplicate = false; // don't create duplicate point in routes
10005 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
10006 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10007 ir++) {
10008 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10009 if (pr && pr->pRoutePointList) {
10010 auto *list = pr->pRoutePointList;
10011 auto pos =
10012 std::find(list->begin(), list->end(), pNearbyPoint);
10013 if (pos != list->end()) {
10014 duplicate = true;
10015 break;
10016 }
10017 }
10018 }
10019 }
10020
10021 // Special case:
10022 // Allow "re-use" of a route's waypoints iff it is a simple
10023 // isolated route. This allows, for instance, creation of a closed
10024 // polygon route
10025 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
10026
10027 if (!duplicate) {
10028 int dlg_return;
10029 dlg_return =
10030 OCPNMessageBox(this,
10031 _("Replace this RoutePoint by the nearby "
10032 "Waypoint?"),
10033 _("OpenCPN RoutePoint change"),
10034 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10035 if (dlg_return == wxID_YES) {
10036 /*double confirmation if the dragged point has been manually
10037 * created which can be important and could be deleted
10038 * unintentionally*/
10039 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
10040 pNearbyPoint);
10041 current =
10042 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
10043
10044 if (tail && current && (tail != current)) {
10045 int dlg_return1;
10046 connect = tail->GetIndexOf(pNearbyPoint);
10047 int index_current_route =
10048 current->GetIndexOf(m_pRoutePointEditTarget);
10049 index_last = current->GetIndexOf(current->GetLastPoint());
10050 dlg_return1 = wxID_NO;
10051 if (index_last ==
10052 index_current_route) { // we are dragging the last
10053 // point of the route
10054 if (connect != tail->GetnPoints()) { // anything to do?
10055
10056 wxString dmsg(
10057 _("Last part of route to be appended to dragged "
10058 "route?"));
10059 if (connect == 1)
10060 dmsg =
10061 _("Full route to be appended to dragged route?");
10062
10063 dlg_return1 = OCPNMessageBox(
10064 this, dmsg, _("OpenCPN Route Create"),
10065 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10066 if (dlg_return1 == wxID_YES) {
10067 appending = true;
10068 }
10069 }
10070 } else if (index_current_route ==
10071 1) { // dragging the first point of the route
10072 if (connect != 1) { // anything to do?
10073
10074 wxString dmsg(
10075 _("First part of route to be inserted into dragged "
10076 "route?"));
10077 if (connect == tail->GetnPoints())
10078 dmsg = _(
10079 "Full route to be inserted into dragged route?");
10080
10081 dlg_return1 = OCPNMessageBox(
10082 this, dmsg, _("OpenCPN Route Create"),
10083 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10084 if (dlg_return1 == wxID_YES) {
10085 inserting = true;
10086 }
10087 }
10088 }
10089 }
10090
10091 if (m_pRoutePointEditTarget->IsShared()) {
10092 dlg_return = wxID_NO;
10093 dlg_return = OCPNMessageBox(
10094 this,
10095 _("Do you really want to delete and replace this "
10096 "WayPoint") +
10097 "\n" + _("which has been created manually?"),
10098 ("OpenCPN RoutePoint warning"),
10099 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10100 }
10101 }
10102 if (dlg_return == wxID_YES) {
10103 pMousePoint = pNearbyPoint;
10104 if (pMousePoint->m_bIsolatedMark) {
10105 pMousePoint->SetShared(true);
10106 }
10107 pMousePoint->m_bIsolatedMark =
10108 false; // definitely no longer isolated
10109 pMousePoint->m_bIsInRoute = true;
10110 }
10111 }
10112 }
10113 }
10114 if (!pMousePoint)
10115 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
10116
10117 if (m_pEditRouteArray) {
10118 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10119 ir++) {
10120 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10121 if (g_pRouteMan->IsRouteValid(pr)) {
10122 if (pMousePoint) { // replace dragged point by nearby one
10123 auto *list = pr->pRoutePointList;
10124 auto pos = std::find(list->begin(), list->end(),
10125 m_pRoutePointEditTarget);
10126
10127 pSelect->DeleteAllSelectableRoutePoints(pr);
10128 pSelect->DeleteAllSelectableRouteSegments(pr);
10129
10130 pr->pRoutePointList->insert(pos, pMousePoint);
10131 pos = std::find(list->begin(), list->end(),
10132 m_pRoutePointEditTarget);
10133 if (pos != list->end()) list->erase(pos);
10134 // pr->pRoutePointList->erase(pos + 1);
10135
10136 pSelect->AddAllSelectableRouteSegments(pr);
10137 pSelect->AddAllSelectableRoutePoints(pr);
10138 }
10139 pr->FinalizeForRendering();
10140 pr->UpdateSegmentDistances();
10141 pr->m_bIsBeingEdited = false;
10142
10143 if (m_bRoutePoinDragging) {
10144 // Special case optimization.
10145 // Dragging a single point of a route
10146 // without any point additions or re-ordering
10147 if (!pMousePoint)
10148 NavObj_dB::GetInstance().UpdateRoutePoint(
10149 m_pRoutePointEditTarget);
10150 else
10151 NavObj_dB::GetInstance().UpdateRoute(pr);
10152 }
10153 pr->SetHiLite(0);
10154 }
10155 }
10156 Refresh(false);
10157 }
10158
10159 if (appending) {
10160 // copy tail from connect until length to end of current after
10161 // dragging
10162
10163 int length = tail->GetnPoints();
10164 for (int i = connect + 1; i <= length; i++) {
10165 current->AddPointAndSegment(tail->GetPoint(i), false);
10166 if (current)
10167 current->m_lastMousePointIndex = current->GetnPoints();
10168 m_routeState++;
10169 top_frame::Get()->RefreshAllCanvas();
10170 ret = true;
10171 }
10172 current->FinalizeForRendering();
10173 current->m_bIsBeingEdited = false;
10174 FinishRoute();
10175 g_pRouteMan->DeleteRoute(tail);
10176 }
10177 if (inserting) {
10178 pSelect->DeleteAllSelectableRoutePoints(current);
10179 pSelect->DeleteAllSelectableRouteSegments(current);
10180 for (int i = 1; i < connect; i++) { // numbering in the tail route
10181 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10182 }
10183 pSelect->AddAllSelectableRouteSegments(current);
10184 pSelect->AddAllSelectableRoutePoints(current);
10185 current->FinalizeForRendering();
10186 current->m_bIsBeingEdited = false;
10187 g_pRouteMan->DeleteRoute(tail);
10188 }
10189
10190 // Update the RouteProperties Dialog, if currently shown
10191 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10192 if (m_pEditRouteArray) {
10193 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10194 ir++) {
10195 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10196 if (g_pRouteMan->IsRouteValid(pr)) {
10197 if (pRoutePropDialog->GetRoute() == pr) {
10198 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10199 }
10200 }
10201 }
10202 }
10203 }
10204
10205 if (pMousePoint) {
10206 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10207 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10208 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10209 // Hide mark properties dialog if open on the replaced point
10210 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10211 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10212 g_pMarkInfoDialog->Hide();
10213
10214 delete m_pRoutePointEditTarget;
10215 m_lastRoutePointEditTarget = NULL;
10216 undo->AfterUndoableAction(pMousePoint);
10217 undo->InvalidateUndo();
10218 } else {
10219 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10220 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10221
10222 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10223 }
10224
10225 delete m_pEditRouteArray;
10226 m_pEditRouteArray = NULL;
10227 }
10228
10229 InvalidateGL();
10230 m_bRouteEditing = false;
10231 m_pRoutePointEditTarget = NULL;
10232
10233 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10234 ret = true;
10235 }
10236
10237 else if (m_bMarkEditing) { // end of Waypoint drag
10238 if (m_pRoutePointEditTarget) {
10239 if (m_bRoutePoinDragging) {
10240 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10241 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10242 }
10243 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10244 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10245 if (!g_bopengl) {
10246 wxRect wp_rect;
10247 RoutePointGui(*m_pRoutePointEditTarget)
10248 .CalculateDCRect(m_dc_route, this, &wp_rect);
10249 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10250 RefreshRect(wp_rect, true);
10251 }
10252 }
10253 m_pRoutePointEditTarget = NULL;
10254 m_bMarkEditing = false;
10255 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10256 ret = true;
10257 }
10258
10259 else if (leftIsDown) { // left click for chart center
10260 leftIsDown = false;
10261 ret = false;
10262
10263 if (!g_btouch) {
10264 if (!m_bChartDragging && !m_bMeasure_Active) {
10265 } else {
10266 m_bChartDragging = false;
10267 }
10268 }
10269 }
10270 m_bRoutePoinDragging = false;
10271 } // !btouch
10272
10273 if (ret) return true;
10274 } // left up
10275
10276 if (event.RightDown()) {
10277 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10278 last_drag.x = mx;
10279 last_drag.y = my;
10280
10281 if (g_btouch) {
10282 // if( m_pRoutePointEditTarget )
10283 // return false;
10284 }
10285
10286 ret = true;
10287 m_FinishRouteOnKillFocus = false;
10288 CallPopupMenu(mx, my);
10289 m_FinishRouteOnKillFocus = true;
10290 } // Right down
10291
10292 return ret;
10293}
10294
10295bool panleftIsDown;
10296bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10297 // Skip all mouse processing if shift is held.
10298 // This allows plugins to implement shift+drag behaviors.
10299 if (event.ShiftDown()) {
10300 return false;
10301 }
10302 int x, y;
10303 event.GetPosition(&x, &y);
10304
10305 x *= m_displayScale;
10306 y *= m_displayScale;
10307
10308 // Check for wheel rotation
10309 // ideally, should be just longer than the time between
10310 // processing accumulated mouse events from the event queue
10311 // as would happen during screen redraws.
10312 int wheel_dir = event.GetWheelRotation();
10313
10314 if (wheel_dir) {
10315 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10316 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10317
10318 double factor = g_mouse_zoom_sensitivity;
10319 if (wheel_dir < 0) factor = 1 / factor;
10320
10321 if (g_bsmoothpanzoom) {
10322 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10323 if (wheel_dir == m_last_wheel_dir) {
10324 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10325 // m_zoom_target /= factor;
10326 } else
10327 StopMovement();
10328 } else {
10329 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10330 m_wheelstopwatch.Start(0);
10331 // m_zoom_target = VPoint.chart_scale / factor;
10332 }
10333 }
10334
10335 m_last_wheel_dir = wheel_dir;
10336
10337 ZoomCanvas(factor, true, false);
10338 }
10339
10340 if (event.LeftDown()) {
10341 // Skip the first left click if it will cause a canvas focus shift
10342 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10343 return false;
10344 }
10345
10346 last_drag.x = x, last_drag.y = y;
10347 m_touchdownPos = wxPoint(x, y);
10348 panleftIsDown = true;
10349 }
10350
10351 if (event.LeftUp()) {
10352 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10353 // seen here.
10354 panleftIsDown = false;
10355
10356 if (!g_btouch) {
10357 if (!m_bChartDragging && !m_bMeasure_Active) {
10358 switch (cursor_region) {
10359 case MID_RIGHT: {
10360 PanCanvas(100, 0);
10361 break;
10362 }
10363
10364 case MID_LEFT: {
10365 PanCanvas(-100, 0);
10366 break;
10367 }
10368
10369 case MID_TOP: {
10370 PanCanvas(0, 100);
10371 break;
10372 }
10373
10374 case MID_BOT: {
10375 PanCanvas(0, -100);
10376 break;
10377 }
10378
10379 case CENTER: {
10380 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10381 break;
10382 }
10383 }
10384 } else {
10385 m_bChartDragging = false;
10386 }
10387 }
10388 }
10389 }
10390
10391 if (event.Dragging() && event.LeftIsDown()) {
10392 /*
10393 * fixed dragging.
10394 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10395 * before the drag event. Hence, as there is no mouse down event, last_drag
10396 * is not reset before the drag. And that results in one single drag
10397 * session, meaning you cannot drag the map a few miles north, lift your
10398 * finger, and the go even further north. Instead, the map resets itself
10399 * always to the very first drag start (since there is not reset of
10400 * last_drag).
10401 *
10402 * Besides, should not left down and dragging be enough of a situation to
10403 * start a drag procedure?
10404 *
10405 * Anyways, guarded it to be active in touch situations only.
10406 */
10407 if (g_btouch && !m_inPinch) {
10408 struct timespec now;
10409 clock_gettime(CLOCK_MONOTONIC, &now);
10410 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10411
10412 bool trigger_hold = false;
10413 if (false == m_bChartDragging) {
10414 if (m_DragTrigger < 0) {
10415 // printf("\ntrigger1\n");
10416 m_DragTrigger = 0;
10417 m_DragTriggerStartTime = tnow;
10418 trigger_hold = true;
10419 } else {
10420 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10421 m_DragTrigger = -1; // Reset trigger
10422 // printf("trigger fired\n");
10423 }
10424 }
10425 }
10426 if (trigger_hold) return true;
10427
10428 if (false == m_bChartDragging) {
10429 // printf("starting drag\n");
10430 // Reset drag calculation members
10431 last_drag.x = x - 1, last_drag.y = y - 1;
10432 m_bChartDragging = true;
10433 m_chart_drag_total_time = 0;
10434 m_chart_drag_total_x = 0;
10435 m_chart_drag_total_y = 0;
10436 m_inertia_last_drag_x = x;
10437 m_inertia_last_drag_y = y;
10438 m_drag_vec_x.clear();
10439 m_drag_vec_y.clear();
10440 m_drag_vec_t.clear();
10441 m_last_drag_time = tnow;
10442 }
10443
10444 // Calculate and store drag dynamics.
10445 uint64_t delta_t = tnow - m_last_drag_time;
10446 double delta_tf = delta_t / 1e9;
10447
10448 m_chart_drag_total_time += delta_tf;
10449 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10450 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10451
10452 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10453 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10454 m_drag_vec_t.push_back(delta_tf);
10455
10456 m_inertia_last_drag_x = x;
10457 m_inertia_last_drag_y = y;
10458 m_last_drag_time = tnow;
10459
10460 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10461 m_bChartDragging = true;
10462 StartTimedMovement();
10463 m_pan_drag.x += last_drag.x - x;
10464 m_pan_drag.y += last_drag.y - y;
10465 last_drag.x = x, last_drag.y = y;
10466 }
10467 } else if (!g_btouch) {
10468 if ((last_drag.x != x) || (last_drag.y != y)) {
10469 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10470 // dragging on route create.
10471 // github #2994
10472 m_bChartDragging = true;
10473 StartTimedMovement();
10474 m_pan_drag.x += last_drag.x - x;
10475 m_pan_drag.y += last_drag.y - y;
10476 last_drag.x = x, last_drag.y = y;
10477 }
10478 }
10479 }
10480
10481 // Handle some special cases
10482 if (g_btouch) {
10483 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10484 // LeftUp handler uses displacement check to distinguish taps from pans.
10485 m_DoubleClickTimer->Start();
10486 singleClickEventIsValid = false;
10487 }
10488 }
10489 }
10490
10491 return true;
10492}
10493
10494void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10495 if (MouseEventOverlayWindows(event)) return;
10496
10497 if (MouseEventSetup(event)) return; // handled, no further action required
10498
10499 bool nm = MouseEventProcessObjects(event);
10500 if (!nm) MouseEventProcessCanvas(event);
10501}
10502
10503void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10504 // Switch to the appropriate cursor on mouse movement
10505
10506 wxCursor *ptarget_cursor = pCursorArrow;
10507 if (!pPlugIn_Cursor) {
10508 ptarget_cursor = pCursorArrow;
10509 if ((!m_routeState) &&
10510 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10511 if (cursor_region == MID_RIGHT) {
10512 ptarget_cursor = pCursorRight;
10513 } else if (cursor_region == MID_LEFT) {
10514 ptarget_cursor = pCursorLeft;
10515 } else if (cursor_region == MID_TOP) {
10516 ptarget_cursor = pCursorDown;
10517 } else if (cursor_region == MID_BOT) {
10518 ptarget_cursor = pCursorUp;
10519 } else {
10520 ptarget_cursor = pCursorArrow;
10521 }
10522 } else if (m_bMeasure_Active ||
10523 m_routeState) // If Measure tool use Pencil Cursor
10524 ptarget_cursor = pCursorPencil;
10525 } else {
10526 ptarget_cursor = pPlugIn_Cursor;
10527 }
10528
10529 SetCursor(*ptarget_cursor);
10530}
10531
10532void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10533 SetCursor(*pCursorArrow);
10534}
10535
10536void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10537 ChartPlugInWrapper *target_plugin_chart = NULL;
10538 s57chart *Chs57 = NULL;
10539 wxFileName file;
10540 wxArrayString files;
10541
10542 ChartBase *target_chart = GetChartAtCursor();
10543 if (target_chart) {
10544 file.Assign(target_chart->GetFullPath());
10545 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10546 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10547 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10548 else
10549 Chs57 = dynamic_cast<s57chart *>(target_chart);
10550 } else { // target_chart = null, might be mbtiles
10551 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10552 unsigned int im = stackIndexArray.size();
10553 int scale = 2147483647; // max 32b integer
10554 if (VPoint.b_quilt && im > 0) {
10555 for (unsigned int is = 0; is < im; is++) {
10556 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10557 CHART_TYPE_MBTILES) {
10558 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10559 double lat, lon;
10560 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10561 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10562 .GetBBox()
10563 .Contains(lat, lon)) {
10564 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10565 scale) {
10566 scale =
10567 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10568 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10569 }
10570 }
10571 }
10572 }
10573 }
10574 }
10575
10576 std::vector<Ais8_001_22 *> area_notices;
10577
10578 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10579 float vp_scale = GetVPScale();
10580
10581 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10582 auto target_data = target.second;
10583 if (!target_data->area_notices.empty()) {
10584 for (auto &ani : target_data->area_notices) {
10585 Ais8_001_22 &area_notice = ani.second;
10586
10587 BoundingBox bbox;
10588
10589 for (Ais8_001_22_SubAreaList::iterator sa =
10590 area_notice.sub_areas.begin();
10591 sa != area_notice.sub_areas.end(); ++sa) {
10592 switch (sa->shape) {
10593 case AIS8_001_22_SHAPE_CIRCLE: {
10594 wxPoint target_point;
10595 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10596 bbox.Expand(target_point);
10597 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10598 break;
10599 }
10600 case AIS8_001_22_SHAPE_RECT: {
10601 wxPoint target_point;
10602 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10603 bbox.Expand(target_point);
10604 if (sa->e_dim_m > sa->n_dim_m)
10605 bbox.EnLarge(sa->e_dim_m * vp_scale);
10606 else
10607 bbox.EnLarge(sa->n_dim_m * vp_scale);
10608 break;
10609 }
10610 case AIS8_001_22_SHAPE_POLYGON:
10611 case AIS8_001_22_SHAPE_POLYLINE: {
10612 for (int i = 0; i < 4; ++i) {
10613 double lat = sa->latitude;
10614 double lon = sa->longitude;
10615 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10616 &lat, &lon);
10617 wxPoint target_point;
10618 GetCanvasPointPix(lat, lon, &target_point);
10619 bbox.Expand(target_point);
10620 }
10621 break;
10622 }
10623 case AIS8_001_22_SHAPE_SECTOR: {
10624 double lat1 = sa->latitude;
10625 double lon1 = sa->longitude;
10626 double lat, lon;
10627 wxPoint target_point;
10628 GetCanvasPointPix(lat1, lon1, &target_point);
10629 bbox.Expand(target_point);
10630 for (int i = 0; i < 18; ++i) {
10631 ll_gc_ll(
10632 lat1, lon1,
10633 sa->left_bound_deg +
10634 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10635 sa->radius_m / 1852.0, &lat, &lon);
10636 GetCanvasPointPix(lat, lon, &target_point);
10637 bbox.Expand(target_point);
10638 }
10639 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10640 &lat, &lon);
10641 GetCanvasPointPix(lat, lon, &target_point);
10642 bbox.Expand(target_point);
10643 break;
10644 }
10645 }
10646 }
10647
10648 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10649 area_notices.push_back(&area_notice);
10650 }
10651 }
10652 }
10653 }
10654 }
10655
10656 if (target_chart || !area_notices.empty() || file.HasName()) {
10657 // Go get the array of all objects at the cursor lat/lon
10658 int sel_rad_pix = 5;
10659 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10660
10661 // Make sure we always get the lights from an object, even if we are
10662 // currently not displaying lights on the chart.
10663
10664 SetCursor(wxCURSOR_WAIT);
10665 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10666 if (!lightsVis) SetShowENCLights(true);
10667 ;
10668
10669 ListOfObjRazRules *rule_list = NULL;
10670 ListOfPI_S57Obj *pi_rule_list = NULL;
10671 if (Chs57)
10672 rule_list =
10673 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10674 else if (target_plugin_chart)
10675 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10676 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10677
10678 ListOfObjRazRules *overlay_rule_list = NULL;
10679 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10680 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10681
10682 if (CHs57_Overlay) {
10683 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10684 zlat, zlon, SelectRadius, &GetVP());
10685 }
10686
10687 if (!lightsVis) SetShowENCLights(false);
10688
10689 wxString objText;
10690 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10691 wxString face = dFont->GetFaceName();
10692
10693 if (NULL == g_pObjectQueryDialog) {
10695 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10696 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10697 }
10698
10699 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10700 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10701
10702#ifdef __WXOSX__
10703 // Auto Adjustment for dark mode
10704 fg = g_pObjectQueryDialog->GetForegroundColour();
10705#endif
10706
10707 objText.Printf(
10708 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10709 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10710
10711#ifdef __WXOSX__
10712 int points = dFont->GetPointSize();
10713#else
10714 int points = dFont->GetPointSize() + 1;
10715#endif
10716
10717 int sizes[7];
10718 for (int i = -2; i < 5; i++) {
10719 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10720 }
10721 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10722
10723 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10724
10725 if (overlay_rule_list && CHs57_Overlay) {
10726 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10727 objText << "<hr noshade>";
10728 }
10729
10730 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10731 an != area_notices.end(); ++an) {
10732 objText << "<b>AIS Area Notice:</b> ";
10733 objText << ais8_001_22_notice_names[(*an)->notice_type];
10734 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10735 (*an)->sub_areas.begin();
10736 sa != (*an)->sub_areas.end(); ++sa)
10737 if (!sa->text.empty()) objText << sa->text;
10738 objText << "<br>expires: " << (*an)->expiry_time.Format();
10739 objText << "<hr noshade>";
10740 }
10741
10742 if (Chs57)
10743 objText << Chs57->CreateObjDescriptions(rule_list);
10744 else if (target_plugin_chart)
10745 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10746 pi_rule_list);
10747
10748 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10749
10750 // Add the additional info files
10751 wxString AddFiles, filenameOK;
10752 int filecount = 0;
10753 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10754 // plugin
10755
10756 AddFiles = wxString::Format(
10757 "<hr noshade><br><b>Additional info files attached to: </b> "
10758 "<font "
10759 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10760 "cellpadding=3>",
10761 file.GetFullName());
10762 file.Normalize();
10763 file.Assign(file.GetPath(), "");
10764 wxDir dir(file.GetFullPath());
10765 wxString filename;
10766 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10767 while (cont) {
10768 file.Assign(dir.GetNameWithSep().append(filename));
10769 wxString FormatString =
10770 "<td valign=top><font size=-2><a "
10771 "href=\"%s\">%s</a></font></td>";
10772 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10773 filenameOK = file.GetFullPath(); // remember last valid name
10774 // we are making a 3 columns table. New row only every third file
10775 if (3 * ((int)filecount / 3) == filecount)
10776 FormatString.Prepend("<tr>"); // new row
10777 else
10778 FormatString.Prepend(
10779 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10780 // spacer column
10781
10782 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10783 file.GetFullName());
10784 filecount++;
10785 }
10786 cont = dir.GetNext(&filename);
10787 }
10788 objText << AddFiles << "</table>";
10789 }
10790 objText << "</font>";
10791 objText << "</body></html>";
10792
10793 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10794 g_pObjectQueryDialog->SetHTMLPage(objText);
10795 g_pObjectQueryDialog->Show();
10796 }
10797 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10798 // generate an event to avoid double code
10799 wxHtmlLinkInfo hli(filenameOK);
10800 wxHtmlLinkEvent hle(1, hli);
10801 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10802 }
10803
10804 if (rule_list) rule_list->Clear();
10805 delete rule_list;
10806
10807 if (overlay_rule_list) overlay_rule_list->Clear();
10808 delete overlay_rule_list;
10809
10810 if (pi_rule_list) pi_rule_list->Clear();
10811 delete pi_rule_list;
10812
10813 SetCursor(wxCURSOR_ARROW);
10814 }
10815}
10816
10817void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10818 bool bNew = false;
10819 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10820 // Dialog
10821 g_pMarkInfoDialog = new MarkInfoDlg(this);
10822 bNew = true;
10823 }
10824
10825 if (1 /*g_bresponsive*/) {
10826 wxSize canvas_size = GetSize();
10827
10828 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10829 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10830
10831 g_pMarkInfoDialog->Layout();
10832
10833 wxPoint canvas_pos = GetPosition();
10834 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10835
10836 bool newFit = false;
10837 if (canvas_size.x < fitted_size.x) {
10838 fitted_size.x = canvas_size.x - 40;
10839 if (canvas_size.y < fitted_size.y)
10840 fitted_size.y -= 40; // scrollbar added
10841 }
10842 if (canvas_size.y < fitted_size.y) {
10843 fitted_size.y = canvas_size.y - 40;
10844 if (canvas_size.x < fitted_size.x)
10845 fitted_size.x -= 40; // scrollbar added
10846 }
10847
10848 if (newFit) {
10849 g_pMarkInfoDialog->SetSize(fitted_size);
10850 g_pMarkInfoDialog->Centre();
10851 }
10852 }
10853
10854 markPoint->m_bRPIsBeingEdited = false;
10855
10856 wxString title_base = _("Mark Properties");
10857 if (markPoint->m_bIsInRoute) {
10858 title_base = _("Waypoint Properties");
10859 }
10860 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10861 g_pMarkInfoDialog->UpdateProperties();
10862 if (markPoint->m_bIsInLayer) {
10863 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10864 GetLayerName(markPoint->m_LayerID)));
10865 g_pMarkInfoDialog->SetDialogTitle(caption);
10866 } else
10867 g_pMarkInfoDialog->SetDialogTitle(title_base);
10868
10869 g_pMarkInfoDialog->Show();
10870 g_pMarkInfoDialog->Raise();
10871 g_pMarkInfoDialog->InitialFocus();
10872 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10873}
10874
10875void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10876 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10877 pRoutePropDialog->SetRouteAndUpdate(selected);
10878 // pNew->UpdateProperties();
10879 pRoutePropDialog->Show();
10880 pRoutePropDialog->Raise();
10881 return;
10882 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10883 this); // There is one global instance of the RouteProp Dialog
10884
10885 if (g_bresponsive) {
10886 wxSize canvas_size = GetSize();
10887 wxPoint canvas_pos = GetPosition();
10888 wxSize fitted_size = pRoutePropDialog->GetSize();
10889 ;
10890
10891 if (canvas_size.x < fitted_size.x) {
10892 fitted_size.x = canvas_size.x;
10893 if (canvas_size.y < fitted_size.y)
10894 fitted_size.y -= 20; // scrollbar added
10895 }
10896 if (canvas_size.y < fitted_size.y) {
10897 fitted_size.y = canvas_size.y;
10898 if (canvas_size.x < fitted_size.x)
10899 fitted_size.x -= 20; // scrollbar added
10900 }
10901
10902 pRoutePropDialog->SetSize(fitted_size);
10903 pRoutePropDialog->Centre();
10904
10905 // int xp = (canvas_size.x - fitted_size.x)/2;
10906 // int yp = (canvas_size.y - fitted_size.y)/2;
10907
10908 wxPoint xxp = ClientToScreen(canvas_pos);
10909 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10910 }
10911
10912 pRoutePropDialog->SetRouteAndUpdate(selected);
10913
10914 pRoutePropDialog->Show();
10915
10916 Refresh(false);
10917}
10918
10919void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10920 pTrackPropDialog = TrackPropDlg::getInstance(
10921 this); // There is one global instance of the RouteProp Dialog
10922
10923 pTrackPropDialog->SetTrackAndUpdate(selected);
10925
10926 pTrackPropDialog->Show();
10927
10928 Refresh(false);
10929}
10930
10931void pupHandler_PasteWaypoint() {
10932 Kml kml;
10933
10934 int pasteBuffer = kml.ParsePasteBuffer();
10935 RoutePoint *pasted = kml.GetParsedRoutePoint();
10936 if (!pasted) return;
10937
10938 double nearby_radius_meters =
10939 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10940
10941 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10942 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10943
10944 int answer = wxID_NO;
10945 if (nearPoint && !nearPoint->m_bIsInLayer) {
10946 wxString msg;
10947 msg << _(
10948 "There is an existing waypoint at the same location as the one you are "
10949 "pasting. Would you like to merge the pasted data with it?\n\n");
10950 msg << _("Answering 'No' will create a new waypoint at the same location.");
10951 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10952 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10953 }
10954
10955 if (answer == wxID_YES) {
10956 nearPoint->SetName(pasted->GetName());
10957 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10958 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10959 pRouteManagerDialog->UpdateWptListCtrl();
10960 }
10961
10962 if (answer == wxID_NO) {
10963 RoutePoint *newPoint = new RoutePoint(pasted);
10964 newPoint->m_bIsolatedMark = true;
10965 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10966 newPoint);
10967 // pConfig->AddNewWayPoint(newPoint, -1);
10968 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10969
10970 pWayPointMan->AddRoutePoint(newPoint);
10971 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10972 pRouteManagerDialog->UpdateWptListCtrl();
10973 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10974 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10975 }
10976
10977 top_frame::Get()->InvalidateAllGL();
10978 top_frame::Get()->RefreshAllCanvas(false);
10979}
10980
10981void pupHandler_PasteRoute() {
10982 Kml kml;
10983
10984 int pasteBuffer = kml.ParsePasteBuffer();
10985 Route *pasted = kml.GetParsedRoute();
10986 if (!pasted) return;
10987
10988 double nearby_radius_meters =
10989 g_Platform->GetSelectRadiusPix() / top_frame::Get()->GetCanvasTrueScale();
10990
10991 RoutePoint *curPoint;
10992 RoutePoint *nearPoint;
10993 RoutePoint *prevPoint = NULL;
10994
10995 bool mergepoints = false;
10996 bool createNewRoute = true;
10997 int existingWaypointCounter = 0;
10998
10999 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11000 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
11001 nearPoint = pWayPointMan->GetNearbyWaypoint(
11002 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11003 if (nearPoint) {
11004 mergepoints = true;
11005 existingWaypointCounter++;
11006 // Small hack here to avoid both extending RoutePoint and repeating all
11007 // the GetNearbyWaypoint calculations. Use existin data field in
11008 // RoutePoint as temporary storage.
11009 curPoint->m_bPtIsSelected = true;
11010 }
11011 }
11012
11013 int answer = wxID_NO;
11014 if (mergepoints) {
11015 wxString msg;
11016 msg << _(
11017 "There are existing waypoints at the same location as some of the ones "
11018 "you are pasting. Would you like to just merge the pasted data into "
11019 "them?\n\n");
11020 msg << _("Answering 'No' will create all new waypoints for this route.");
11021 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
11022 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
11023
11024 if (answer == wxID_CANCEL) {
11025 return;
11026 }
11027 }
11028
11029 // If all waypoints exist since before, and a route with the same name, we
11030 // don't create a new route.
11031 if (mergepoints && answer == wxID_YES &&
11032 existingWaypointCounter == pasted->GetnPoints()) {
11033 for (Route *proute : *pRouteList) {
11034 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
11035 createNewRoute = false;
11036 break;
11037 }
11038 }
11039 }
11040
11041 Route *newRoute = 0;
11042 RoutePoint *newPoint = 0;
11043
11044 if (createNewRoute) {
11045 newRoute = new Route();
11046 newRoute->m_RouteNameString = pasted->m_RouteNameString;
11047 }
11048
11049 for (int i = 1; i <= pasted->GetnPoints(); i++) {
11050 curPoint = pasted->GetPoint(i);
11051 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
11052 curPoint->m_bPtIsSelected = false;
11053 newPoint = pWayPointMan->GetNearbyWaypoint(
11054 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
11055 newPoint->SetName(curPoint->GetName());
11056 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
11057
11058 if (createNewRoute) newRoute->AddPoint(newPoint);
11059 } else {
11060 curPoint->m_bPtIsSelected = false;
11061
11062 newPoint = new RoutePoint(curPoint);
11063 newPoint->m_bIsolatedMark = false;
11064 newPoint->SetIconName("circle");
11065 newPoint->m_bIsVisible = true;
11066 newPoint->m_bShowName = false;
11067 newPoint->SetShared(false);
11068
11069 newRoute->AddPoint(newPoint);
11070 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
11071 newPoint);
11072 // pConfig->AddNewWayPoint(newPoint, -1);
11073 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
11074 pWayPointMan->AddRoutePoint(newPoint);
11075 }
11076 if (i > 1 && createNewRoute)
11077 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
11078 curPoint->m_lat, curPoint->m_lon,
11079 prevPoint, newPoint, newRoute);
11080 prevPoint = newPoint;
11081 }
11082
11083 if (createNewRoute) {
11084 pRouteList->push_back(newRoute);
11085 // pConfig->AddNewRoute(newRoute); // use auto next num
11086 NavObj_dB::GetInstance().InsertRoute(newRoute);
11087
11088 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
11089 pRoutePropDialog->SetRouteAndUpdate(newRoute);
11090 }
11091
11092 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
11093 pRouteManagerDialog->UpdateRouteListCtrl();
11094 pRouteManagerDialog->UpdateWptListCtrl();
11095 }
11096 top_frame::Get()->InvalidateAllGL();
11097 top_frame::Get()->RefreshAllCanvas(false);
11098 }
11099 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
11100 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
11101}
11102
11103void pupHandler_PasteTrack() {
11104 Kml kml;
11105
11106 int pasteBuffer = kml.ParsePasteBuffer();
11107 Track *pasted = kml.GetParsedTrack();
11108 if (!pasted) return;
11109
11110 TrackPoint *curPoint;
11111
11112 Track *newTrack = new Track();
11113 TrackPoint *newPoint;
11114 TrackPoint *prevPoint = NULL;
11115
11116 newTrack->SetName(pasted->GetName());
11117
11118 for (int i = 0; i < pasted->GetnPoints(); i++) {
11119 curPoint = pasted->GetPoint(i);
11120
11121 newPoint = new TrackPoint(curPoint);
11122
11123 wxDateTime now = wxDateTime::Now();
11124 newPoint->SetCreateTime(curPoint->GetCreateTime());
11125
11126 newTrack->AddPoint(newPoint);
11127
11128 if (prevPoint)
11129 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
11130 newPoint->m_lat, newPoint->m_lon,
11131 prevPoint, newPoint, newTrack);
11132
11133 prevPoint = newPoint;
11134 }
11135
11136 g_TrackList.push_back(newTrack);
11137 // pConfig->AddNewTrack(newTrack);
11138 NavObj_dB::GetInstance().InsertTrack(newTrack);
11139
11140 top_frame::Get()->InvalidateAllGL();
11141 top_frame::Get()->RefreshAllCanvas(false);
11142}
11143
11144bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11145 wxJSONValue v;
11146 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11147 v["CursorPosition_x"] = x;
11148 v["CursorPosition_y"] = y;
11149 // Send a limited set of selection types depending on what is
11150 // found under the mouse point.
11151 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11152 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11153 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11154
11155 wxJSONWriter w;
11156 wxString out;
11157 w.Write(v, out);
11158 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11159
11160 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11161
11162#if 0
11163#define SELTYPE_UNKNOWN 0x0001
11164#define SELTYPE_ROUTEPOINT 0x0002
11165#define SELTYPE_ROUTESEGMENT 0x0004
11166#define SELTYPE_TIDEPOINT 0x0008
11167#define SELTYPE_CURRENTPOINT 0x0010
11168#define SELTYPE_ROUTECREATE 0x0020
11169#define SELTYPE_AISTARGET 0x0040
11170#define SELTYPE_MARKPOINT 0x0080
11171#define SELTYPE_TRACKSEGMENT 0x0100
11172#define SELTYPE_DRAGHANDLE 0x0200
11173#endif
11174
11175 if (g_bhide_context_menus) return true;
11176 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11177 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11178 m_pIDXCandidate, m_nmea_log);
11179
11180 Connect(
11181 wxEVT_COMMAND_MENU_SELECTED,
11182 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11183
11184#ifdef __WXGTK__
11185 // Funny requirement here for gtk, to clear the menu trigger event
11186 // TODO
11187 // Causes a slight "flasH" of the menu,
11188 if (m_inLongPress) {
11189 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11190 m_inLongPress = false;
11191 }
11192#endif
11193
11194 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11195
11196 Disconnect(
11197 wxEVT_COMMAND_MENU_SELECTED,
11198 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11199
11200 delete m_canvasMenu;
11201 m_canvasMenu = NULL;
11202
11203#ifdef __WXQT__
11204 // gFrame->SurfaceToolbar();
11205 // g_MainToolbar->Raise();
11206#endif
11207
11208 return true;
11209}
11210
11211void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11212 // Pass menu events from the canvas to the menu handler
11213 // This is necessarily in ChartCanvas since that is the menu's parent.
11214 if (m_canvasMenu) {
11215 m_canvasMenu->PopupMenuHandler(event);
11216 }
11217 return;
11218}
11219
11220void ChartCanvas::StartRoute() {
11221 // Do not allow more than one canvas to create a route at one time.
11222 if (g_brouteCreating) return;
11223
11224 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11225
11226 g_brouteCreating = true;
11227 m_routeState = 1;
11228 m_bDrawingRoute = false;
11229 SetCursor(*pCursorPencil);
11230 // SetCanvasToolbarItemState(ID_ROUTE, true);
11231 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11232
11233 HideGlobalToolbar();
11234
11235#ifdef __ANDROID__
11236 androidSetRouteAnnunciator(true);
11237#endif
11238}
11239
11240wxString ChartCanvas::FinishRoute() {
11241 m_routeState = 0;
11242 m_prev_pMousePoint = NULL;
11243 m_bDrawingRoute = false;
11244 wxString rv = "";
11245 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11246
11247 // SetCanvasToolbarItemState(ID_ROUTE, false);
11248 top_frame::Get()->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11249#ifdef __ANDROID__
11250 androidSetRouteAnnunciator(false);
11251#endif
11252
11253 SetCursor(*pCursorArrow);
11254
11255 if (m_pMouseRoute) {
11256 if (m_bAppendingRoute) {
11257 // pConfig->UpdateRoute(m_pMouseRoute);
11258 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11259 } else {
11260 if (m_pMouseRoute->GetnPoints() > 1) {
11261 // pConfig->AddNewRoute(m_pMouseRoute);
11262 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11263 } else {
11264 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11265 m_pMouseRoute = NULL;
11266 }
11267 }
11268 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11269
11270 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11271 (pRoutePropDialog->IsShown())) {
11272 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11273 }
11274
11275 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11276 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11277 pRouteManagerDialog->UpdateRouteListCtrl();
11278 }
11279 }
11280 m_bAppendingRoute = false;
11281 m_pMouseRoute = NULL;
11282
11283 m_pSelectedRoute = NULL;
11284
11285 undo->InvalidateUndo();
11286 top_frame::Get()->RefreshAllCanvas(true);
11287
11288 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11289
11290 ShowGlobalToolbar();
11291
11292 g_brouteCreating = false;
11293
11294 return rv;
11295}
11296
11297void ChartCanvas::HideGlobalToolbar() {
11298 if (m_canvasIndex == 0) {
11299 m_last_TBviz = top_frame::Get()->SetGlobalToolbarViz(false);
11300 }
11301}
11302
11303void ChartCanvas::ShowGlobalToolbar() {
11304 if (m_canvasIndex == 0) {
11305 if (m_last_TBviz) top_frame::Get()->SetGlobalToolbarViz(true);
11306 }
11307}
11308
11309void ChartCanvas::ShowAISTargetList() {
11310 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11311 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11312 }
11313
11314 g_pAISTargetList->UpdateAISTargetList();
11315}
11316
11317void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11318 if (!m_bShowOutlines) return;
11319
11320 if (!ChartData) return;
11321
11322 int nEntry = ChartData->GetChartTableEntries();
11323
11324 for (int i = 0; i < nEntry; i++) {
11325 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11326
11327 // Check to see if the candidate chart is in the currently active group
11328 bool b_group_draw = false;
11329 if (m_groupIndex > 0) {
11330 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11331 int index = pt->GetGroupArray()[ig];
11332 if (m_groupIndex == index) {
11333 b_group_draw = true;
11334 break;
11335 }
11336 }
11337 } else
11338 b_group_draw = true;
11339
11340 if (b_group_draw) RenderChartOutline(dc, i, vp);
11341 }
11342
11343 // On CM93 Composite Charts, draw the outlines of the next smaller
11344 // scale cell
11345 cm93compchart *pcm93 = NULL;
11346 if (VPoint.b_quilt) {
11347 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11348 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11349 pcm93 = (cm93compchart *)pch;
11350 break;
11351 }
11352 } else if (m_singleChart &&
11353 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11354 pcm93 = (cm93compchart *)m_singleChart;
11355
11356 if (pcm93) {
11357 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11358 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11359
11360 if (zoom_factor > 8.0) {
11361 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11362 dc.SetPen(mPen);
11363 } else {
11364 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11365 dc.SetPen(mPen);
11366 }
11367
11368 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11369 }
11370}
11371
11372void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11373#ifdef ocpnUSE_GL
11374 if (g_bopengl && m_glcc) {
11375 /* opengl version specially optimized */
11376 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11377 return;
11378 }
11379#endif
11380
11381 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11382 if (!ChartData->IsChartAvailable(dbIndex)) return;
11383 }
11384
11385 float plylat, plylon;
11386 float plylat1, plylon1;
11387
11388 int pixx, pixy, pixx1, pixy1;
11389
11390 LLBBox box;
11391 ChartData->GetDBBoundingBox(dbIndex, box);
11392
11393 // Don't draw an outline in the case where the chart covers the entire world
11394 // */
11395 if (box.GetLonRange() == 360) return;
11396
11397 double lon_bias = 0;
11398 // chart is outside of viewport lat/lon bounding box
11399 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11400
11401 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11402
11403 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11404 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11405
11406 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11407 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11408
11409 else
11410 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11411
11412 // Are there any aux ply entries?
11413 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11414 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11415 {
11416 wxPoint r, r1;
11417
11418 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11419 plylon += lon_bias;
11420
11421 GetCanvasPointPix(plylat, plylon, &r);
11422 pixx = r.x;
11423 pixy = r.y;
11424
11425 for (int i = 0; i < nPly - 1; i++) {
11426 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11427 plylon1 += lon_bias;
11428
11429 GetCanvasPointPix(plylat1, plylon1, &r1);
11430 pixx1 = r1.x;
11431 pixy1 = r1.y;
11432
11433 int pixxs1 = pixx1;
11434 int pixys1 = pixy1;
11435
11436 bool b_skip = false;
11437
11438 if (vp.chart_scale > 5e7) {
11439 // calculate projected distance between these two points in meters
11440 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11441 pow((double)(pixy1 - pixy), 2)) /
11442 vp.view_scale_ppm;
11443
11444 if (dist > 0.0) {
11445 // calculate GC distance between these two points in meters
11446 double distgc =
11447 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11448
11449 // If the distances are nonsense, it means that the scale is very
11450 // small and the segment wrapped the world So skip it....
11451 // TODO improve this to draw two segments
11452 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11453 b_skip = true;
11454 } else
11455 b_skip = true;
11456 }
11457
11458 ClipResult res = cohen_sutherland_line_clip_i(
11459 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11460 if (res != Invisible && !b_skip)
11461 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11462
11463 plylat = plylat1;
11464 plylon = plylon1;
11465 pixx = pixxs1;
11466 pixy = pixys1;
11467 }
11468
11469 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11470 plylon1 += lon_bias;
11471
11472 GetCanvasPointPix(plylat1, plylon1, &r1);
11473 pixx1 = r1.x;
11474 pixy1 = r1.y;
11475
11476 ClipResult res = cohen_sutherland_line_clip_i(
11477 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11478 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11479 }
11480
11481 else // Use Aux PlyPoints
11482 {
11483 wxPoint r, r1;
11484
11485 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11486 for (int j = 0; j < nAuxPlyEntries; j++) {
11487 int nAuxPly =
11488 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11489 GetCanvasPointPix(plylat, plylon, &r);
11490 pixx = r.x;
11491 pixy = r.y;
11492
11493 for (int i = 0; i < nAuxPly - 1; i++) {
11494 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11495
11496 GetCanvasPointPix(plylat1, plylon1, &r1);
11497 pixx1 = r1.x;
11498 pixy1 = r1.y;
11499
11500 int pixxs1 = pixx1;
11501 int pixys1 = pixy1;
11502
11503 bool b_skip = false;
11504
11505 if (vp.chart_scale > 5e7) {
11506 // calculate projected distance between these two points in meters
11507 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11508 ((pixy1 - pixy) * (pixy1 - pixy))) /
11509 vp.view_scale_ppm;
11510 if (dist > 0.0) {
11511 // calculate GC distance between these two points in meters
11512 double distgc =
11513 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11514
11515 // If the distances are nonsense, it means that the scale is very
11516 // small and the segment wrapped the world So skip it....
11517 // TODO improve this to draw two segments
11518 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11519 b_skip = true;
11520 } else
11521 b_skip = true;
11522 }
11523
11524 ClipResult res = cohen_sutherland_line_clip_i(
11525 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11526 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11527
11528 plylat = plylat1;
11529 plylon = plylon1;
11530 pixx = pixxs1;
11531 pixy = pixys1;
11532 }
11533
11534 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11535 GetCanvasPointPix(plylat1, plylon1, &r1);
11536 pixx1 = r1.x;
11537 pixy1 = r1.y;
11538
11539 ClipResult res = cohen_sutherland_line_clip_i(
11540 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11541 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11542 }
11543 }
11544}
11545
11546static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11547 const wxArrayString &legend) {
11548 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11549
11550 int pointsize = dFont->GetPointSize();
11551 pointsize /= OCPN_GetWinDIPScaleFactor();
11552
11553 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11554 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11555 false, dFont->GetFaceName());
11556
11557 dc.SetFont(*psRLI_font);
11558
11559 int h = 0;
11560 int w = 0;
11561 int hl, wl;
11562
11563 int xp, yp;
11564 int hilite_offset = 3;
11565
11566 for (wxString line : legend) {
11567#ifdef __WXMAC__
11568 wxScreenDC sdc;
11569 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11570#else
11571 dc.GetTextExtent(line, &wl, &hl);
11572 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11573 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11574#endif
11575 h += hl;
11576 w = wxMax(w, wl);
11577 }
11578 w += (hl / 2); // Add a little right pad
11579
11580 xp = ref_point.x - w;
11581 yp = ref_point.y;
11582 yp += hilite_offset;
11583
11584 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11585
11586 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11587 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11588
11589 for (wxString line : legend) {
11590 dc.DrawText(line, xp, yp);
11591 yp += hl;
11592 }
11593}
11594
11595void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11596 if (!g_bAllowShipToActive) return;
11597
11598 Route *rt = g_pRouteMan->GetpActiveRoute();
11599 if (!rt) return;
11600
11601 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11602 wxPoint2DDouble pa, pb;
11604 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11605
11606 // set pen
11607 int width =
11608 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11609 if (rt->m_width != wxPENSTYLE_INVALID)
11610 width = rt->m_width; // set route pen style if any
11611 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11612 g_shipToActiveStyle, 5)]; // get setting pen style
11613 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11614 wxColour color =
11615 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11616 : // set setting route pen color
11617 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11618 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11619
11620 dc.SetPen(*mypen);
11621 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11622
11623 if (!Use_Opengl)
11624 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11625 (int)pb.m_y, GetVP(), true);
11626
11627#ifdef ocpnUSE_GL
11628 else {
11629#ifdef USE_ANDROID_GLES2
11630 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11631#else
11632 if (style != wxPENSTYLE_SOLID) {
11633 if (glChartCanvas::dash_map.find(style) !=
11634 glChartCanvas::dash_map.end()) {
11635 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11636 dc.SetPen(*mypen);
11637 }
11638 }
11639 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11640#endif
11641
11642 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11643 (int)pb.m_x, (int)pb.m_y, GetVP());
11644 }
11645#endif
11646 }
11647}
11648
11649void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11650 Route *route = 0;
11651 if (m_routeState >= 2) route = m_pMouseRoute;
11652 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11653 route = m_pMeasureRoute;
11654
11655 if (!route) return;
11656
11657 // Validate route pointer
11658 if (!g_pRouteMan->IsRouteValid(route)) return;
11659
11660 double render_lat = m_cursor_lat;
11661 double render_lon = m_cursor_lon;
11662
11663 int np = route->GetnPoints();
11664 if (np) {
11665 if (g_btouch && (np > 1)) np--;
11666 RoutePoint rp = route->GetPoint(np);
11667 render_lat = rp.m_lat;
11668 render_lon = rp.m_lon;
11669 }
11670
11671 double rhumbBearing, rhumbDist;
11672 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11673 &rhumbBearing, &rhumbDist);
11674 double brg = rhumbBearing;
11675 double dist = rhumbDist;
11676
11677 // Skip GreatCircle rubberbanding on touch devices.
11678 if (!g_btouch) {
11679 double gcBearing, gcBearing2, gcDist;
11680 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11681 m_cursor_lat, &gcDist, &gcBearing,
11682 &gcBearing2);
11683 double gcDistm = gcDist / 1852.0;
11684
11685 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11686 rhumbBearing = 90.;
11687
11688 wxPoint destPoint, lastPoint;
11689
11690 route->m_NextLegGreatCircle = false;
11691 int milesDiff = rhumbDist - gcDistm;
11692 if (milesDiff > 1) {
11693 brg = gcBearing;
11694 dist = gcDistm;
11695 route->m_NextLegGreatCircle = true;
11696 }
11697
11698 // FIXME (MacOS, the first segment is rendered wrong)
11699 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11700 &lastPoint);
11701
11702 if (route->m_NextLegGreatCircle) {
11703 for (int i = 1; i <= milesDiff; i++) {
11704 double p = (double)i * (1.0 / (double)milesDiff);
11705 double pLat, pLon;
11706 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11707 &pLon, &pLat, &gcBearing2);
11708 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11709 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11710 false);
11711 lastPoint = destPoint;
11712 }
11713 } else {
11714 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11715 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11716 false);
11717 if (m_bMeasure_DistCircle) {
11718 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11719 powf((float)(r_rband.y - lastPoint.y), 2));
11720
11721 dc.SetPen(*g_pRouteMan->GetRoutePen());
11722 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11723 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11724 }
11725 }
11726 }
11727 }
11728
11729 wxString routeInfo;
11730 wxArrayString infoArray;
11731 double varBrg = 0;
11732 if (g_bShowTrue)
11733 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11734 0x00B0);
11735
11736 if (g_bShowMag) {
11737 double latAverage = (m_cursor_lat + render_lat) / 2;
11738 double lonAverage = (m_cursor_lon + render_lon) / 2;
11739 varBrg = top_frame::Get()->GetMag(brg, latAverage, lonAverage);
11740
11741 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11742 (int)varBrg, 0x00B0);
11743 }
11744 routeInfo << " " << FormatDistanceAdaptive(dist);
11745 infoArray.Add(routeInfo);
11746 routeInfo.Clear();
11747
11748 // To make it easier to use a route as a bearing on a charted object add for
11749 // the first leg also the reverse bearing.
11750 if (np == 1) {
11751 routeInfo << "Reverse: ";
11752 if (g_bShowTrue)
11753 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11754 (int)(brg + 180.) % 360, 0x00B0);
11755 if (g_bShowMag)
11756 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11757 (int)(varBrg + 180.) % 360, 0x00B0);
11758 infoArray.Add(routeInfo);
11759 routeInfo.Clear();
11760 }
11761
11762 wxString s0;
11763 if (!route->m_bIsInLayer)
11764 s0.Append(_("Route") + ": ");
11765 else
11766 s0.Append(_("Layer Route: "));
11767
11768 double disp_length = route->m_route_length;
11769 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11770 s0 += FormatDistanceAdaptive(disp_length);
11771
11772 infoArray.Add(s0);
11773 routeInfo.Clear();
11774
11775 RouteLegInfo(dc, r_rband, infoArray);
11776
11777 m_brepaint_piano = true;
11778}
11779
11780void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11781 if (!m_bShowVisibleSectors) return;
11782
11783 if (g_bDeferredInitDone) {
11784 // need to re-evaluate sectors?
11785 double rhumbBearing, rhumbDist;
11786 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11787 &rhumbBearing, &rhumbDist);
11788
11789 if (rhumbDist > 0.05) // miles
11790 {
11791 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11792 m_sectorlegsVisible);
11793 m_sector_glat = gLat;
11794 m_sector_glon = gLon;
11795 }
11796 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11797 }
11798}
11799
11800void ChartCanvas::WarpPointerDeferred(int x, int y) {
11801 warp_x = x;
11802 warp_y = y;
11803 warp_flag = true;
11804}
11805
11806int s_msg;
11807
11808void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11809 if (!ps52plib) return;
11810
11811 if (VPoint.b_quilt) { // quilted
11812 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11813
11814 if (m_pQuilt->IsQuiltVector()) {
11815 if (ps52plib->GetStateHash() != m_s52StateHash) {
11816 UpdateS52State();
11817 m_s52StateHash = ps52plib->GetStateHash();
11818 }
11819 }
11820 } else {
11821 if (ps52plib->GetStateHash() != m_s52StateHash) {
11822 UpdateS52State();
11823 m_s52StateHash = ps52plib->GetStateHash();
11824 }
11825 }
11826
11827 // Plugin charts
11828 bool bSendPlibState = true;
11829 if (VPoint.b_quilt) { // quilted
11830 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11831 }
11832
11833 if (bSendPlibState) {
11834 wxJSONValue v;
11835 v["OpenCPN Version Major"] = VERSION_MAJOR;
11836 v["OpenCPN Version Minor"] = VERSION_MINOR;
11837 v["OpenCPN Version Patch"] = VERSION_PATCH;
11838 v["OpenCPN Version Date"] = VERSION_DATE;
11839 v["OpenCPN Version Full"] = VERSION_FULL;
11840
11841 // S52PLIB state
11842 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11843 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11844 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11845 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11846 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11847 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11848 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11849
11850 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11851
11852 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11853 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11854
11855 // Global S52 options
11856
11857 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11858 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11859 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11860 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11861 ps52plib->m_bShowS57ImportantTextOnly;
11862 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11863 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11864 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11865 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11866 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11867
11868 // Some global GUI parameters, for completeness
11869 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11870 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11871 v["OpenCPN Scale Factor Exp"] =
11872 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11873 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11874
11875 wxJSONWriter w;
11876 wxString out;
11877 w.Write(v, out);
11878
11879 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11880 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11881 g_lastS52PLIBPluginMessage = out;
11882 }
11883 }
11884}
11885int spaint;
11886int s_in_update;
11887void ChartCanvas::OnPaint(wxPaintEvent &event) {
11888 wxPaintDC dc(this);
11889
11890 // GetToolbar()->Show( m_bToolbarEnable );
11891
11892 // Paint updates may have been externally disabled (temporarily, to avoid
11893 // Yield() recursion performance loss) It is important that the wxPaintDC is
11894 // built, even if we elect to not process this paint message. Otherwise, the
11895 // paint message may not be removed from the message queue, esp on Windows.
11896 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11897
11898 if (!m_b_paint_enable) {
11899 return;
11900 }
11901
11902 // If necessary, reconfigure the S52 PLIB
11904
11905#ifdef ocpnUSE_GL
11906 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11907
11908 if (m_glcc && g_bopengl) {
11909 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11910 s_in_update++;
11911 m_glcc->Update();
11912 s_in_update--;
11913 }
11914
11915 return;
11916 }
11917#endif
11918
11919 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11920
11921 wxRegion ru = GetUpdateRegion();
11922
11923 int rx, ry, rwidth, rheight;
11924 ru.GetBox(rx, ry, rwidth, rheight);
11925
11926#ifdef ocpnUSE_DIBSECTION
11927 ocpnMemDC temp_dc;
11928#else
11929 wxMemoryDC temp_dc;
11930#endif
11931
11932 long height = GetVP().pix_height;
11933
11934#ifdef __WXMAC__
11935 // On OS X we have to explicitly extend the region for the piano area
11936 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11937 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11938 height += m_Piano->GetHeight();
11939#endif // __WXMAC__
11940 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11941
11942 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11943 if (pthumbwin) {
11944 int thumbx, thumby, thumbsx, thumbsy;
11945 pthumbwin->GetPosition(&thumbx, &thumby);
11946 pthumbwin->GetSize(&thumbsx, &thumbsy);
11947 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11948
11949 if (pthumbwin->IsShown()) {
11950 rgn_chart.Subtract(rgn_thumbwin);
11951 ru.Subtract(rgn_thumbwin);
11952 }
11953 }
11954
11955 // subtract the chart bar if it isn't transparent, and determine if we need to
11956 // paint it
11957 wxRegion rgn_blit = ru;
11958 if (g_bShowChartBar) {
11959 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11960 GetClientSize().x, m_Piano->GetHeight());
11961
11962 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11963 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11964 if (style->chartStatusWindowTransparent)
11965 m_brepaint_piano = true;
11966 else
11967 ru.Subtract(chart_bar_rect);
11968 }
11969 }
11970
11971 if (m_Compass && m_Compass->IsShown()) {
11972 wxRect compassRect = m_Compass->GetRect();
11973 if (ru.Contains(compassRect) != wxOutRegion) {
11974 ru.Subtract(compassRect);
11975 }
11976 }
11977
11978 if (m_notification_button) {
11979 wxRect noteRect = m_notification_button->GetRect();
11980 if (ru.Contains(noteRect) != wxOutRegion) {
11981 ru.Subtract(noteRect);
11982 }
11983 }
11984
11985 // Is this viewpoint the same as the previously painted one?
11986 bool b_newview = true;
11987
11988 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11989 (m_cache_vp.rotation == VPoint.rotation) &&
11990 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11991 m_cache_vp.IsValid()) {
11992 b_newview = false;
11993 }
11994
11995 // If the ViewPort is skewed or rotated, we may be able to use the cached
11996 // rotated bitmap.
11997 bool b_rcache_ok = false;
11998 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11999 b_rcache_ok = !b_newview;
12000
12001 // Make a special VP
12002 if (VPoint.b_MercatorProjectionOverride)
12003 VPoint.SetProjectionType(PROJECTION_MERCATOR);
12004 ViewPort svp = VPoint;
12005
12006 svp.pix_width = svp.rv_rect.width;
12007 svp.pix_height = svp.rv_rect.height;
12008
12009 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
12010 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
12011 // VPoint.rv_rect.height);
12012
12013 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
12014
12015 // If we are going to use the cached rotated image, there is no need to fetch
12016 // any chart data and this will do it...
12017 if (b_rcache_ok) chart_get_region.Clear();
12018
12019 // Blit pan acceleration
12020 if (VPoint.b_quilt) // quilted
12021 {
12022 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
12023
12024 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
12025
12026 bool busy = false;
12027 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
12028 m_cache_vp.rotation != VPoint.rotation)) {
12029 AbstractPlatform::ShowBusySpinner();
12030 busy = true;
12031 }
12032
12033 if ((m_working_bm.GetWidth() != svp.pix_width) ||
12034 (m_working_bm.GetHeight() != svp.pix_height))
12035 m_working_bm.Create(svp.pix_width, svp.pix_height,
12036 -1); // make sure the target is big enoug
12037
12038 if (fabs(VPoint.rotation) < 0.01) {
12039 bool b_save = true;
12040
12041 if (g_SencThreadManager) {
12042 if (g_SencThreadManager->GetJobCount()) {
12043 b_save = false;
12044 m_cache_vp.Invalidate();
12045 }
12046 }
12047
12048 // If the saved wxBitmap from last OnPaint is useable
12049 // calculate the blit parameters
12050
12051 // We can only do screen blit painting if subsequent ViewPorts differ by
12052 // whole pixels So, in small scale bFollow mode, force the full screen
12053 // render. This seems a hack....There may be better logic here.....
12054
12055 // if(m_bFollow)
12056 // b_save = false;
12057
12058 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
12059 if (b_newview) {
12060 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
12061 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
12062
12063 int dy = c_new.y - c_old.y;
12064 int dx = c_new.x - c_old.x;
12065
12066 // printf("In OnPaint Trying Blit dx: %d
12067 // dy:%d\n\n", dx, dy);
12068
12069 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
12070 if (dx || dy) {
12071 // Blit the reuseable portion of the cached wxBitmap to a working
12072 // bitmap
12073 temp_dc.SelectObject(m_working_bm);
12074
12075 wxMemoryDC cache_dc;
12076 cache_dc.SelectObject(m_cached_chart_bm);
12077
12078 if (dy > 0) {
12079 if (dx > 0) {
12080 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
12081 VPoint.pix_height - dy, &cache_dc, dx, dy);
12082 } else {
12083 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
12084 VPoint.pix_height - dy, &cache_dc, 0, dy);
12085 }
12086
12087 } else {
12088 if (dx > 0) {
12089 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
12090 VPoint.pix_height + dy, &cache_dc, dx, 0);
12091 } else {
12092 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
12093 VPoint.pix_height + dy, &cache_dc, 0, 0);
12094 }
12095 }
12096
12097 OCPNRegion update_region;
12098 if (dy) {
12099 if (dy > 0)
12100 update_region.Union(
12101 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
12102 else
12103 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
12104 }
12105
12106 if (dx) {
12107 if (dx > 0)
12108 update_region.Union(
12109 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
12110 else
12111 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
12112 }
12113
12114 // Render the new region
12115 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12116 update_region);
12117 cache_dc.SelectObject(wxNullBitmap);
12118 } else {
12119 // No sensible (dx, dy) change in the view, so use the cached
12120 // member bitmap
12121 temp_dc.SelectObject(m_cached_chart_bm);
12122 b_save = false;
12123 }
12124 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
12125
12126 } else // not blitable
12127 {
12128 temp_dc.SelectObject(m_working_bm);
12129 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12130 chart_get_region);
12131 }
12132 } else {
12133 // No change in the view, so use the cached member bitmap2
12134 temp_dc.SelectObject(m_cached_chart_bm);
12135 b_save = false;
12136 }
12137 } else // cached bitmap is not yet valid
12138 {
12139 temp_dc.SelectObject(m_working_bm);
12140 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12141 chart_get_region);
12142 }
12143
12144 // Save the fully rendered quilt image as a wxBitmap member of this class
12145 if (b_save) {
12146 // if((m_cached_chart_bm.GetWidth() !=
12147 // svp.pix_width) ||
12148 // (m_cached_chart_bm.GetHeight() !=
12149 // svp.pix_height))
12150 // m_cached_chart_bm.Create(svp.pix_width,
12151 // svp.pix_height, -1); // target wxBitmap
12152 // is big enough
12153 wxMemoryDC scratch_dc_0;
12154 scratch_dc_0.SelectObject(m_cached_chart_bm);
12155 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12156
12157 scratch_dc_0.SelectObject(wxNullBitmap);
12158
12159 m_bm_cache_vp =
12160 VPoint; // save the ViewPort associated with the cached wxBitmap
12161 }
12162 }
12163
12164 else // quilted, rotated
12165 {
12166 temp_dc.SelectObject(m_working_bm);
12167 OCPNRegion chart_get_all_region(
12168 wxRect(0, 0, svp.pix_width, svp.pix_height));
12169 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12170 chart_get_all_region);
12171 }
12172
12173 AbstractPlatform::HideBusySpinner();
12174
12175 }
12176
12177 else // not quilted
12178 {
12179 if (!m_singleChart) {
12180 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12181 dc.Clear();
12182 return;
12183 }
12184
12185 if (!chart_get_region.IsEmpty()) {
12186 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12187 }
12188 }
12189
12190 if (temp_dc.IsOk()) {
12191 // Arrange to render the World Chart vector data behind the rendered
12192 // current chart so that uncovered canvas areas show at least the world
12193 // chart.
12194 OCPNRegion chartValidRegion;
12195 if (!VPoint.b_quilt) {
12196 // Make a region covering the current chart on the canvas
12197
12198 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12199 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12200 else {
12201 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12202 // require that the viewport passed here have pix_width and pix_height
12203 // set to the actual display, not the virtual (rv_rect) sizes
12204 // (the vector calculations require the virtual sizes in svp)
12205
12206 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12207 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12208 }
12209 } else
12210 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12211
12212 temp_dc.DestroyClippingRegion();
12213
12214 // Copy current chart region
12215 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12216
12217 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12218
12219 if (!backgroundRegion.IsEmpty()) {
12220 // Draw the Background Chart only in the areas NOT covered by the
12221 // current chart view
12222
12223 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12224 clipping regions with more than 1 rectangle so... */
12225 wxColour water = pWorldBackgroundChart->water;
12226 if (water.IsOk()) {
12227 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12228 temp_dc.SetBrush(wxBrush(water));
12229 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12230 while (upd.HaveRects()) {
12231 wxRect rect = upd.GetRect();
12232 temp_dc.DrawRectangle(rect);
12233 upd.NextRect();
12234 }
12235 }
12236 // Associate with temp_dc
12237 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12238 temp_dc.SetDeviceClippingRegion(*clip_region);
12239 delete clip_region;
12240
12241 ocpnDC bgdc(temp_dc);
12242 double r = VPoint.rotation;
12243 SetVPRotation(VPoint.skew);
12244
12245 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12246 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12247
12248 SetVPRotation(r);
12249 }
12250 } // temp_dc.IsOk();
12251
12252 wxMemoryDC *pChartDC = &temp_dc;
12253 wxMemoryDC rotd_dc;
12254
12255 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12256 // Can we use the current rotated image cache?
12257 if (!b_rcache_ok) {
12258#ifdef __WXMSW__
12259 wxMemoryDC tbase_dc;
12260 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12261 tbase_dc.SelectObject(bm_base);
12262 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12263 tbase_dc.SelectObject(wxNullBitmap);
12264#else
12265 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12266#endif
12267
12268 wxImage base_image;
12269 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12270
12271 // Use a local static image rotator to improve wxWidgets code profile
12272 // Especially, on GTK the wxRound and wxRealPoint functions are very
12273 // expensive.....
12274
12275 double angle = GetVP().skew - GetVP().rotation;
12276 wxImage ri;
12277 bool b_rot_ok = false;
12278 if (base_image.IsOk()) {
12279 ViewPort rot_vp = GetVP();
12280
12281 m_b_rot_hidef = false;
12282
12283 ri = Image_Rotate(
12284 base_image, angle,
12285 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12286 m_b_rot_hidef, &m_roffset);
12287
12288 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12289 (rot_vp.rotation == VPoint.rotation) &&
12290 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12291 rot_vp.IsValid() && (ri.IsOk())) {
12292 b_rot_ok = true;
12293 }
12294 }
12295
12296 if (b_rot_ok) {
12297 delete m_prot_bm;
12298 m_prot_bm = new wxBitmap(ri);
12299 }
12300
12301 m_roffset.x += VPoint.rv_rect.x;
12302 m_roffset.y += VPoint.rv_rect.y;
12303 }
12304
12305 if (m_prot_bm && m_prot_bm->IsOk()) {
12306 rotd_dc.SelectObject(*m_prot_bm);
12307 pChartDC = &rotd_dc;
12308 } else {
12309 pChartDC = &temp_dc;
12310 m_roffset = wxPoint(0, 0);
12311 }
12312 } else { // unrotated
12313 pChartDC = &temp_dc;
12314 m_roffset = wxPoint(0, 0);
12315 }
12316
12317 wxPoint offset = m_roffset;
12318
12319 // Save the PixelCache viewpoint for next time
12320 m_cache_vp = VPoint;
12321
12322 // Set up a scratch DC for overlay objects
12323 wxMemoryDC mscratch_dc;
12324 mscratch_dc.SelectObject(*pscratch_bm);
12325
12326 mscratch_dc.ResetBoundingBox();
12327 mscratch_dc.DestroyClippingRegion();
12328 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12329
12330 // Blit the externally invalidated areas of the chart onto the scratch dc
12331 wxRegionIterator upd(rgn_blit); // get the update rect list
12332 while (upd) {
12333 wxRect rect = upd.GetRect();
12334
12335 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12336 rect.x - offset.x, rect.y - offset.y);
12337 upd++;
12338 }
12339
12340 // If multi-canvas, indicate which canvas has keyboard focus
12341 // by drawing a simple blue bar at the top.
12342 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12343 if (this == wxWindow::FindFocus()) {
12344 g_focusCanvas = this;
12345
12346 wxColour colour = GetGlobalColor("BLUE4");
12347 mscratch_dc.SetPen(wxPen(colour));
12348 mscratch_dc.SetBrush(wxBrush(colour));
12349
12350 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12351 mscratch_dc.DrawRectangle(activeRect);
12352 }
12353 }
12354
12355 // Any MBtiles?
12356 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12357 unsigned int im = stackIndexArray.size();
12358 if (VPoint.b_quilt && im > 0) {
12359 std::vector<int> tiles_to_show;
12360 for (unsigned int is = 0; is < im; is++) {
12361 const ChartTableEntry &cte =
12362 ChartData->GetChartTableEntry(stackIndexArray[is]);
12363 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12364 continue;
12365 }
12366 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12367 tiles_to_show.push_back(stackIndexArray[is]);
12368 }
12369 }
12370
12371 if (tiles_to_show.size())
12372 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12373 }
12374
12375 // May get an unexpected OnPaint call while switching display modes
12376 // Guard for that.
12377 if (!g_bopengl) {
12378 ocpnDC scratch_dc(mscratch_dc);
12379 RenderAlertMessage(mscratch_dc, GetVP());
12380 }
12381
12382#if 0
12383 // quiting?
12384 if (g_bquiting) {
12385#ifdef ocpnUSE_DIBSECTION
12386 ocpnMemDC q_dc;
12387#else
12388 wxMemoryDC q_dc;
12389#endif
12390 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12391 q_dc.SelectObject(qbm);
12392
12393 // Get a copy of the screen
12394 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12395
12396 // Draw a rectangle over the screen with a stipple brush
12397 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12398 q_dc.SetBrush(qbr);
12399 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12400
12401 // Blit back into source
12402 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12403 wxCOPY);
12404
12405 q_dc.SelectObject(wxNullBitmap);
12406 }
12407#endif
12408
12409#if 0
12410 // It is possible that this two-step method may be reuired for some platforms.
12411 // So, retain in the code base to aid recovery if necessary
12412
12413 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12414 if( VPoint.b_quilt ) {
12415 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12416 ChartBase *chart = m_pQuilt->GetRefChart();
12417 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12418
12419 // Clear the text Global declutter list
12420 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12421 if(ChPI)
12422 ChPI->ClearPLIBTextList();
12423 else{
12424 if(ps52plib)
12425 ps52plib->ClearTextList();
12426 }
12427
12428 wxMemoryDC t_dc;
12429 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12430
12431 wxColor maskBackground = wxColour(1,0,0);
12432 t_dc.SelectObject( qbm );
12433 t_dc.SetBackground(wxBrush(maskBackground));
12434 t_dc.Clear();
12435
12436 // Copy the scratch DC into the new bitmap
12437 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12438
12439 // Render the text to the new bitmap
12440 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12441 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12442
12443 // Copy the new bitmap back to the scratch dc
12444 wxRegionIterator upd_final( ru );
12445 while( upd_final ) {
12446 wxRect rect = upd_final.GetRect();
12447 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12448 upd_final++;
12449 }
12450
12451 t_dc.SelectObject( wxNullBitmap );
12452 }
12453 }
12454 }
12455#endif
12456 // Direct rendering model...
12457 if (VPoint.b_quilt) {
12458 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12459 ChartBase *chart = m_pQuilt->GetRefChart();
12460 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12461 // Clear the text Global declutter list
12462 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12463 if (ChPI)
12464 ChPI->ClearPLIBTextList();
12465 else {
12466 if (ps52plib) ps52plib->ClearTextList();
12467 }
12468
12469 // Render the text directly to the scratch bitmap
12470 OCPNRegion chart_all_text_region(
12471 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12472
12473 if (g_bShowChartBar && m_Piano) {
12474 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12475 GetVP().pix_width, m_Piano->GetHeight());
12476
12477 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12478 if (!style->chartStatusWindowTransparent)
12479 chart_all_text_region.Subtract(chart_bar_rect);
12480 }
12481
12482 if (m_Compass && m_Compass->IsShown()) {
12483 wxRect compassRect = m_Compass->GetRect();
12484 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12485 chart_all_text_region.Subtract(compassRect);
12486 }
12487 }
12488
12489 mscratch_dc.DestroyClippingRegion();
12490
12491 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12492 chart_all_text_region);
12493 }
12494 }
12495 }
12496
12497 // Now that charts are fully rendered, apply the overlay objects as decals.
12498 ocpnDC scratch_dc(mscratch_dc);
12499 DrawOverlayObjects(scratch_dc, ru);
12500
12501 // And finally, blit the scratch dc onto the physical dc
12502 wxRegionIterator upd_final(rgn_blit);
12503 while (upd_final) {
12504 wxRect rect = upd_final.GetRect();
12505 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12506 rect.y);
12507 upd_final++;
12508 }
12509
12510 // Deselect the chart bitmap from the temp_dc, so that it will not be
12511 // destroyed in the temp_dc dtor
12512 temp_dc.SelectObject(wxNullBitmap);
12513 // And for the scratch bitmap
12514 mscratch_dc.SelectObject(wxNullBitmap);
12515
12516 dc.DestroyClippingRegion();
12517
12518 PaintCleanup();
12519}
12520
12521void ChartCanvas::PaintCleanup() {
12522 // Handle the current graphic window, if present
12523 if (m_inPinch) return;
12524
12525 if (pCwin) {
12526 pCwin->Show();
12527 if (m_bTCupdate) {
12528 pCwin->Refresh();
12529 pCwin->Update();
12530 }
12531 }
12532
12533 // And set flags for next time
12534 m_bTCupdate = false;
12535
12536 // Handle deferred WarpPointer
12537 if (warp_flag) {
12538 WarpPointer(warp_x, warp_y);
12539 warp_flag = false;
12540 }
12541
12542 // Start movement timers, this runs nearly immediately.
12543 // the reason we cannot simply call it directly is the
12544 // refresh events it emits may be blocked from this paint event
12545 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12546 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12547}
12548
12549#if 0
12550wxColour GetErrorGraphicColor(double val)
12551{
12552 /*
12553 double valm = wxMin(val_max, val);
12554
12555 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12556 unsigned char red = (unsigned char)(255 * (valm/val_max));
12557
12558 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12559
12560 hv.saturation = 1.0;
12561 hv.value = 1.0;
12562
12563 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12564 return wxColour(rv.red, rv.green, rv.blue);
12565 */
12566
12567 // HTML colors taken from NOAA WW3 Web representation
12568 wxColour c;
12569 if((val > 0) && (val < 1)) c.Set("#002ad9");
12570 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12571 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12572 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12573 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12574 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12575 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12576 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12577 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12578 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12579 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12580 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12581 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12582 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12583 else if((val >= 30) && (val < 36)) c.Set("#870000");
12584 else if((val >= 36) && (val < 42)) c.Set("#690000");
12585 else if((val >= 42) && (val < 48)) c.Set("#550000");
12586 else if( val >= 48) c.Set("#410000");
12587
12588 return c;
12589}
12590
12591void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12592{
12593 wxImage gr_image(vp->pix_width, vp->pix_height);
12594 gr_image.InitAlpha();
12595
12596 double maxval = -10000;
12597 double minval = 10000;
12598
12599 double rlat, rlon;
12600 double glat, glon;
12601
12602 GetCanvasPixPoint(0, 0, rlat, rlon);
12603
12604 for(int i=1; i < vp->pix_height-1; i++)
12605 {
12606 for(int j=0; j < vp->pix_width; j++)
12607 {
12608 // Reference mercator value
12609// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12610
12611 // Georef value
12612 GetCanvasPixPoint(j, i, glat, glon);
12613
12614 maxval = wxMax(maxval, (glat - rlat));
12615 minval = wxMin(minval, (glat - rlat));
12616
12617 }
12618 rlat = glat;
12619 }
12620
12621 GetCanvasPixPoint(0, 0, rlat, rlon);
12622 for(int i=1; i < vp->pix_height-1; i++)
12623 {
12624 for(int j=0; j < vp->pix_width; j++)
12625 {
12626 // Reference mercator value
12627// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12628
12629 // Georef value
12630 GetCanvasPixPoint(j, i, glat, glon);
12631
12632 double f = ((glat - rlat)-minval)/(maxval - minval);
12633
12634 double dy = (f * 40);
12635
12636 wxColour c = GetErrorGraphicColor(dy);
12637 unsigned char r = c.Red();
12638 unsigned char g = c.Green();
12639 unsigned char b = c.Blue();
12640
12641 gr_image.SetRGB(j, i, r,g,b);
12642 if((glat - rlat )!= 0)
12643 gr_image.SetAlpha(j, i, 128);
12644 else
12645 gr_image.SetAlpha(j, i, 255);
12646
12647 }
12648 rlat = glat;
12649 }
12650
12651 // Create a Bitmap
12652 wxBitmap *pbm = new wxBitmap(gr_image);
12653 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12654 pbm->SetMask(gr_mask);
12655
12656 pmdc->DrawBitmap(*pbm, 0,0);
12657
12658 delete pbm;
12659
12660}
12661
12662#endif
12663
12664void ChartCanvas::CancelMouseRoute() {
12665 m_routeState = 0;
12666 m_pMouseRoute = NULL;
12667 m_bDrawingRoute = false;
12668}
12669
12670int ChartCanvas::GetNextContextMenuId() {
12671 return CanvasMenuHandler::GetNextContextMenuId();
12672}
12673
12674bool ChartCanvas::SetCursor(const wxCursor &c) {
12675#ifdef ocpnUSE_GL
12676 if (g_bopengl && m_glcc)
12677 return m_glcc->SetCursor(c);
12678 else
12679#endif
12680 return wxWindow::SetCursor(c);
12681}
12682
12683void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12684 if (g_bquiting) return;
12685 // Keep the mouse position members up to date
12686 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12687
12688 // Retrigger the route leg popup timer
12689 // This handles the case when the chart is moving in auto-follow mode,
12690 // but no user mouse input is made. The timer handler may Hide() the
12691 // popup if the chart moved enough n.b. We use slightly longer oneshot
12692 // value to allow this method's Refresh() to complete before potentially
12693 // getting another Refresh() in the popup timer handler.
12694 if (!m_RolloverPopupTimer.IsRunning() &&
12695 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12696 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12697 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12698 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12699
12700#ifdef ocpnUSE_GL
12701 if (m_glcc && g_bopengl) {
12702 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12703 // overlay objects.
12704 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12705
12706 m_glcc->Refresh(eraseBackground,
12707 NULL); // We always are going to render the entire screen
12708 // anyway, so make
12709 // sure that the window managers understand the invalid area
12710 // is actually the entire client area.
12711
12712 // We need to selectively Refresh some child windows, if they are visible.
12713 // Note that some children are refreshed elsewhere on timer ticks, so don't
12714 // need attention here.
12715
12716 // Thumbnail chart
12717 if (pthumbwin && pthumbwin->IsShown()) {
12718 pthumbwin->Raise();
12719 pthumbwin->Refresh(false);
12720 }
12721
12722 // ChartInfo window
12723 if (m_pCIWin && m_pCIWin->IsShown()) {
12724 m_pCIWin->Raise();
12725 m_pCIWin->Refresh(false);
12726 }
12727
12728 // if(g_MainToolbar)
12729 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12730
12731 } else
12732#endif
12733 wxWindow::Refresh(eraseBackground, rect);
12734}
12735
12736void ChartCanvas::Update() {
12737 if (m_glcc && g_bopengl) {
12738#ifdef ocpnUSE_GL
12739 m_glcc->Update();
12740#endif
12741 } else
12742 wxWindow::Update();
12743}
12744
12745void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12746 if (!pemboss) return;
12747 int x = pemboss->x, y = pemboss->y;
12748 const double factor = 200;
12749
12750 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12751 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12752 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12753
12754 // Grab a snipped image out of the chart
12755 wxMemoryDC snip_dc;
12756 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12757 snip_dc.SelectObject(snip_bmp);
12758
12759 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12760 snip_dc.SelectObject(wxNullBitmap);
12761
12762 wxImage snip_img = snip_bmp.ConvertToImage();
12763
12764 // Apply Emboss map to the snip image
12765 unsigned char *pdata = snip_img.GetData();
12766 if (pdata) {
12767 for (int y = 0; y < pemboss->height; y++) {
12768 int map_index = (y * pemboss->width);
12769 for (int x = 0; x < pemboss->width; x++) {
12770 double val = (pemboss->pmap[map_index] * factor) / 256.;
12771
12772 int nred = (int)((*pdata) + val);
12773 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12774 *pdata++ = (unsigned char)nred;
12775
12776 int ngreen = (int)((*pdata) + val);
12777 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12778 *pdata++ = (unsigned char)ngreen;
12779
12780 int nblue = (int)((*pdata) + val);
12781 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12782 *pdata++ = (unsigned char)nblue;
12783
12784 map_index++;
12785 }
12786 }
12787 }
12788
12789 // Convert embossed snip to a bitmap
12790 wxBitmap emb_bmp(snip_img);
12791
12792 // Map to another memoryDC
12793 wxMemoryDC result_dc;
12794 result_dc.SelectObject(emb_bmp);
12795
12796 // Blit to target
12797 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12798
12799 result_dc.SelectObject(wxNullBitmap);
12800}
12801
12802emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12803 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12804
12805 if (GetQuiltMode()) {
12806 // disable Overzoom indicator for MBTiles
12807 int refIndex = GetQuiltRefChartdbIndex();
12808 if (refIndex >= 0) {
12809 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12810 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12811 if (current_type == CHART_TYPE_MBTILES) {
12812 ChartBase *pChart = m_pQuilt->GetRefChart();
12813 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12814 if (ptc) {
12815 zoom_factor = ptc->GetZoomFactor();
12816 }
12817 }
12818 }
12819
12820 if (zoom_factor <= 3.9) return NULL;
12821 } else {
12822 if (m_singleChart) {
12823 if (zoom_factor <= 3.9) return NULL;
12824 } else
12825 return NULL;
12826 }
12827
12828 if (m_pEM_OverZoom) {
12829 m_pEM_OverZoom->x = 4;
12830 m_pEM_OverZoom->y = 0;
12831 if (g_MainToolbar && IsPrimaryCanvas()) {
12832 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12833 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12834 }
12835 }
12836 return m_pEM_OverZoom;
12837}
12838
12839void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12840 GridDraw(dc);
12841
12842 // bool pluginOverlayRender = true;
12843 //
12844 // if(g_canvasConfig > 0){ // Multi canvas
12845 // if(IsPrimaryCanvas())
12846 // pluginOverlayRender = false;
12847 // }
12848
12849 g_overlayCanvas = this;
12850
12851 if (g_pi_manager) {
12852 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12853 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12855 }
12856
12857 AISDrawAreaNotices(dc, GetVP(), this);
12858
12859 wxDC *pdc = dc.GetDC();
12860 if (pdc) {
12861 pdc->DestroyClippingRegion();
12862 wxDCClipper(*pdc, ru);
12863 }
12864
12865 if (m_bShowNavobjects) {
12866 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12867 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12868 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12869 DrawAnchorWatchPoints(dc);
12870 } else {
12871 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12872 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12873 }
12874
12875 AISDraw(dc, GetVP(), this);
12876 ShipDraw(dc);
12877 AlertDraw(dc);
12878
12879 RenderVisibleSectorLights(dc);
12880
12881 RenderAllChartOutlines(dc, GetVP());
12882 RenderRouteLegs(dc);
12883 RenderShipToActive(dc, false);
12884 ScaleBarDraw(dc);
12885 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12886 if (g_pi_manager) {
12887 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12889 }
12890
12891 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12892 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12893
12894 if (g_pi_manager) {
12895 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12897 }
12898
12899 if (m_bShowTide) {
12900 RebuildTideSelectList(GetVP().GetBBox());
12901 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12902 }
12903
12904 if (m_bShowCurrent) {
12905 RebuildCurrentSelectList(GetVP().GetBBox());
12906 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12907 }
12908
12909 if (!g_PrintingInProgress) {
12910 if (IsPrimaryCanvas()) {
12911 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12912 }
12913
12914 if (IsPrimaryCanvas()) {
12915 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12916 }
12917
12918 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12919
12920 if (m_pTrackRolloverWin) {
12921 m_pTrackRolloverWin->Draw(dc);
12922 m_brepaint_piano = true;
12923 }
12924
12925 if (m_pRouteRolloverWin) {
12926 m_pRouteRolloverWin->Draw(dc);
12927 m_brepaint_piano = true;
12928 }
12929
12930 if (m_pAISRolloverWin) {
12931 m_pAISRolloverWin->Draw(dc);
12932 m_brepaint_piano = true;
12933 }
12934 if (m_brepaint_piano && g_bShowChartBar) {
12935 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12936 }
12937
12938 if (m_Compass) m_Compass->Paint(dc);
12939
12940 if (!g_CanvasHideNotificationIcon) {
12941 if (IsPrimaryCanvas()) {
12942 auto &noteman = NotificationManager::GetInstance();
12943 if (m_notification_button) {
12944 if (noteman.GetNotificationCount()) {
12945 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12946 if (m_notification_button->UpdateStatus()) Refresh();
12947 m_notification_button->Show(true);
12948 m_notification_button->Paint(dc);
12949 } else {
12950 m_notification_button->Show(false);
12951 }
12952 }
12953 }
12954 }
12955 }
12956 if (g_pi_manager) {
12957 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12959 }
12960}
12961
12962emboss_data *ChartCanvas::EmbossDepthScale() {
12963 if (!m_bShowDepthUnits) return NULL;
12964
12965 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12966
12967 if (GetQuiltMode()) {
12968 wxString s = m_pQuilt->GetQuiltDepthUnit();
12969 s.MakeUpper();
12970 if (s == "FEET")
12971 depth_unit_type = DEPTH_UNIT_FEET;
12972 else if (s.StartsWith("FATHOMS"))
12973 depth_unit_type = DEPTH_UNIT_FATHOMS;
12974 else if (s.StartsWith("METERS"))
12975 depth_unit_type = DEPTH_UNIT_METERS;
12976 else if (s.StartsWith("METRES"))
12977 depth_unit_type = DEPTH_UNIT_METERS;
12978 else if (s.StartsWith("METRIC"))
12979 depth_unit_type = DEPTH_UNIT_METERS;
12980 else if (s.StartsWith("METER"))
12981 depth_unit_type = DEPTH_UNIT_METERS;
12982
12983 } else {
12984 if (m_singleChart) {
12985 depth_unit_type = m_singleChart->GetDepthUnitType();
12986 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12987 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12988 }
12989 }
12990
12991 emboss_data *ped = NULL;
12992 switch (depth_unit_type) {
12993 case DEPTH_UNIT_FEET:
12994 ped = m_pEM_Feet;
12995 break;
12996 case DEPTH_UNIT_METERS:
12997 ped = m_pEM_Meters;
12998 break;
12999 case DEPTH_UNIT_FATHOMS:
13000 ped = m_pEM_Fathoms;
13001 break;
13002 default:
13003 return NULL;
13004 }
13005
13006 ped->x = (GetVP().pix_width - ped->width);
13007
13008 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
13009 wxRect r = m_Compass->GetRect();
13010 ped->y = r.y + r.height;
13011 } else {
13012 ped->y = 40;
13013 }
13014 return ped;
13015}
13016
13017void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
13018 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13019 wxFont font;
13020 if (style->embossFont == wxEmptyString) {
13021 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13022 font = *dFont;
13023 font.SetPointSize(60);
13024 font.SetWeight(wxFONTWEIGHT_BOLD);
13025 } else
13026 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13027 wxFONTWEIGHT_BOLD, false, style->embossFont);
13028
13029 int emboss_width = 500;
13030 int emboss_height = 200;
13031
13032 // Free any existing emboss maps
13033 delete m_pEM_Feet;
13034 delete m_pEM_Meters;
13035 delete m_pEM_Fathoms;
13036
13037 // Create the 3 DepthUnit emboss map structures
13038 m_pEM_Feet =
13039 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
13040 m_pEM_Meters =
13041 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
13042 m_pEM_Fathoms =
13043 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
13044}
13045
13046#define OVERZOOM_TEXT _("OverZoom")
13047
13048void ChartCanvas::SetOverzoomFont() {
13049 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
13050 int w, h;
13051
13052 wxFont font;
13053 if (style->embossFont == wxEmptyString) {
13054 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
13055 font = *dFont;
13056 font.SetPointSize(40);
13057 font.SetWeight(wxFONTWEIGHT_BOLD);
13058 } else
13059 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
13060 wxFONTWEIGHT_BOLD, false, style->embossFont);
13061
13062 wxClientDC dc(this);
13063 dc.SetFont(font);
13064 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13065
13066 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
13067 font.SetPointSize(font.GetPointSize() - 1);
13068 dc.SetFont(font);
13069 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
13070 }
13071 m_overzoomFont = font;
13072 m_overzoomTextWidth = w;
13073 m_overzoomTextHeight = h;
13074}
13075
13076void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
13077 delete m_pEM_OverZoom;
13078
13079 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
13080 m_pEM_OverZoom =
13081 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
13082 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
13083}
13084
13085emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
13086 int height, const wxString &str,
13087 ColorScheme cs) {
13088 int *pmap;
13089
13090 // Create a temporary bitmap
13091 wxBitmap bmp(width, height, -1);
13092
13093 // Create a memory DC
13094 wxMemoryDC temp_dc;
13095 temp_dc.SelectObject(bmp);
13096
13097 // Paint on it
13098 temp_dc.SetBackground(*wxWHITE_BRUSH);
13099 temp_dc.SetTextBackground(*wxWHITE);
13100 temp_dc.SetTextForeground(*wxBLACK);
13101
13102 temp_dc.Clear();
13103
13104 temp_dc.SetFont(font);
13105
13106 int str_w, str_h;
13107 temp_dc.GetTextExtent(str, &str_w, &str_h);
13108 // temp_dc.DrawText( str, width - str_w - 10, 10 );
13109 temp_dc.DrawText(str, 1, 1);
13110
13111 // Deselect the bitmap
13112 temp_dc.SelectObject(wxNullBitmap);
13113
13114 // Convert bitmap the wxImage for manipulation
13115 wxImage img = bmp.ConvertToImage();
13116
13117 int image_width = str_w * 105 / 100;
13118 int image_height = str_h * 105 / 100;
13119 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
13120 wxMin(image_height, img.GetHeight()));
13121 wxImage imgs = img.GetSubImage(r);
13122
13123 double val_factor;
13124 switch (cs) {
13125 case GLOBAL_COLOR_SCHEME_DAY:
13126 default:
13127 val_factor = 1;
13128 break;
13129 case GLOBAL_COLOR_SCHEME_DUSK:
13130 val_factor = .5;
13131 break;
13132 case GLOBAL_COLOR_SCHEME_NIGHT:
13133 val_factor = .25;
13134 break;
13135 }
13136
13137 int val;
13138 int index;
13139 const int w = imgs.GetWidth();
13140 const int h = imgs.GetHeight();
13141 pmap = (int *)calloc(w * h * sizeof(int), 1);
13142 // Create emboss map by differentiating the emboss image
13143 // and storing integer results in pmap
13144 // n.b. since the image is B/W, it is sufficient to check
13145 // one channel (i.e. red) only
13146 for (int y = 1; y < h - 1; y++) {
13147 for (int x = 1; x < w - 1; x++) {
13148 val =
13149 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13150 val = (int)(val * val_factor);
13151 index = (y * w) + x;
13152 pmap[index] = val;
13153 }
13154 }
13155
13156 emboss_data *pret = new emboss_data;
13157 pret->pmap = pmap;
13158 pret->width = w;
13159 pret->height = h;
13160
13161 return pret;
13162}
13163
13164void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13165 Track *active_track = NULL;
13166 for (Track *pTrackDraw : g_TrackList) {
13167 if (g_pActiveTrack == pTrackDraw) {
13168 active_track = pTrackDraw;
13169 continue;
13170 }
13171
13172 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13173 }
13174
13175 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13176}
13177
13178void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13179 Track *active_track = NULL;
13180 for (Track *pTrackDraw : g_TrackList) {
13181 if (g_pActiveTrack == pTrackDraw) {
13182 active_track = pTrackDraw;
13183 break;
13184 }
13185 }
13186 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13187}
13188
13189void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13190 Route *active_route = NULL;
13191 for (Route *pRouteDraw : *pRouteList) {
13192 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13193 active_route = pRouteDraw;
13194 continue;
13195 }
13196
13197 // if(m_canvasIndex == 1)
13198 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13199 }
13200
13201 // Draw any active or selected route (or track) last, so that is is always on
13202 // top
13203 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13204}
13205
13206void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13207 Route *active_route = NULL;
13208
13209 for (Route *pRouteDraw : *pRouteList) {
13210 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13211 active_route = pRouteDraw;
13212 break;
13213 }
13214 }
13215 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13216}
13217
13218void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13219 if (!pWayPointMan) return;
13220
13221 auto node = pWayPointMan->GetWaypointList()->begin();
13222
13223 while (node != pWayPointMan->GetWaypointList()->end()) {
13224 RoutePoint *pWP = *node;
13225 if (pWP) {
13226 if (pWP->m_bIsInRoute) {
13227 ++node;
13228 continue;
13229 }
13230
13231 /* technically incorrect... waypoint has bounding box */
13232 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13233 RoutePointGui(*pWP).Draw(dc, this, NULL);
13234 else {
13235 // Are Range Rings enabled?
13236 if (pWP->GetShowWaypointRangeRings() &&
13237 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13238 double factor = 1.00;
13239 if (pWP->GetWaypointRangeRingsStepUnits() ==
13240 1) // convert kilometers to NMi
13241 factor = 1 / 1.852;
13242
13243 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13244 pWP->GetWaypointRangeRingsStep() / 60.;
13245 radius *= 2; // Fudge factor
13246
13247 LLBBox radar_box;
13248 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13249 pWP->m_lat + radius, pWP->m_lon + radius);
13250 if (!BltBBox.IntersectOut(radar_box)) {
13251 RoutePointGui(*pWP).Draw(dc, this, NULL);
13252 }
13253 }
13254 }
13255 }
13256
13257 ++node;
13258 }
13259}
13260
13261void ChartCanvas::DrawBlinkObjects() {
13262 // All RoutePoints
13263 wxRect update_rect;
13264
13265 if (!pWayPointMan) return;
13266
13267 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13268 if (pWP) {
13269 if (pWP->m_bBlink) {
13270 update_rect.Union(pWP->CurrentRect_in_DC);
13271 }
13272 }
13273 }
13274 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13275}
13276
13277void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13278 // draw anchor watch rings, if activated
13279
13281 wxPoint r1, r2;
13282 wxPoint lAnchorPoint1, lAnchorPoint2;
13283 double lpp1 = 0.0;
13284 double lpp2 = 0.0;
13285 if (pAnchorWatchPoint1) {
13286 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13288 &lAnchorPoint1);
13289 }
13290 if (pAnchorWatchPoint2) {
13291 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13293 &lAnchorPoint2);
13294 }
13295
13296 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13297 wxPen ppPenr(GetGlobalColor("URED"), 2);
13298
13299 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13300 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13301 dc.SetBrush(*ppBrush);
13302
13303 if (lpp1 > 0) {
13304 dc.SetPen(ppPeng);
13305 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13306 }
13307
13308 if (lpp2 > 0) {
13309 dc.SetPen(ppPeng);
13310 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13311 }
13312
13313 if (lpp1 < 0) {
13314 dc.SetPen(ppPenr);
13315 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13316 }
13317
13318 if (lpp2 < 0) {
13319 dc.SetPen(ppPenr);
13320 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13321 }
13322 }
13323}
13324
13325double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13326 double lpp = 0.;
13327 wxPoint r1;
13328 wxPoint lAnchorPoint;
13329 double d1 = 0.0;
13330 double dabs;
13331 double tlat1, tlon1;
13332
13333 if (pAnchorWatchPoint) {
13334 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13335 d1 = ocpn::AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13336 dabs = fabs(d1 / 1852.);
13337 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13338 &tlat1, &tlon1);
13339 GetCanvasPointPix(tlat1, tlon1, &r1);
13340 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13341 &lAnchorPoint);
13342 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13343 pow((double)(lAnchorPoint.y - r1.y), 2));
13344
13345 // This is an entry watch
13346 if (d1 < 0) lpp = -lpp;
13347 }
13348 return lpp;
13349}
13350
13351//------------------------------------------------------------------------------------------
13352// Tides Support
13353//------------------------------------------------------------------------------------------
13354void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13355 if (!ptcmgr) return;
13356 if (this != wxWindow::FindFocus()) return;
13357
13358 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13359
13360 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13361 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13362 double lon = pIDX->IDX_lon;
13363 double lat = pIDX->IDX_lat;
13364
13365 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13366 if ((type == 't') || (type == 'T')) {
13367 if (BBox.Contains(lat, lon)) {
13368 // Manage the point selection list
13369 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13370 }
13371 }
13372 }
13373}
13374
13375void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13376 if (!ptcmgr) return;
13377
13378 wxDateTime this_now = gTimeSource;
13379 bool cur_time = !gTimeSource.IsValid();
13380 if (cur_time) this_now = wxDateTime::Now();
13381 time_t t_this_now = this_now.GetTicks();
13382
13383 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13384 wxPENSTYLE_SOLID);
13385 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13386 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13387 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13388 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13389
13390 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13391 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13392 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13393 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13394 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13395 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13396
13397 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13398 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13399 int font_size = wxMax(10, dFont->GetPointSize());
13400 font_size /= g_Platform->GetDisplayDIPMult(this);
13401 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13402 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13403 false, dFont->GetFaceName());
13404
13405 dc.SetPen(*pblack_pen);
13406 dc.SetBrush(*pgreen_brush);
13407
13408 wxBitmap bm;
13409 switch (m_cs) {
13410 case GLOBAL_COLOR_SCHEME_DAY:
13411 bm = m_bmTideDay;
13412 break;
13413 case GLOBAL_COLOR_SCHEME_DUSK:
13414 bm = m_bmTideDusk;
13415 break;
13416 case GLOBAL_COLOR_SCHEME_NIGHT:
13417 bm = m_bmTideNight;
13418 break;
13419 default:
13420 bm = m_bmTideDay;
13421 break;
13422 }
13423
13424 int bmw = bm.GetWidth();
13425 int bmh = bm.GetHeight();
13426
13427 float scale_factor = 1.0;
13428
13429 // Set the onscreen size of the symbol
13430 // Compensate for various display resolutions
13431 float icon_pixelRefDim = 45;
13432
13433 // Tidal report graphic is scaled by the text size of the label in use
13434 wxScreenDC sdc;
13435 int height;
13436 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13437 height *= g_Platform->GetDisplayDIPMult(this);
13438 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13439
13440 scale_factor *= pix_factor;
13441
13442 float user_scale_factor = g_ChartScaleFactorExp;
13443 if (g_ChartScaleFactorExp > 1.0)
13444 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13445 1.2; // soften the scale factor a bit
13446
13447 scale_factor *= user_scale_factor;
13448 scale_factor *= GetContentScaleFactor();
13449
13450 {
13451 double marge = 0.05;
13452 std::vector<LLBBox> drawn_boxes;
13453 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13454 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13455
13456 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13457 if ((type == 't') || (type == 'T')) // only Tides
13458 {
13459 double lon = pIDX->IDX_lon;
13460 double lat = pIDX->IDX_lat;
13461
13462 if (BBox.ContainsMarge(lat, lon, marge)) {
13463 // Avoid drawing detailed graphic for duplicate tide stations
13464 if (GetVP().chart_scale < 500000) {
13465 bool bdrawn = false;
13466 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13467 if (drawn_boxes[i].Contains(lat, lon)) {
13468 bdrawn = true;
13469 break;
13470 }
13471 }
13472 if (bdrawn) continue; // the station loop
13473
13474 LLBBox this_box;
13475 this_box.Set(lat, lon, lat, lon);
13476 this_box.EnLarge(.005);
13477 drawn_boxes.push_back(this_box);
13478 }
13479
13480 wxPoint r;
13481 GetCanvasPointPix(lat, lon, &r);
13482 // draw standard icons
13483 if (GetVP().chart_scale > 500000) {
13484 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13485 }
13486 // draw "extended" icons
13487 else {
13488 dc.SetFont(*plabelFont);
13489 {
13490 {
13491 float val, nowlev;
13492 float ltleve = 0.;
13493 float htleve = 0.;
13494 time_t tctime;
13495 time_t lttime = 0;
13496 time_t httime = 0;
13497 bool wt;
13498 // define if flood or ebb in the last ten minutes and verify if
13499 // data are useable
13500 if (ptcmgr->GetTideFlowSens(
13501 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13502 pIDX->IDX_rec_num, nowlev, val, wt)) {
13503 // search forward the first HW or LW near "now" ( starting at
13504 // "now" - ten minutes )
13505 ptcmgr->GetHightOrLowTide(
13506 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13507 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13508 wt, pIDX->IDX_rec_num, val, tctime);
13509 if (wt) {
13510 httime = tctime;
13511 htleve = val;
13512 } else {
13513 lttime = tctime;
13514 ltleve = val;
13515 }
13516 wt = !wt;
13517
13518 // then search opposite tide near "now"
13519 if (tctime > t_this_now) // search backward
13520 ptcmgr->GetHightOrLowTide(
13521 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13522 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13523 pIDX->IDX_rec_num, val, tctime);
13524 else
13525 // or search forward
13526 ptcmgr->GetHightOrLowTide(
13527 t_this_now, FORWARD_TEN_MINUTES_STEP,
13528 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13529 val, tctime);
13530 if (wt) {
13531 httime = tctime;
13532 htleve = val;
13533 } else {
13534 lttime = tctime;
13535 ltleve = val;
13536 }
13537
13538 // draw the tide rectangle:
13539
13540 // tide icon rectangle has default pre-scaled width = 12 ,
13541 // height = 45
13542 int width = (int)(12 * scale_factor + 0.5);
13543 int height = (int)(45 * scale_factor + 0.5);
13544 int linew = wxMax(1, (int)(scale_factor));
13545 int xDraw = r.x - (width / 2);
13546 int yDraw = r.y - (height / 2);
13547
13548 // process tide state ( %height and flow sens )
13549 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13550 int hs = (httime > lttime) ? -4 : 4;
13551 hs *= (int)(scale_factor + 0.5);
13552 if (ts > 0.995 || ts < 0.005) hs = 0;
13553 int ht_y = (int)(height * ts);
13554
13555 // draw yellow tide rectangle outlined in black
13556 pblack_pen->SetWidth(linew);
13557 dc.SetPen(*pblack_pen);
13558 dc.SetBrush(*pyelo_brush);
13559 dc.DrawRectangle(xDraw, yDraw, width, height);
13560
13561 // draw blue rectangle as water height, smaller in width than
13562 // yellow rectangle
13563 dc.SetPen(*pblue_pen);
13564 dc.SetBrush(*pblue_brush);
13565 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13566 (width - (4 * linew)), height - ht_y);
13567
13568 // draw sens arrows (ensure they are not "under-drawn" by top
13569 // line of blue rectangle )
13570 int hl;
13571 wxPoint arrow[3];
13572 arrow[0].x = xDraw + 2 * linew;
13573 arrow[1].x = xDraw + width / 2;
13574 arrow[2].x = xDraw + width - 2 * linew;
13575 pyelo_pen->SetWidth(linew);
13576 pblue_pen->SetWidth(linew);
13577 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13578 {
13579 hl = (int)(height * 0.25) + yDraw;
13580 arrow[0].y = hl;
13581 arrow[1].y = hl + hs;
13582 arrow[2].y = hl;
13583 if (ts < 0.15)
13584 dc.SetPen(*pyelo_pen);
13585 else
13586 dc.SetPen(*pblue_pen);
13587 dc.DrawLines(3, arrow);
13588 }
13589 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13590 {
13591 hl = (int)(height * 0.5) + yDraw;
13592 arrow[0].y = hl;
13593 arrow[1].y = hl + hs;
13594 arrow[2].y = hl;
13595 if (ts < 0.40)
13596 dc.SetPen(*pyelo_pen);
13597 else
13598 dc.SetPen(*pblue_pen);
13599 dc.DrawLines(3, arrow);
13600 }
13601 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13602 {
13603 hl = (int)(height * 0.75) + yDraw;
13604 arrow[0].y = hl;
13605 arrow[1].y = hl + hs;
13606 arrow[2].y = hl;
13607 if (ts < 0.65)
13608 dc.SetPen(*pyelo_pen);
13609 else
13610 dc.SetPen(*pblue_pen);
13611 dc.DrawLines(3, arrow);
13612 }
13613 // Convert nowlev to preferred height units (it comes from
13614 // GetHightOrLowTide in station units)
13615 double nowlev_converted = nowlev;
13616 Station_Data *pmsd = pIDX->pref_sta_data;
13617 if (pmsd) {
13618 // Convert from station units to meters first
13619 int unit_c = TCDataFactory::findunit(pmsd->unit);
13620 if (unit_c >= 0) {
13621 nowlev_converted =
13622 nowlev_converted *
13623 TCDataFactory::known_units[unit_c].conv_factor;
13624 }
13625 // Now convert from meters to preferred height units
13626 nowlev_converted = toUsrHeight(nowlev_converted);
13627 }
13628 // draw tide level text
13629 wxString s;
13630 s.Printf("%3.1f", nowlev_converted);
13631 // Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13632 if (pmsd) s.Append(getUsrHeightUnit());
13633 int wx1;
13634 dc.GetTextExtent(s, &wx1, NULL);
13635 wx1 *= g_Platform->GetDisplayDIPMult(this);
13636 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13637 }
13638 }
13639 }
13640 }
13641 }
13642 }
13643 }
13644 }
13645}
13646
13647//------------------------------------------------------------------------------------------
13648// Currents Support
13649//------------------------------------------------------------------------------------------
13650
13651void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13652 if (!ptcmgr) return;
13653
13654 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13655
13656 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13657 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13658 double lon = pIDX->IDX_lon;
13659 double lat = pIDX->IDX_lat;
13660
13661 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13662 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13663 if ((BBox.Contains(lat, lon))) {
13664 // Manage the point selection list
13665 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13666 }
13667 }
13668 }
13669}
13670
13671void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13672 if (!ptcmgr) return;
13673
13674 float tcvalue, dir;
13675 bool bnew_val;
13676 char sbuf[20];
13677 wxFont *pTCFont;
13678 double lon_last = 0.;
13679 double lat_last = 0.;
13680 // arrow size for Raz Blanchard : 12 knots north
13681 double marge = 0.2;
13682 bool cur_time = !gTimeSource.IsValid();
13683
13684 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13685 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13686
13687 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13688 wxPENSTYLE_SOLID);
13689 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13690 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13691 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13692 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13693 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13694 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13695 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13696 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13697
13698 double skew_angle = GetVPRotation();
13699
13700 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13701 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13702 int font_size = wxMax(10, dFont->GetPointSize());
13703 font_size /= g_Platform->GetDisplayDIPMult(this);
13704 pTCFont = FontMgr::Get().FindOrCreateFont(
13705 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13706 false, dFont->GetFaceName());
13707
13708 float scale_factor = 1.0;
13709
13710 // Set the onscreen size of the symbol
13711 // Current report graphic is scaled by the text size of the label in use
13712 wxScreenDC sdc;
13713 int height;
13714 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13715 height *= g_Platform->GetDisplayDIPMult(this);
13716 float nominal_icon_size_pixels = 15;
13717 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13718
13719 scale_factor *= pix_factor;
13720
13721 float user_scale_factor = g_ChartScaleFactorExp;
13722 if (g_ChartScaleFactorExp > 1.0)
13723 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13724 1.2; // soften the scale factor a bit
13725
13726 scale_factor *= user_scale_factor;
13727
13728 scale_factor *= GetContentScaleFactor();
13729
13730 {
13731 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13732 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13733 double lon = pIDX->IDX_lon;
13734 double lat = pIDX->IDX_lat;
13735
13736 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13737 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13738 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13739 wxPoint r;
13740 GetCanvasPointPix(lat, lon, &r);
13741
13742 wxPoint d[4]; // points of a diamond at the current station location
13743 int dd = (int)(5.0 * scale_factor + 0.5);
13744 d[0].x = r.x;
13745 d[0].y = r.y + dd;
13746 d[1].x = r.x + dd;
13747 d[1].y = r.y;
13748 d[2].x = r.x;
13749 d[2].y = r.y - dd;
13750 d[3].x = r.x - dd;
13751 d[3].y = r.y;
13752
13753 if (1) {
13754 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13755 dc.SetPen(*pblack_pen);
13756 dc.SetBrush(*porange_brush);
13757 dc.DrawPolygon(4, d);
13758
13759 if (type == 'C') {
13760 dc.SetBrush(*pblack_brush);
13761 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13762 }
13763
13764 if (GetVP().chart_scale < 1000000) {
13765 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13766 continue;
13767 } else
13768 continue;
13769
13770 if (1 /*type == 'c'*/) {
13771 {
13772 // Get the display pixel location of the current station
13773 int pixxc, pixyc;
13774 pixxc = r.x;
13775 pixyc = r.y;
13776
13777 // Adjust drawing size using logarithmic scale. tcvalue is
13778 // current in knots
13779 double a1 = fabs(tcvalue) * 10.;
13780 // Current values <= 0.1 knot will have no arrow
13781 a1 = wxMax(1.0, a1);
13782 double a2 = log10(a1);
13783
13784 float cscale = scale_factor * a2 * 0.3;
13785
13786 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13787 dc.SetPen(*porange_pen);
13788 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13789 cscale);
13790 // Draw text, if enabled
13791
13792 if (bDrawCurrentValues) {
13793 dc.SetFont(*pTCFont);
13794 tcvalue = toUsrSpeed(tcvalue);
13795 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13796 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13797 }
13798 }
13799 } // scale
13800 }
13801 /* This is useful for debugging the TC database
13802 else
13803 {
13804 dc.SetPen ( *porange_pen );
13805 dc.SetBrush ( *pgray_brush );
13806 dc.DrawPolygon ( 4, d );
13807 }
13808 */
13809 }
13810 lon_last = lon;
13811 lat_last = lat;
13812 }
13813 }
13814 }
13815}
13816
13817void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13818 ShowSingleTideDialog(x, y, pvIDX);
13819}
13820
13821void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13822 if (!pvIDX) return; // Validate input
13823
13824 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13825
13826 // Check if a tide dialog is already open and visible
13827 if (pCwin && pCwin->IsShown()) {
13828 // Same tide station: bring existing dialog to front (preserves user
13829 // context)
13830 if (pCwin->GetCurrentIDX() == pNewIDX) {
13831 pCwin->Raise();
13832 pCwin->SetFocus();
13833
13834 // Provide subtle visual feedback that dialog is already open
13835 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13836 return;
13837 }
13838
13839 // Different tide station: close current dialog before opening new one
13840 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13841 }
13842
13843 if (pCwin) {
13844 // This shouldn't happen but ensures clean state
13845 pCwin->Destroy();
13846 pCwin = NULL;
13847 }
13848
13849 // Create and display new tide dialog
13850 pCwin = new TCWin(this, x, y, pvIDX);
13851
13852 // Ensure the dialog is properly shown and focused
13853 if (pCwin) {
13854 pCwin->Show();
13855 pCwin->Raise();
13856 pCwin->SetFocus();
13857 }
13858}
13859
13860bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13861
13863 if (pCwin) {
13864 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13865 }
13866}
13867
13868#define NUM_CURRENT_ARROW_POINTS 9
13869static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13870 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13871 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13872 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13873
13874void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13875 double scale) {
13876 if (scale > 1e-2) {
13877 float sin_rot = sin(rot_angle * PI / 180.);
13878 float cos_rot = cos(rot_angle * PI / 180.);
13879
13880 // Move to the first point
13881
13882 float xt = CurrentArrowArray[0].x;
13883 float yt = CurrentArrowArray[0].y;
13884
13885 float xp = (xt * cos_rot) - (yt * sin_rot);
13886 float yp = (xt * sin_rot) + (yt * cos_rot);
13887 int x1 = (int)(xp * scale);
13888 int y1 = (int)(yp * scale);
13889
13890 // Walk thru the point list
13891 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13892 xt = CurrentArrowArray[ip].x;
13893 yt = CurrentArrowArray[ip].y;
13894
13895 float xp = (xt * cos_rot) - (yt * sin_rot);
13896 float yp = (xt * sin_rot) + (yt * cos_rot);
13897 int x2 = (int)(xp * scale);
13898 int y2 = (int)(yp * scale);
13899
13900 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13901
13902 x1 = x2;
13903 y1 = y2;
13904 }
13905 }
13906}
13907
13908wxString ChartCanvas::FindValidUploadPort() {
13909 wxString port;
13910 // Try to use the saved persistent upload port first
13911 if (!g_uploadConnection.IsEmpty() &&
13912 g_uploadConnection.StartsWith("Serial")) {
13913 port = g_uploadConnection;
13914 }
13915
13916 else {
13917 // If there is no persistent upload port recorded (yet)
13918 // then use the first available serial connection which has output defined.
13919 for (auto *cp : TheConnectionParams()) {
13920 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13921 port << "Serial:" << cp->Port;
13922 }
13923 }
13924 return port;
13925}
13926
13927void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13928 if (!win) return;
13929
13930 if (NULL == g_pais_query_dialog_active) {
13931 int pos_x = g_ais_query_dialog_x;
13932 int pos_y = g_ais_query_dialog_y;
13933
13934 if (g_pais_query_dialog_active) {
13935 g_pais_query_dialog_active->Destroy();
13936 g_pais_query_dialog_active = new AISTargetQueryDialog();
13937 } else {
13938 g_pais_query_dialog_active = new AISTargetQueryDialog();
13939 }
13940
13941 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13942 wxPoint(pos_x, pos_y));
13943
13944 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13945 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13946 g_pais_query_dialog_active->SetMMSI(mmsi);
13947 g_pais_query_dialog_active->UpdateText();
13948 wxSize sz = g_pais_query_dialog_active->GetSize();
13949
13950 bool b_reset_pos = false;
13951#ifdef __WXMSW__
13952 // Support MultiMonitor setups which an allow negative window positions.
13953 // If the requested window title bar does not intersect any installed
13954 // monitor, then default to simple primary monitor positioning.
13955 RECT frame_title_rect;
13956 frame_title_rect.left = pos_x;
13957 frame_title_rect.top = pos_y;
13958 frame_title_rect.right = pos_x + sz.x;
13959 frame_title_rect.bottom = pos_y + 30;
13960
13961 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13962 b_reset_pos = true;
13963#else
13964
13965 // Make sure drag bar (title bar) of window intersects wxClient Area of
13966 // screen, with a little slop...
13967 wxRect window_title_rect; // conservative estimate
13968 window_title_rect.x = pos_x;
13969 window_title_rect.y = pos_y;
13970 window_title_rect.width = sz.x;
13971 window_title_rect.height = 30;
13972
13973 wxRect ClientRect = wxGetClientDisplayRect();
13974 ClientRect.Deflate(
13975 60, 60); // Prevent the new window from being too close to the edge
13976 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13977
13978#endif
13979
13980 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13981
13982 } else {
13983 g_pais_query_dialog_active->SetMMSI(mmsi);
13984 g_pais_query_dialog_active->UpdateText();
13985 }
13986
13987 g_pais_query_dialog_active->Show();
13988}
13989
13990void ChartCanvas::ToggleCanvasQuiltMode() {
13991 bool cur_mode = GetQuiltMode();
13992
13993 if (!GetQuiltMode())
13994 SetQuiltMode(true);
13995 else if (GetQuiltMode()) {
13996 SetQuiltMode(false);
13997 g_sticky_chart = GetQuiltReferenceChartIndex();
13998 }
13999
14000 if (cur_mode != GetQuiltMode()) {
14001 SetupCanvasQuiltMode();
14002 DoCanvasUpdate();
14003 InvalidateGL();
14004 Refresh();
14005 }
14006 // TODO What to do about this?
14007 // g_bQuiltEnable = GetQuiltMode();
14008
14009 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
14010 if (ps52plib) ps52plib->GenerateStateHash();
14011
14012 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14013 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14014}
14015
14016void ChartCanvas::DoCanvasStackDelta(int direction) {
14017 if (!GetQuiltMode()) {
14018 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
14019 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
14020 if ((current_stack_index + direction) < 0) return;
14021
14022 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
14023 int new_dbIndex =
14024 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
14025
14026 if (IsChartQuiltableRef(new_dbIndex)) {
14027 ToggleCanvasQuiltMode();
14028 SelectQuiltRefdbChart(new_dbIndex);
14029 m_bpersistent_quilt = false;
14030 }
14031 } else {
14032 SelectChartFromStack(current_stack_index + direction);
14033 }
14034 } else {
14035 std::vector<int> piano_chart_index_array =
14036 GetQuiltExtendedStackdbIndexArray();
14037 int refdb = GetQuiltRefChartdbIndex();
14038
14039 // Find the ref chart in the stack
14040 int current_index = -1;
14041 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14042 if (refdb == piano_chart_index_array[i]) {
14043 current_index = i;
14044 break;
14045 }
14046 }
14047 if (current_index == -1) return;
14048
14049 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
14050 int target_family = ctet.GetChartFamily();
14051
14052 int new_index = -1;
14053 int check_index = current_index + direction;
14054 bool found = false;
14055 int check_dbIndex = -1;
14056 int new_dbIndex = -1;
14057
14058 // When quilted. switch within the same chart family
14059 while (!found &&
14060 (unsigned int)check_index < piano_chart_index_array.size() &&
14061 (check_index >= 0)) {
14062 check_dbIndex = piano_chart_index_array[check_index];
14063 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14064 if (target_family == cte.GetChartFamily()) {
14065 found = true;
14066 new_index = check_index;
14067 new_dbIndex = check_dbIndex;
14068 break;
14069 }
14070
14071 check_index += direction;
14072 }
14073
14074 if (!found) return;
14075
14076 if (!IsChartQuiltableRef(new_dbIndex)) {
14077 ToggleCanvasQuiltMode();
14078 SelectdbChart(new_dbIndex);
14079 m_bpersistent_quilt = true;
14080 } else {
14081 SelectQuiltRefChart(new_index);
14082 }
14083 }
14084
14085 // update the state of the menu items (checkmarks etc)
14086 top_frame::Get()->UpdateGlobalMenuItems();
14087 SetQuiltChartHiLiteIndex(-1);
14088
14089 ReloadVP();
14090}
14091
14092//--------------------------------------------------------------------------------------------------------
14093//
14094// Toolbar support
14095//
14096//--------------------------------------------------------------------------------------------------------
14097
14098void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
14099 // Handle the per-canvas toolbar clicks here
14100
14101 switch (event.GetId()) {
14102 case ID_ZOOMIN: {
14103 ZoomCanvasSimple(g_plus_minus_zoom_factor);
14104 break;
14105 }
14106
14107 case ID_ZOOMOUT: {
14108 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
14109 break;
14110 }
14111
14112 case ID_STKUP:
14113 DoCanvasStackDelta(1);
14114 DoCanvasUpdate();
14115 break;
14116
14117 case ID_STKDN:
14118 DoCanvasStackDelta(-1);
14119 DoCanvasUpdate();
14120 break;
14121
14122 case ID_FOLLOW: {
14123 TogglebFollow();
14124 break;
14125 }
14126
14127 case ID_CURRENT: {
14128 ShowCurrents(!GetbShowCurrent());
14129 ReloadVP();
14130 Refresh(false);
14131 break;
14132 }
14133
14134 case ID_TIDE: {
14135 ShowTides(!GetbShowTide());
14136 ReloadVP();
14137 Refresh(false);
14138 break;
14139 }
14140
14141 case ID_ROUTE: {
14142 if (0 == m_routeState) {
14143 StartRoute();
14144 } else {
14145 FinishRoute();
14146 }
14147
14148#ifdef __ANDROID__
14149 androidSetRouteAnnunciator(m_routeState == 1);
14150#endif
14151 break;
14152 }
14153
14154 case ID_AIS: {
14155 SetAISCanvasDisplayStyle(-1);
14156 break;
14157 }
14158
14159 default:
14160 break;
14161 }
14162
14163 // And then let gFrame handle the rest....
14164 event.Skip();
14165}
14166
14167void ChartCanvas::SetShowAIS(bool show) {
14168 m_bShowAIS = show;
14169 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14170 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14171}
14172
14173void ChartCanvas::SetAttenAIS(bool show) {
14174 m_bShowAISScaled = show;
14175 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14176 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14177}
14178
14179void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14180 // make some arrays to hold the dfferences between cycle steps
14181 // show all, scaled, hide all
14182 bool bShowAIS_Array[3] = {true, true, false};
14183 bool bShowScaled_Array[3] = {false, true, true};
14184 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14185 _("Attenuate less critical AIS targets"),
14186 _("Hide AIS Targets")};
14187 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14188 int ArraySize = 3;
14189 int AIS_Toolbar_Switch = 0;
14190 if (StyleIndx == -1) { // -1 means coming from toolbar button
14191 // find current state of switch
14192 for (int i = 1; i < ArraySize; i++) {
14193 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14194 (bShowScaled_Array[i] == m_bShowAISScaled))
14195 AIS_Toolbar_Switch = i;
14196 }
14197 AIS_Toolbar_Switch++; // we did click so continu with next item
14198 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14199 AIS_Toolbar_Switch++;
14200
14201 } else { // coming from menu bar.
14202 AIS_Toolbar_Switch = StyleIndx;
14203 }
14204 // make sure we are not above array
14205 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14206
14207 int AIS_Toolbar_Switch_Next =
14208 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14209 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14210 AIS_Toolbar_Switch_Next++;
14211 if (AIS_Toolbar_Switch_Next >= ArraySize)
14212 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14213
14214 // Set found values to global and member variables
14215 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14216 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14217}
14218
14219void ChartCanvas::TouchAISToolActive() {}
14220
14221void ChartCanvas::UpdateAISTBTool() {}
14222
14223//---------------------------------------------------------------------------------
14224//
14225// Compass/GPS status icon support
14226//
14227//---------------------------------------------------------------------------------
14228
14229void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14230 // Look for change in overlap or positions
14231 bool b_update = false;
14232 int cc1_edge_comp = 2;
14233 wxRect rect = m_Compass->GetRect();
14234 wxSize parent_size = GetSize();
14235
14236 parent_size *= m_displayScale;
14237
14238 // check to see if it would overlap if it was in its home position (upper
14239 // right)
14240 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14241 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14242 wxRect compass_rect(compass_pt, rect.GetSize());
14243
14244 m_Compass->Move(compass_pt);
14245
14246 if (m_Compass && m_Compass->IsShown())
14247 m_Compass->UpdateStatus(b_force_new | b_update);
14248
14249 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14250 scaler = wxMax(scaler, 1.0);
14251 wxPoint note_point = wxPoint(
14252 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14253 if (m_notification_button) {
14254 m_notification_button->Move(note_point);
14255 m_notification_button->UpdateStatus();
14256 }
14257
14258 if (b_force_new | b_update) Refresh();
14259}
14260
14261void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14262 ChartTypeEnum New_Type,
14263 ChartFamilyEnum New_Family) {
14264 if (!GetpCurrentStack()) return;
14265 if (!ChartData) return;
14266
14267 if (index < GetpCurrentStack()->nEntry) {
14268 // Open the new chart
14269 ChartBase *pTentative_Chart;
14270 pTentative_Chart = ChartData->OpenStackChartConditional(
14271 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14272
14273 if (pTentative_Chart) {
14274 if (m_singleChart) m_singleChart->Deactivate();
14275
14276 m_singleChart = pTentative_Chart;
14277 m_singleChart->Activate();
14278
14279 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14280 GetpCurrentStack(), m_singleChart->GetFullPath());
14281 }
14282
14283 // Setup the view
14284 double zLat, zLon;
14285 if (m_bFollow) {
14286 zLat = gLat;
14287 zLon = gLon;
14288 } else {
14289 zLat = m_vLat;
14290 zLon = m_vLon;
14291 }
14292
14293 double best_scale_ppm = GetBestVPScale(m_singleChart);
14294 double rotation = GetVPRotation();
14295 double oldskew = GetVPSkew();
14296 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14297
14298 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14299 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14300 if (fabs(newskew) > 0.0001) rotation = newskew;
14301 }
14302
14303 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14304
14305 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14306 }
14307
14308 // refresh Piano
14309 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14310 if (idx < 0) return;
14311
14312 std::vector<int> piano_active_chart_index_array;
14313 piano_active_chart_index_array.push_back(
14314 GetpCurrentStack()->GetCurrentEntrydbIndex());
14315 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14316}
14317
14318void ChartCanvas::SelectdbChart(int dbindex) {
14319 if (!GetpCurrentStack()) return;
14320 if (!ChartData) return;
14321
14322 if (dbindex >= 0) {
14323 // Open the new chart
14324 ChartBase *pTentative_Chart;
14325 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14326
14327 if (pTentative_Chart) {
14328 if (m_singleChart) m_singleChart->Deactivate();
14329
14330 m_singleChart = pTentative_Chart;
14331 m_singleChart->Activate();
14332
14333 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14334 GetpCurrentStack(), m_singleChart->GetFullPath());
14335 }
14336
14337 // Setup the view
14338 double zLat, zLon;
14339 if (m_bFollow) {
14340 zLat = gLat;
14341 zLon = gLon;
14342 } else {
14343 zLat = m_vLat;
14344 zLon = m_vLon;
14345 }
14346
14347 double best_scale_ppm = GetBestVPScale(m_singleChart);
14348
14349 if (m_singleChart)
14350 SetViewPoint(zLat, zLon, best_scale_ppm,
14351 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14352
14353 // SetChartUpdatePeriod( );
14354
14355 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14356 }
14357
14358 // TODO refresh_Piano();
14359}
14360
14361void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14362 double target_scale = GetVP().view_scale_ppm;
14363
14364 if (!GetQuiltMode()) {
14365 if (GetpCurrentStack()) {
14366 int stack_index = -1;
14367 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14368 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14369 if (check_dbIndex < 0) continue;
14370 const ChartTableEntry &cte =
14371 ChartData->GetChartTableEntry(check_dbIndex);
14372 if (type == cte.GetChartType()) {
14373 stack_index = i;
14374 break;
14375 } else if (family == cte.GetChartFamily()) {
14376 stack_index = i;
14377 break;
14378 }
14379 }
14380
14381 if (stack_index >= 0) {
14382 SelectChartFromStack(stack_index);
14383 }
14384 }
14385 } else {
14386 int sel_dbIndex = -1;
14387 std::vector<int> piano_chart_index_array =
14388 GetQuiltExtendedStackdbIndexArray();
14389 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14390 int check_dbIndex = piano_chart_index_array[i];
14391 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14392 if (type == cte.GetChartType()) {
14393 if (IsChartQuiltableRef(check_dbIndex)) {
14394 sel_dbIndex = check_dbIndex;
14395 break;
14396 }
14397 } else if (family == cte.GetChartFamily()) {
14398 if (IsChartQuiltableRef(check_dbIndex)) {
14399 sel_dbIndex = check_dbIndex;
14400 break;
14401 }
14402 }
14403 }
14404
14405 if (sel_dbIndex >= 0) {
14406 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14407 // Re-qualify the quilt reference chart selection
14408 AdjustQuiltRefChart();
14409 }
14410
14411 // Now reset the scale to the target...
14412 SetVPScale(target_scale);
14413 }
14414
14415 SetQuiltChartHiLiteIndex(-1);
14416
14417 ReloadVP();
14418}
14419
14420bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14421 return std::find(m_tile_yesshow_index_array.begin(),
14422 m_tile_yesshow_index_array.end(),
14423 index) != m_tile_yesshow_index_array.end();
14424}
14425
14426bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14427 return std::find(m_tile_noshow_index_array.begin(),
14428 m_tile_noshow_index_array.end(),
14429 index) != m_tile_noshow_index_array.end();
14430}
14431
14432void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14433 if (std::find(m_tile_noshow_index_array.begin(),
14434 m_tile_noshow_index_array.end(),
14435 index) == m_tile_noshow_index_array.end()) {
14436 m_tile_noshow_index_array.push_back(index);
14437 }
14438}
14439
14440//-------------------------------------------------------------------------------------------------------
14441//
14442// Piano support
14443//
14444//-------------------------------------------------------------------------------------------------------
14445
14446void ChartCanvas::HandlePianoClick(
14447 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14448 if (g_options && g_options->IsShown())
14449 return; // Piano might be invalid due to chartset updates.
14450 if (!m_pCurrentStack) return;
14451 if (!ChartData) return;
14452
14453 // stop movement or on slow computer we may get something like :
14454 // zoom out with the wheel (timer is set)
14455 // quickly click and display a chart, which may zoom in
14456 // but the delayed timer fires first and it zooms out again!
14457 StopMovement();
14458
14459 // When switching by piano key click, we may appoint the new target chart to
14460 // be any chart in the composite array.
14461 // As an improvement to UX, find the chart that is "closest" to the current
14462 // vp,
14463 // and select that chart. This will cause a jump to the centroid of that
14464 // chart
14465
14466 double distance = 25000; // RTW
14467 int closest_index = -1;
14468 for (int chart_index : selected_dbIndex_array) {
14469 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14470 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14471 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14472
14473 // measure distance as Manhattan style
14474 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14475 if (test_distance < distance) {
14476 distance = test_distance;
14477 closest_index = chart_index;
14478 }
14479 }
14480
14481 int selected_dbIndex = selected_dbIndex_array[0];
14482 if (closest_index >= 0) selected_dbIndex = closest_index;
14483
14484 if (!GetQuiltMode()) {
14485 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14486 if (IsChartQuiltableRef(selected_dbIndex)) {
14487 ToggleCanvasQuiltMode();
14488 SelectQuiltRefdbChart(selected_dbIndex);
14489 m_bpersistent_quilt = false;
14490 } else {
14491 SelectChartFromStack(selected_index);
14492 }
14493 } else {
14494 SelectChartFromStack(selected_index);
14495 g_sticky_chart = selected_dbIndex;
14496 }
14497
14498 if (m_singleChart)
14499 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14500 } else {
14501 // Handle MBTiles overlays first
14502 // Left click simply toggles the noshow array index entry
14503 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14504 bool bfound = false;
14505 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14506 if (m_tile_noshow_index_array[i] ==
14507 selected_dbIndex) { // chart is in the noshow list
14508 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14509 i); // erase it
14510 bfound = true;
14511 break;
14512 }
14513 }
14514 if (!bfound) {
14515 m_tile_noshow_index_array.push_back(selected_dbIndex);
14516 }
14517
14518 // If not already present, add this tileset to the "yes_show" array.
14519 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14520 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14521 }
14522
14523 else {
14524 if (IsChartQuiltableRef(selected_dbIndex)) {
14525 // if( ChartData ) ChartData->PurgeCache();
14526
14527 // If the chart is a vector chart, and of very large scale,
14528 // then we had better set the new scale directly to avoid excessive
14529 // underzoom on, eg, Inland ENCs
14530 bool set_scale = false;
14531 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14532 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14533 set_scale = true;
14534 }
14535 }
14536
14537 if (!set_scale) {
14538 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14539 } else {
14540 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14541
14542 // Adjust scale so that the selected chart is underzoomed/overzoomed
14543 // by a controlled amount
14544 ChartBase *pc =
14545 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14546 if (pc) {
14547 double proposed_scale_onscreen =
14549
14550 if (g_bPreserveScaleOnX) {
14551 proposed_scale_onscreen =
14552 wxMin(proposed_scale_onscreen,
14553 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14554 GetCanvasWidth()));
14555 } else {
14556 proposed_scale_onscreen =
14557 wxMin(proposed_scale_onscreen,
14558 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14559 GetCanvasWidth()));
14560
14561 proposed_scale_onscreen =
14562 wxMax(proposed_scale_onscreen,
14563 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14565 }
14566
14567 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14568 }
14569 }
14570 } else {
14571 ToggleCanvasQuiltMode();
14572 SelectdbChart(selected_dbIndex);
14573 m_bpersistent_quilt = true;
14574 }
14575 }
14576 }
14577
14578 SetQuiltChartHiLiteIndex(-1);
14579 // update the state of the menu items (checkmarks etc)
14580 top_frame::Get()->UpdateGlobalMenuItems();
14581 HideChartInfoWindow();
14582 DoCanvasUpdate();
14583 ReloadVP(); // Pick up the new selections
14584}
14585
14586void ChartCanvas::HandlePianoRClick(
14587 int x, int y, int selected_index,
14588 const std::vector<int> &selected_dbIndex_array) {
14589 if (g_options && g_options->IsShown())
14590 return; // Piano might be invalid due to chartset updates.
14591 if (!GetpCurrentStack()) return;
14592
14593 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14594 UpdateCanvasControlBar();
14595
14596 SetQuiltChartHiLiteIndex(-1);
14597}
14598
14599void ChartCanvas::HandlePianoRollover(
14600 int selected_index, const std::vector<int> &selected_dbIndex_array,
14601 int n_charts, int scale) {
14602 if (g_options && g_options->IsShown())
14603 return; // Piano might be invalid due to chartset updates.
14604 if (!GetpCurrentStack()) return;
14605 if (!ChartData) return;
14606
14607 if (ChartData->IsBusy()) return;
14608
14609 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14610
14611 if (!GetQuiltMode()) {
14612 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14613 } else {
14614 // Select the correct vector
14615 std::vector<int> piano_chart_index_array;
14616 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14617 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14618 if ((GetpCurrentStack()->nEntry > 1) ||
14619 (piano_chart_index_array.size() >= 1)) {
14620 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14621
14622 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14623 ReloadVP(false); // no VP adjustment allowed
14624 } else if (GetpCurrentStack()->nEntry == 1) {
14625 const ChartTableEntry &cte =
14626 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14627 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14628 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14629 ReloadVP(false);
14630 } else if ((-1 == selected_index) &&
14631 (0 == selected_dbIndex_array.size())) {
14632 ShowChartInfoWindow(key_location.x, -1);
14633 }
14634 }
14635 } else {
14636 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14637
14638 if ((GetpCurrentStack()->nEntry > 1) ||
14639 (piano_chart_index_array.size() >= 1)) {
14640 if (n_charts > 1)
14641 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14642 selected_dbIndex_array);
14643 else if (n_charts == 1)
14644 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14645
14646 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14647 ReloadVP(false); // no VP adjustment allowed
14648 }
14649 }
14650 }
14651}
14652
14653void ChartCanvas::ClearPianoRollover() {
14654 ClearQuiltChartHiLiteIndexArray();
14655 ShowChartInfoWindow(0, -1);
14656 std::vector<int> vec;
14657 ShowCompositeInfoWindow(0, 0, 0, vec);
14658 ReloadVP(false);
14659}
14660
14661void ChartCanvas::UpdateCanvasControlBar() {
14662 if (m_pianoFrozen) return;
14663
14664 if (!GetpCurrentStack()) return;
14665 if (!ChartData) return;
14666 if (!g_bShowChartBar) return;
14667
14668 int sel_type = -1;
14669 int sel_family = -1;
14670
14671 std::vector<int> piano_chart_index_array;
14672 std::vector<int> empty_piano_chart_index_array;
14673
14674 wxString old_hash = m_Piano->GetStoredHash();
14675
14676 if (GetQuiltMode()) {
14677 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14678 GetQuiltFullScreendbIndexArray());
14679
14680 std::vector<int> piano_active_chart_index_array =
14681 GetQuiltCandidatedbIndexArray();
14682 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14683
14684 std::vector<int> piano_eclipsed_chart_index_array =
14685 GetQuiltEclipsedStackdbIndexArray();
14686 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14687
14688 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14689 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14690
14691 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14692 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14693 } else {
14694 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14695 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14696 // TODO refresh_Piano();
14697
14698 if (m_singleChart) {
14699 sel_type = m_singleChart->GetChartType();
14700 sel_family = m_singleChart->GetChartFamily();
14701 }
14702 }
14703
14704 // Set up the TMerc and Skew arrays
14705 std::vector<int> piano_skew_chart_index_array;
14706 std::vector<int> piano_tmerc_chart_index_array;
14707 std::vector<int> piano_poly_chart_index_array;
14708
14709 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14710 const ChartTableEntry &ctei =
14711 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14712 double skew_norm = ctei.GetChartSkew();
14713 if (skew_norm > 180.) skew_norm -= 360.;
14714
14715 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14716 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14717
14718 // Polyconic skewed charts should show as skewed
14719 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14720 if (fabs(skew_norm) > 1.)
14721 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14722 else
14723 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14724 } else if (fabs(skew_norm) > 1.)
14725 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14726 }
14727 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14728 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14729 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14730
14731 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14732 if (new_hash != old_hash) {
14733 m_Piano->FormatKeys();
14734 HideChartInfoWindow();
14735 m_Piano->ResetRollover();
14736 SetQuiltChartHiLiteIndex(-1);
14737 m_brepaint_piano = true;
14738 }
14739
14740 // Create a bitmask int that describes what Family/Type of charts are shown in
14741 // the bar, and notify the platform.
14742 int mask = 0;
14743 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14744 const ChartTableEntry &ctei =
14745 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14746 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14747 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14748 if (e == CHART_FAMILY_RASTER) mask |= 1;
14749 if (e == CHART_FAMILY_VECTOR) {
14750 if (t == CHART_TYPE_CM93COMP)
14751 mask |= 4;
14752 else
14753 mask |= 2;
14754 }
14755 }
14756
14757 wxString s_indicated;
14758 if (sel_type == CHART_TYPE_CM93COMP)
14759 s_indicated = "cm93";
14760 else {
14761 if (sel_family == CHART_FAMILY_RASTER)
14762 s_indicated = "raster";
14763 else if (sel_family == CHART_FAMILY_VECTOR)
14764 s_indicated = "vector";
14765 }
14766
14767 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14768}
14769
14770void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14771
14772void ChartCanvas::PianoPopupMenu(
14773 int x, int y, int selected_index,
14774 const std::vector<int> &selected_dbIndex_array) {
14775 if (!GetpCurrentStack()) return;
14776
14777 // No context menu if quilting is disabled
14778 if (!GetQuiltMode()) return;
14779
14780 m_piano_ctx_menu = new wxMenu();
14781
14782 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14783 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14784 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14785 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14786 } else {
14787 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14788 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14789 // wxEVT_COMMAND_MENU_SELECTED,
14790 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14791
14792 menu_selected_dbIndex = selected_dbIndex_array[0];
14793 menu_selected_index = selected_index;
14794
14795 // Search the no-show array
14796 bool b_is_in_noshow = false;
14797 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14798 if (m_quilt_noshow_index_array[i] ==
14799 menu_selected_dbIndex) // chart is in the noshow list
14800 {
14801 b_is_in_noshow = true;
14802 break;
14803 }
14804 }
14805
14806 if (b_is_in_noshow) {
14807 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14808 _("Show This Chart"));
14809 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14810 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14811 } else if (GetpCurrentStack()->nEntry > 1) {
14812 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14813 _("Hide This Chart"));
14814 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14815 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14816 }
14817 }
14818
14819 wxPoint pos = wxPoint(x, y - 30);
14820
14821 // Invoke the drop-down menu
14822 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14823 PopupMenu(m_piano_ctx_menu, pos);
14824
14825 delete m_piano_ctx_menu;
14826 m_piano_ctx_menu = NULL;
14827
14828 HideChartInfoWindow();
14829 m_Piano->ResetRollover();
14830
14831 SetQuiltChartHiLiteIndex(-1);
14832 ClearQuiltChartHiLiteIndexArray();
14833
14834 ReloadVP();
14835}
14836
14837void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14838 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14839 if (m_quilt_noshow_index_array[i] ==
14840 menu_selected_dbIndex) // chart is in the noshow list
14841 {
14842 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14843 break;
14844 }
14845 }
14846}
14847
14848void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14849 if (!GetpCurrentStack()) return;
14850 if (!ChartData) return;
14851
14852 RemoveChartFromQuilt(menu_selected_dbIndex);
14853
14854 // It could happen that the chart being disabled is the reference
14855 // chart....
14856 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14857 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14858
14859 int i = menu_selected_index + 1; // select next smaller scale chart
14860 bool b_success = false;
14861 while (i < GetpCurrentStack()->nEntry - 1) {
14862 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14863 if (type == ChartData->GetDBChartType(dbIndex)) {
14864 SelectQuiltRefChart(i);
14865 b_success = true;
14866 break;
14867 }
14868 i++;
14869 }
14870
14871 // If that did not work, try to select the next larger scale compatible
14872 // chart
14873 if (!b_success) {
14874 i = menu_selected_index - 1;
14875 while (i > 0) {
14876 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14877 if (type == ChartData->GetDBChartType(dbIndex)) {
14878 SelectQuiltRefChart(i);
14879 b_success = true;
14880 break;
14881 }
14882 i--;
14883 }
14884 }
14885 }
14886}
14887
14888void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14889 // Remove the item from the list (if it appears) to avoid multiple addition
14890 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14891 if (m_quilt_noshow_index_array[i] ==
14892 dbIndex) // chart is already in the noshow list
14893 {
14894 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14895 break;
14896 }
14897 }
14898
14899 m_quilt_noshow_index_array.push_back(dbIndex);
14900}
14901
14902bool ChartCanvas::UpdateS52State() {
14903 bool retval = false;
14904
14905 if (ps52plib) {
14906 ps52plib->SetShowS57Text(m_encShowText);
14907 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14908 ps52plib->m_bShowSoundg = m_encShowDepth;
14909 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14910 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14911
14912 // Lights
14913 if (!m_encShowLights) // On, going off
14914 ps52plib->AddObjNoshow("LIGHTS");
14915 else // Off, going on
14916 ps52plib->RemoveObjNoshow("LIGHTS");
14917 ps52plib->SetLightsOff(!m_encShowLights);
14918 ps52plib->m_bExtendLightSectors = true;
14919
14920 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14921 ps52plib->SetAnchorOn(m_encShowAnchor);
14922 ps52plib->SetQualityOfData(m_encShowDataQual);
14923 }
14924
14925 return retval;
14926}
14927
14928void ChartCanvas::SetShowENCDataQual(bool show) {
14929 m_encShowDataQual = show;
14930 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14931 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14932
14933 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14934}
14935
14936void ChartCanvas::SetShowENCText(bool show) {
14937 m_encShowText = show;
14938 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14939 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14940
14941 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14942}
14943
14944void ChartCanvas::SetENCDisplayCategory(int category) {
14945 m_encDisplayCategory = category;
14946 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14947}
14948
14949void ChartCanvas::SetShowENCDepth(bool show) {
14950 m_encShowDepth = show;
14951 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14952 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14953
14954 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14955}
14956
14957void ChartCanvas::SetShowENCLightDesc(bool show) {
14958 m_encShowLightDesc = show;
14959 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14960 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14961
14962 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14963}
14964
14965void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14966 m_encShowBuoyLabels = show;
14967 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14968}
14969
14970void ChartCanvas::SetShowENCLights(bool show) {
14971 m_encShowLights = show;
14972 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14973 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14974
14975 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14976}
14977
14978void ChartCanvas::SetShowENCAnchor(bool show) {
14979 m_encShowAnchor = show;
14980 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14981 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14982
14983 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14984}
14985
14986wxRect ChartCanvas::GetMUIBarRect() {
14987 wxRect rv;
14988 if (m_muiBar) {
14989 rv = m_muiBar->GetRect();
14990 }
14991
14992 return rv;
14993}
14994
14995void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14996 if (!GetAlertString().IsEmpty()) {
14997 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14998 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14999
15000 dc.SetFont(*pfont);
15001 dc.SetPen(*wxTRANSPARENT_PEN);
15002
15003 dc.SetBrush(wxColour(243, 229, 47));
15004 int w, h;
15005 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
15006 h += 2;
15007 // int yp = vp.pix_height - 20 - h;
15008
15009 wxRect sbr = GetScaleBarRect();
15010 int xp = sbr.x + sbr.width + 10;
15011 int yp = (sbr.y + sbr.height) - h;
15012
15013 int wdraw = w + 10;
15014 dc.DrawRectangle(xp, yp, wdraw, h);
15015 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
15016 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
15017 }
15018}
15019
15020//--------------------------------------------------------------------------------------------------------
15021// Screen Brightness Control Support Routines
15022//
15023//--------------------------------------------------------------------------------------------------------
15024
15025#ifdef __UNIX__
15026#define BRIGHT_XCALIB
15027#define __OPCPN_USEICC__
15028#endif
15029
15030#ifdef __OPCPN_USEICC__
15031int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15032 double co_green, double co_blue);
15033
15034wxString temp_file_name;
15035#endif
15036
15037#if 0
15038class ocpnCurtain: public wxDialog
15039{
15040 DECLARE_CLASS( ocpnCurtain )
15041 DECLARE_EVENT_TABLE()
15042
15043public:
15044 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
15045 ~ocpnCurtain( );
15046 bool ProcessEvent(wxEvent& event);
15047
15048};
15049
15050IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
15051
15052BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
15053END_EVENT_TABLE()
15054
15055ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
15056{
15057 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
15058}
15059
15060ocpnCurtain::~ocpnCurtain()
15061{
15062}
15063
15064bool ocpnCurtain::ProcessEvent(wxEvent& event)
15065{
15066 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
15067 return GetParent()->GetEventHandler()->ProcessEvent(event);
15068}
15069#endif
15070
15071#ifdef _WIN32
15072#include <windows.h>
15073
15074HMODULE hGDI32DLL;
15075typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15076typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
15077SetDeviceGammaRamp_ptr_type
15078 g_pSetDeviceGammaRamp; // the API entry points in the dll
15079GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
15080
15081WORD *g_pSavedGammaMap;
15082
15083#endif
15084
15085int InitScreenBrightness() {
15086#ifdef _WIN32
15087#ifdef ocpnUSE_GL
15088 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15089 HDC hDC;
15090 BOOL bbr;
15091
15092 if (NULL == hGDI32DLL) {
15093 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
15094
15095 if (NULL != hGDI32DLL) {
15096 // Get the entry points of the required functions
15097 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15098 hGDI32DLL, "SetDeviceGammaRamp");
15099 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15100 hGDI32DLL, "GetDeviceGammaRamp");
15101
15102 // If the functions are not found, unload the DLL and return false
15103 if ((NULL == g_pSetDeviceGammaRamp) ||
15104 (NULL == g_pGetDeviceGammaRamp)) {
15105 FreeLibrary(hGDI32DLL);
15106 hGDI32DLL = NULL;
15107 return 0;
15108 }
15109 }
15110 }
15111
15112 // Interface is ready, so....
15113 // Get some storage
15114 if (!g_pSavedGammaMap) {
15115 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
15116
15117 hDC = GetDC(NULL); // Get the full screen DC
15118 bbr = g_pGetDeviceGammaRamp(
15119 hDC, g_pSavedGammaMap); // Get the existing ramp table
15120 ReleaseDC(NULL, hDC); // Release the DC
15121 }
15122
15123 // On Windows hosts, try to adjust the registry to allow full range
15124 // setting of Gamma table This is an undocumented Windows hack.....
15125 wxRegKey *pRegKey = new wxRegKey(
15126 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
15127 "NT\\CurrentVersion\\ICM");
15128 if (!pRegKey->Exists()) pRegKey->Create();
15129 pRegKey->SetValue("GdiIcmGammaRange", 256);
15130
15131 g_brightness_init = true;
15132 return 1;
15133 }
15134#endif
15135
15136 {
15137 if (NULL == g_pcurtain) {
15138 if (top_frame::Get()->CanSetTransparent()) {
15139 // Build the curtain window
15140 g_pcurtain = new wxDialog(top_frame::Get()->GetPrimaryCanvasWindow(),
15141 -1, "", wxPoint(0, 0), ::wxGetDisplaySize(),
15142 wxNO_BORDER | wxTRANSPARENT_WINDOW |
15143 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15144
15145 // g_pcurtain = new ocpnCurtain(gFrame,
15146 // wxPoint(0,0),::wxGetDisplaySize(),
15147 // wxNO_BORDER | wxTRANSPARENT_WINDOW
15148 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
15149
15150 g_pcurtain->Hide();
15151
15152 HWND hWnd = GetHwndOf(g_pcurtain);
15153 SetWindowLong(hWnd, GWL_EXSTYLE,
15154 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15155 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15156 g_pcurtain->SetTransparent(0);
15157
15158 g_pcurtain->Maximize();
15159 g_pcurtain->Show();
15160
15161 // All of this is obtuse, but necessary for Windows...
15162 g_pcurtain->Enable();
15163 g_pcurtain->Disable();
15164
15165 top_frame::Get()->Disable();
15166 top_frame::Get()->Enable();
15167 // SetFocus();
15168 }
15169 }
15170 g_brightness_init = true;
15171
15172 return 1;
15173 }
15174#else
15175 // Look for "xcalib" application
15176 wxString cmd("xcalib -version");
15177
15178 wxArrayString output;
15179 long r = wxExecute(cmd, output);
15180 if (0 != r)
15181 wxLogMessage(
15182 " External application \"xcalib\" not found. Screen brightness "
15183 "not changed.");
15184
15185 g_brightness_init = true;
15186 return 0;
15187#endif
15188}
15189
15190int RestoreScreenBrightness() {
15191#ifdef _WIN32
15192
15193 if (g_pSavedGammaMap) {
15194 HDC hDC = GetDC(NULL); // Get the full screen DC
15195 g_pSetDeviceGammaRamp(hDC,
15196 g_pSavedGammaMap); // Restore the saved ramp table
15197 ReleaseDC(NULL, hDC); // Release the DC
15198
15199 free(g_pSavedGammaMap);
15200 g_pSavedGammaMap = NULL;
15201 }
15202
15203 if (g_pcurtain) {
15204 g_pcurtain->Close();
15205 g_pcurtain->Destroy();
15206 g_pcurtain = NULL;
15207 }
15208
15209 g_brightness_init = false;
15210 return 1;
15211
15212#endif
15213
15214#ifdef BRIGHT_XCALIB
15215 if (g_brightness_init) {
15216 wxString cmd;
15217 cmd = "xcalib -clear";
15218 wxExecute(cmd, wxEXEC_ASYNC);
15219 g_brightness_init = false;
15220 }
15221
15222 return 1;
15223#endif
15224
15225 return 0;
15226}
15227
15228// Set brightness. [0..100]
15229int SetScreenBrightness(int brightness) {
15230#ifdef _WIN32
15231
15232 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15233 // some (most modern?) versions of gdi32.dll Load the required library dll,
15234 // if not already in place
15235#ifdef ocpnUSE_GL
15236 if (top_frame::Get()->GetWxGlCanvas() && g_bopengl) {
15237 if (g_pcurtain) {
15238 g_pcurtain->Close();
15239 g_pcurtain->Destroy();
15240 g_pcurtain = NULL;
15241 }
15242
15243 InitScreenBrightness();
15244
15245 if (NULL == hGDI32DLL) {
15246 // Unicode stuff.....
15247 wchar_t wdll_name[80];
15248 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15249 LPCWSTR cstr = wdll_name;
15250
15251 hGDI32DLL = LoadLibrary(cstr);
15252
15253 if (NULL != hGDI32DLL) {
15254 // Get the entry points of the required functions
15255 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15256 hGDI32DLL, "SetDeviceGammaRamp");
15257 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15258 hGDI32DLL, "GetDeviceGammaRamp");
15259
15260 // If the functions are not found, unload the DLL and return false
15261 if ((NULL == g_pSetDeviceGammaRamp) ||
15262 (NULL == g_pGetDeviceGammaRamp)) {
15263 FreeLibrary(hGDI32DLL);
15264 hGDI32DLL = NULL;
15265 return 0;
15266 }
15267 }
15268 }
15269
15270 HDC hDC = GetDC(NULL); // Get the full screen DC
15271
15272 /*
15273 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15274 if (cmcap != CM_GAMMA_RAMP)
15275 {
15276 wxLogMessage(" Video hardware does not support brightness control by
15277 gamma ramp adjustment."); return false;
15278 }
15279 */
15280
15281 int increment = brightness * 256 / 100;
15282
15283 // Build the Gamma Ramp table
15284 WORD GammaTable[3][256];
15285
15286 int table_val = 0;
15287 for (int i = 0; i < 256; i++) {
15288 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15289 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15290 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15291
15292 table_val += increment;
15293
15294 if (table_val > 65535) table_val = 65535;
15295 }
15296
15297 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15298 ReleaseDC(NULL, hDC); // Release the DC
15299
15300 return 1;
15301 }
15302#endif
15303
15304 {
15305 if (g_pSavedGammaMap) {
15306 HDC hDC = GetDC(NULL); // Get the full screen DC
15307 g_pSetDeviceGammaRamp(hDC,
15308 g_pSavedGammaMap); // Restore the saved ramp table
15309 ReleaseDC(NULL, hDC); // Release the DC
15310 }
15311
15312 if (brightness < 100) {
15313 if (NULL == g_pcurtain) InitScreenBrightness();
15314
15315 if (g_pcurtain) {
15316 int sbrite = wxMax(1, brightness);
15317 sbrite = wxMin(100, sbrite);
15318
15319 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15320 }
15321 } else {
15322 if (g_pcurtain) {
15323 g_pcurtain->Close();
15324 g_pcurtain->Destroy();
15325 g_pcurtain = NULL;
15326 }
15327 }
15328
15329 return 1;
15330 }
15331
15332#endif
15333
15334#ifdef BRIGHT_XCALIB
15335
15336 if (!g_brightness_init) {
15337 last_brightness = 100;
15338 g_brightness_init = true;
15339 temp_file_name = wxFileName::CreateTempFileName("");
15340 InitScreenBrightness();
15341 }
15342
15343#ifdef __OPCPN_USEICC__
15344 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15345 // desired, and then activate this temporary profile using xcalib <filename>
15346 if (!CreateSimpleICCProfileFile(
15347 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15348 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15349 wxString cmd("xcalib ");
15350 cmd += temp_file_name;
15351
15352 wxExecute(cmd, wxEXEC_ASYNC);
15353 }
15354
15355#else
15356 // Or, use "xcalib -co" to set overall contrast value
15357 // This is not as nice, since the -co parameter wants to be a fraction of
15358 // the current contrast, and values greater than 100 are not allowed. As a
15359 // result, increases of contrast must do a "-clear" step first, which
15360 // produces objectionable flashing.
15361 if (brightness > last_brightness) {
15362 wxString cmd;
15363 cmd = "xcalib -clear";
15364 wxExecute(cmd, wxEXEC_ASYNC);
15365
15366 ::wxMilliSleep(10);
15367
15368 int brite_adj = wxMax(1, brightness);
15369 cmd.Printf("xcalib -co %2d -a", brite_adj);
15370 wxExecute(cmd, wxEXEC_ASYNC);
15371 } else {
15372 int brite_adj = wxMax(1, brightness);
15373 int factor = (brite_adj * 100) / last_brightness;
15374 factor = wxMax(1, factor);
15375 wxString cmd;
15376 cmd.Printf("xcalib -co %2d -a", factor);
15377 wxExecute(cmd, wxEXEC_ASYNC);
15378 }
15379
15380#endif
15381
15382 last_brightness = brightness;
15383
15384#endif
15385
15386 return 0;
15387}
15388
15389#ifdef __OPCPN_USEICC__
15390
15391#define MLUT_TAG 0x6d4c5554L
15392#define VCGT_TAG 0x76636774L
15393
15394int GetIntEndian(unsigned char *s) {
15395 int ret;
15396 unsigned char *p;
15397 int i;
15398
15399 p = (unsigned char *)&ret;
15400
15401 if (1)
15402 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15403 else
15404 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15405
15406 return ret;
15407}
15408
15409unsigned short GetShortEndian(unsigned char *s) {
15410 unsigned short ret;
15411 unsigned char *p;
15412 int i;
15413
15414 p = (unsigned char *)&ret;
15415
15416 if (1)
15417 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15418 else
15419 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15420
15421 return ret;
15422}
15423
15424// Create a very simple Gamma correction file readable by xcalib
15425int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15426 double co_green, double co_blue) {
15427 FILE *fp;
15428
15429 if (file_name) {
15430 fp = fopen(file_name, "wb");
15431 if (!fp) return -1; /* file can not be created */
15432 } else
15433 return -1; /* filename char pointer not valid */
15434
15435 // Write header
15436 char header[128];
15437 for (int i = 0; i < 128; i++) header[i] = 0;
15438
15439 fwrite(header, 128, 1, fp);
15440
15441 // Num tags
15442 int numTags0 = 1;
15443 int numTags = GetIntEndian((unsigned char *)&numTags0);
15444 fwrite(&numTags, 1, 4, fp);
15445
15446 int tagName0 = VCGT_TAG;
15447 int tagName = GetIntEndian((unsigned char *)&tagName0);
15448 fwrite(&tagName, 1, 4, fp);
15449
15450 int tagOffset0 = 128 + 4 * sizeof(int);
15451 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15452 fwrite(&tagOffset, 1, 4, fp);
15453
15454 int tagSize0 = 1;
15455 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15456 fwrite(&tagSize, 1, 4, fp);
15457
15458 fwrite(&tagName, 1, 4, fp); // another copy of tag
15459
15460 fwrite(&tagName, 1, 4, fp); // dummy
15461
15462 // Table type
15463
15464 /* VideoCardGammaTable (The simplest type) */
15465 int gammatype0 = 0;
15466 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15467 fwrite(&gammatype, 1, 4, fp);
15468
15469 int numChannels0 = 3;
15470 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15471 fwrite(&numChannels, 1, 2, fp);
15472
15473 int numEntries0 = 256;
15474 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15475 fwrite(&numEntries, 1, 2, fp);
15476
15477 int entrySize0 = 1;
15478 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15479 fwrite(&entrySize, 1, 2, fp);
15480
15481 unsigned char ramp[256];
15482
15483 // Red ramp
15484 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15485 fwrite(ramp, 256, 1, fp);
15486
15487 // Green ramp
15488 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15489 fwrite(ramp, 256, 1, fp);
15490
15491 // Blue ramp
15492 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15493 fwrite(ramp, 256, 1, fp);
15494
15495 fclose(fp);
15496
15497 return 0;
15498}
15499#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_122.cpp:30
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:13862
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:11887
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:13821
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:8044
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7850
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:13860
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:13817
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:10296
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:500
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
Locked down interface published in 5.14.1.
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:190
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.