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/own_ship.h"
58#include "model/plugin_comm.h"
59#include "model/route.h"
60#include "model/routeman.h"
61#include "model/select.h"
62#include "model/select_item.h"
63#include "model/track.h"
64
65#include "ais.h"
68#include "canvas_config.h"
69#include "canvas_menu.h"
70#include "canvas_options.h"
71#include "chartdb.h"
72#include "chartimg.h"
73#include "chcanv.h"
74#include "ch_info_win.h"
75#include "cm93.h" // for chart outline draw
76#include "compass.h"
77#include "concanv.h"
78#include "detail_slider.h"
79#include "hotkeys_dlg.h"
80#include "font_mgr.h"
81#include "gl_texture_descr.h"
82#include "go_to_position_dlg.h"
83#include "gshhs.h"
84#include "ienc_toolbar.h"
85#include "kml.h"
86#include "line_clip.h"
87#include "mark_info.h"
88#include "mbtiles.h"
89#include "mui_bar.h"
90#include "navutil.h"
91#include "ocpn_aui_manager.h"
92#include "ocpndc.h"
93#include "ocpn_frame.h"
94#include "ocpn_pixel.h"
95#include "ocpn_region.h"
96#include "options.h"
97#include "piano.h"
98#include "pluginmanager.h"
99#include "quilt.h"
100#include "route_gui.h"
101#include "routemanagerdialog.h"
102#include "route_point_gui.h"
103#include "route_prop_dlg_impl.h"
104#include "s52plib.h"
105#include "s52utils.h"
106#include "s57_query_dlg.h"
107#include "s57chart.h" // for ArrayOfS57Obj
108#include "shapefile_basemap.h"
109#include "styles.h"
110#include "tcmgr.h"
111#include "tc_win.h"
112#include "thumbwin.h"
113#include "tide_time.h"
114#include "timers.h"
115#include "toolbar.h"
116#include "track_gui.h"
117#include "track_prop_dlg.h"
118#include "undo.h"
119
120#include "s57_ocpn_utils.h"
121
122#ifdef __ANDROID__
123#include "androidUTIL.h"
124#endif
125
126#ifdef ocpnUSE_GL
127#include "gl_chart_canvas.h"
130#endif
131
132#ifdef __VISUALC__
133#include <wx/msw/msvcrt.h>
134#endif
135
136#ifndef __WXMSW__
137#include <signal.h>
138#include <setjmp.h>
139#endif
140
141#ifdef __WXMSW__
142#define printf printf2
143
144int __cdecl printf2(const char *format, ...) {
145 char str[1024];
146
147 va_list argptr;
148 va_start(argptr, format);
149 int ret = vsnprintf(str, sizeof(str), format, argptr);
150 va_end(argptr);
151 OutputDebugStringA(str);
152 return ret;
153}
154#endif
155
156#if defined(__MSVC__) && (_MSC_VER < 1700)
157#define trunc(d) ((d > 0) ? floor(d) : ceil(d))
158#endif
159
160// Define to enable the invocation of a temporary menubar by pressing the Alt
161// key. Not implemented for Windows XP, as it interferes with Alt-Tab
162// processing.
163#define OCPN_ALT_MENUBAR 1
164
165// Profiling support
166// #include "/usr/include/valgrind/callgrind.h"
167
168// ----------------------------------------------------------------------------
169// Useful Prototypes
170// ----------------------------------------------------------------------------
171extern ColorScheme global_color_scheme; // library dependence
172extern wxColor GetDimColor(wxColor c); // library dependence
173
174static bool g_bSmoothRecenter = true;
175static bool bDrawCurrentValues;
185static int mouse_x;
195static int mouse_y;
196static bool mouse_leftisdown;
197static bool g_brouteCreating;
198static int r_gamma_mult;
199static int g_gamma_mult;
200static int b_gamma_mult;
201static int gamma_state;
202static bool g_brightness_init;
203static int last_brightness;
204static wxGLContext *g_pGLcontext; // shared common context
205
206// "Curtain" mode parameters
207static wxDialog *g_pcurtain;
208
209static wxString g_lastS52PLIBPluginMessage;
210
211#define MIN_BRIGHT 10
212#define MAX_BRIGHT 100
213
214//------------------------------------------------------------------------------
215// ChartCanvas Implementation
216//------------------------------------------------------------------------------
217BEGIN_EVENT_TABLE(ChartCanvas, wxWindow)
218EVT_PAINT(ChartCanvas::OnPaint)
219EVT_ACTIVATE(ChartCanvas::OnActivate)
220EVT_SIZE(ChartCanvas::OnSize)
221#ifndef HAVE_WX_GESTURE_EVENTS
222EVT_MOUSE_EVENTS(ChartCanvas::MouseEvent)
223#endif
224EVT_TIMER(DBLCLICK_TIMER, ChartCanvas::MouseTimedEvent)
225EVT_TIMER(PAN_TIMER, ChartCanvas::PanTimerEvent)
226EVT_TIMER(MOVEMENT_TIMER, ChartCanvas::MovementTimerEvent)
227EVT_TIMER(MOVEMENT_STOP_TIMER, ChartCanvas::MovementStopTimerEvent)
228EVT_TIMER(CURTRACK_TIMER, ChartCanvas::OnCursorTrackTimerEvent)
229EVT_TIMER(ROT_TIMER, ChartCanvas::RotateTimerEvent)
230EVT_TIMER(ROPOPUP_TIMER, ChartCanvas::OnRolloverPopupTimerEvent)
231EVT_TIMER(ROUTEFINISH_TIMER, ChartCanvas::OnRouteFinishTimerEvent)
232EVT_KEY_DOWN(ChartCanvas::OnKeyDown)
233EVT_KEY_UP(ChartCanvas::OnKeyUp)
234EVT_CHAR(ChartCanvas::OnKeyChar)
235EVT_MOUSE_CAPTURE_LOST(ChartCanvas::LostMouseCapture)
236EVT_KILL_FOCUS(ChartCanvas::OnKillFocus)
237EVT_SET_FOCUS(ChartCanvas::OnSetFocus)
238EVT_MENU(-1, ChartCanvas::OnToolLeftClick)
239EVT_TIMER(DEFERRED_FOCUS_TIMER, ChartCanvas::OnDeferredFocusTimerEvent)
240EVT_TIMER(MOVEMENT_VP_TIMER, ChartCanvas::MovementVPTimerEvent)
241EVT_TIMER(DRAG_INERTIA_TIMER, ChartCanvas::OnChartDragInertiaTimer)
242EVT_TIMER(JUMP_EASE_TIMER, ChartCanvas::OnJumpEaseTimer)
243EVT_TIMER(MENU_TIMER, ChartCanvas::OnMenuTimer)
244EVT_TIMER(TAP_TIMER, ChartCanvas::OnTapTimer)
245
246END_EVENT_TABLE()
247
248// Define a constructor for my canvas
249ChartCanvas::ChartCanvas(wxFrame *frame, int canvasIndex, wxWindow *nmea_log)
250 : wxWindow(frame, wxID_ANY, wxPoint(20, 20), wxSize(5, 5), wxNO_BORDER),
251 m_nmea_log(nmea_log) {
252 parent_frame = (MyFrame *)frame; // save a pointer to parent
253 m_canvasIndex = canvasIndex;
254
255 pscratch_bm = NULL;
256
257 SetBackgroundColour(wxColour(0, 0, 0));
258 SetBackgroundStyle(wxBG_STYLE_CUSTOM); // on WXMSW, this prevents flashing on
259 // color scheme change
260
261 m_groupIndex = 0;
262 m_bDrawingRoute = false;
263 m_bRouteEditing = false;
264 m_bMarkEditing = false;
265 m_bRoutePoinDragging = false;
266 m_bIsInRadius = false;
267 m_bMayToggleMenuBar = true;
268
269 m_bFollow = false;
270 m_bShowNavobjects = true;
271 m_bTCupdate = false;
272 m_bAppendingRoute = false; // was true in MSW, why??
273 pThumbDIBShow = NULL;
274 m_bShowCurrent = false;
275 m_bShowTide = false;
276 bShowingCurrent = false;
277 pCwin = NULL;
278 warp_flag = false;
279 m_bzooming = false;
280 m_b_paint_enable = true;
281 m_routeState = 0;
282
283 pss_overlay_bmp = NULL;
284 pss_overlay_mask = NULL;
285 m_bChartDragging = false;
286 m_bMeasure_Active = false;
287 m_bMeasure_DistCircle = false;
288 m_pMeasureRoute = NULL;
289 m_pTrackRolloverWin = NULL;
290 m_pRouteRolloverWin = NULL;
291 m_pAISRolloverWin = NULL;
292 m_bedge_pan = false;
293 m_disable_edge_pan = false;
294 m_dragoffsetSet = false;
295 m_bautofind = false;
296 m_bFirstAuto = true;
297 m_groupIndex = 0;
298 m_singleChart = NULL;
299 m_upMode = NORTH_UP_MODE;
300 m_bShowAIS = true;
301 m_bShowAISScaled = false;
302 m_timed_move_vp_active = false;
303 m_inPinch = false;
304 m_disable_adjust_on_zoom = false;
305
306 m_vLat = 0.;
307 m_vLon = 0.;
308
309 m_pCIWin = NULL;
310
311 m_pSelectedRoute = NULL;
312 m_pSelectedTrack = NULL;
313 m_pRoutePointEditTarget = NULL;
314 m_pFoundPoint = NULL;
315 m_pMouseRoute = NULL;
316 m_prev_pMousePoint = NULL;
317 m_pEditRouteArray = NULL;
318 m_pFoundRoutePoint = NULL;
319 m_FinishRouteOnKillFocus = true;
320
321 m_pRolloverRouteSeg = NULL;
322 m_pRolloverTrackSeg = NULL;
323 m_bsectors_shown = false;
324
325 m_bbrightdir = false;
326 r_gamma_mult = 1;
327 g_gamma_mult = 1;
328 b_gamma_mult = 1;
329
330 m_pos_image_user_day = NULL;
331 m_pos_image_user_dusk = NULL;
332 m_pos_image_user_night = NULL;
333 m_pos_image_user_grey_day = NULL;
334 m_pos_image_user_grey_dusk = NULL;
335 m_pos_image_user_grey_night = NULL;
336
337 m_zoom_factor = 1;
338 m_rotation_speed = 0;
339 m_mustmove = 0;
340
341 m_OSoffsetx = 0.;
342 m_OSoffsety = 0.;
343
344 m_pos_image_user_yellow_day = NULL;
345 m_pos_image_user_yellow_dusk = NULL;
346 m_pos_image_user_yellow_night = NULL;
347
348 SetOwnShipState(SHIP_INVALID);
349
350 undo = new Undo(this);
351
352 VPoint.Invalidate();
353
354 m_glcc = NULL;
355
356 m_focus_indicator_pix = 1;
357
358 m_pCurrentStack = NULL;
359 m_bpersistent_quilt = false;
360 m_piano_ctx_menu = NULL;
361 m_Compass = NULL;
362 m_NotificationsList = NULL;
363 m_notification_button = NULL;
364
365 g_ChartNotRenderScaleFactor = 2.0;
366 m_bShowScaleInStatusBar = true;
367
368 m_muiBar = NULL;
369 m_bShowScaleInStatusBar = false;
370 m_show_focus_bar = true;
371
372 m_bShowOutlines = false;
373 m_bDisplayGrid = false;
374 m_bShowDepthUnits = true;
375 m_encDisplayCategory = (int)STANDARD;
376
377 m_encShowLights = true;
378 m_encShowAnchor = true;
379 m_encShowDataQual = false;
380 m_bShowGPS = true;
381 m_pQuilt = new Quilt(this);
382 SetQuiltMode(true);
383 SetAlertString("");
384 m_sector_glat = 0;
385 m_sector_glon = 0;
386 g_PrintingInProgress = false;
387
388#ifdef HAVE_WX_GESTURE_EVENTS
389 m_oldVPSScale = -1.0;
390 m_popupWanted = false;
391 m_leftdown = false;
392#endif /* HAVE_WX_GESTURE_EVENTS */
393 m_inLongPress = false;
394 m_sw_down_time = 0;
395 m_sw_up_time = 0;
396 m_sw_left_down.Start();
397 m_sw_left_up.Start();
398
399 SetupGlCanvas();
400
401 singleClickEventIsValid = false;
402
403 // Build the cursors
404
405 pCursorLeft = NULL;
406 pCursorRight = NULL;
407 pCursorUp = NULL;
408 pCursorDown = NULL;
409 pCursorArrow = NULL;
410 pCursorPencil = NULL;
411 pCursorCross = NULL;
412
413 RebuildCursors();
414
415 SetCursor(*pCursorArrow);
416
417 pPanTimer = new wxTimer(this, m_MouseDragging);
418 pPanTimer->Stop();
419
420 pMovementTimer = new wxTimer(this, MOVEMENT_TIMER);
421 pMovementTimer->Stop();
422
423 pMovementStopTimer = new wxTimer(this, MOVEMENT_STOP_TIMER);
424 pMovementStopTimer->Stop();
425
426 pRotDefTimer = new wxTimer(this, ROT_TIMER);
427 pRotDefTimer->Stop();
428
429 m_DoubleClickTimer = new wxTimer(this, DBLCLICK_TIMER);
430 m_DoubleClickTimer->Stop();
431
432 m_VPMovementTimer.SetOwner(this, MOVEMENT_VP_TIMER);
433 m_chart_drag_inertia_timer.SetOwner(this, DRAG_INERTIA_TIMER);
434 m_chart_drag_inertia_active = false;
435
436 m_easeTimer.SetOwner(this, JUMP_EASE_TIMER);
437 m_animationActive = false;
438 m_menuTimer.SetOwner(this, MENU_TIMER);
439 m_tap_timer.SetOwner(this, TAP_TIMER);
440
441 m_panx = m_pany = 0;
442 m_panspeed = 0;
443 m_panx_target_final = m_pany_target_final = 0;
444 m_panx_target_now = m_pany_target_now = 0;
445 m_DragTrigger = -1;
446
447 pCurTrackTimer = new wxTimer(this, CURTRACK_TIMER);
448 pCurTrackTimer->Stop();
449 m_curtrack_timer_msec = 10;
450
451 m_wheelzoom_stop_oneshot = 0;
452 m_last_wheel_dir = 0;
453
454 m_RolloverPopupTimer.SetOwner(this, ROPOPUP_TIMER);
455
456 m_deferredFocusTimer.SetOwner(this, DEFERRED_FOCUS_TIMER);
457
458 m_rollover_popup_timer_msec = 20;
459
460 m_routeFinishTimer.SetOwner(this, ROUTEFINISH_TIMER);
461
462 m_b_rot_hidef = true;
463
464 proute_bm = NULL;
465 m_prot_bm = NULL;
466
467 m_upMode = NORTH_UP_MODE;
468 m_bLookAhead = false;
469
470 // Set some benign initial values
471
472 m_cs = GLOBAL_COLOR_SCHEME_DAY;
473 VPoint.clat = 0;
474 VPoint.clon = 0;
475 VPoint.view_scale_ppm = 1;
476 VPoint.Invalidate();
477 m_nMeasureState = 0;
478 m_ignore_next_leftup = false;
479
480 m_canvas_scale_factor = 1.;
481
482 m_canvas_width = 1000;
483
484 m_overzoomTextWidth = 0;
485 m_overzoomTextHeight = 0;
486
487 // Create the default world chart
488 pWorldBackgroundChart = new GSHHSChart;
489 gShapeBasemap.Reset();
490
491 // Create the default depth unit emboss maps
492 m_pEM_Feet = NULL;
493 m_pEM_Meters = NULL;
494 m_pEM_Fathoms = NULL;
495
496 CreateDepthUnitEmbossMaps(GLOBAL_COLOR_SCHEME_DAY);
497
498 m_pEM_OverZoom = NULL;
499 SetOverzoomFont();
500 CreateOZEmbossMapData(GLOBAL_COLOR_SCHEME_DAY);
501
502 // Build icons for tide/current points
503 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
504 m_bmTideDay =
505 style->GetIconScaled("tidesml", 1. / g_Platform->GetDisplayDIPMult(this));
506
507 // Dusk
508 m_bmTideDusk = CreateDimBitmap(m_bmTideDay, .50);
509
510 // Night
511 m_bmTideNight = CreateDimBitmap(m_bmTideDay, .20);
512
513 // Build Dusk/Night ownship icons
514 double factor_dusk = 0.5;
515 double factor_night = 0.25;
516
517 // Red
518 m_os_image_red_day = style->GetIcon("ship-red").ConvertToImage();
519
520 int rimg_width = m_os_image_red_day.GetWidth();
521 int rimg_height = m_os_image_red_day.GetHeight();
522
523 m_os_image_red_dusk = m_os_image_red_day.Copy();
524 m_os_image_red_night = m_os_image_red_day.Copy();
525
526 for (int iy = 0; iy < rimg_height; iy++) {
527 for (int ix = 0; ix < rimg_width; ix++) {
528 if (!m_os_image_red_day.IsTransparent(ix, iy)) {
529 wxImage::RGBValue rgb(m_os_image_red_day.GetRed(ix, iy),
530 m_os_image_red_day.GetGreen(ix, iy),
531 m_os_image_red_day.GetBlue(ix, iy));
532 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
533 hsv.value = hsv.value * factor_dusk;
534 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
535 m_os_image_red_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
536
537 hsv = wxImage::RGBtoHSV(rgb);
538 hsv.value = hsv.value * factor_night;
539 nrgb = wxImage::HSVtoRGB(hsv);
540 m_os_image_red_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
541 }
542 }
543 }
544
545 // Grey
546 m_os_image_grey_day =
547 style->GetIcon("ship-red").ConvertToImage().ConvertToGreyscale();
548
549 int gimg_width = m_os_image_grey_day.GetWidth();
550 int gimg_height = m_os_image_grey_day.GetHeight();
551
552 m_os_image_grey_dusk = m_os_image_grey_day.Copy();
553 m_os_image_grey_night = m_os_image_grey_day.Copy();
554
555 for (int iy = 0; iy < gimg_height; iy++) {
556 for (int ix = 0; ix < gimg_width; ix++) {
557 if (!m_os_image_grey_day.IsTransparent(ix, iy)) {
558 wxImage::RGBValue rgb(m_os_image_grey_day.GetRed(ix, iy),
559 m_os_image_grey_day.GetGreen(ix, iy),
560 m_os_image_grey_day.GetBlue(ix, iy));
561 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
562 hsv.value = hsv.value * factor_dusk;
563 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
564 m_os_image_grey_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
565
566 hsv = wxImage::RGBtoHSV(rgb);
567 hsv.value = hsv.value * factor_night;
568 nrgb = wxImage::HSVtoRGB(hsv);
569 m_os_image_grey_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
570 }
571 }
572 }
573
574 // Yellow
575 m_os_image_yellow_day = m_os_image_red_day.Copy();
576
577 gimg_width = m_os_image_yellow_day.GetWidth();
578 gimg_height = m_os_image_yellow_day.GetHeight();
579
580 m_os_image_yellow_dusk = m_os_image_red_day.Copy();
581 m_os_image_yellow_night = m_os_image_red_day.Copy();
582
583 for (int iy = 0; iy < gimg_height; iy++) {
584 for (int ix = 0; ix < gimg_width; ix++) {
585 if (!m_os_image_yellow_day.IsTransparent(ix, iy)) {
586 wxImage::RGBValue rgb(m_os_image_yellow_day.GetRed(ix, iy),
587 m_os_image_yellow_day.GetGreen(ix, iy),
588 m_os_image_yellow_day.GetBlue(ix, iy));
589 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
590 hsv.hue += 60. / 360.; // shift to yellow
591 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
592 m_os_image_yellow_day.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
593
594 hsv = wxImage::RGBtoHSV(rgb);
595 hsv.value = hsv.value * factor_dusk;
596 hsv.hue += 60. / 360.; // shift to yellow
597 nrgb = wxImage::HSVtoRGB(hsv);
598 m_os_image_yellow_dusk.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
599
600 hsv = wxImage::RGBtoHSV(rgb);
601 hsv.hue += 60. / 360.; // shift to yellow
602 hsv.value = hsv.value * factor_night;
603 nrgb = wxImage::HSVtoRGB(hsv);
604 m_os_image_yellow_night.SetRGB(ix, iy, nrgb.red, nrgb.green, nrgb.blue);
605 }
606 }
607 }
608
609 // Set initial pointers to ownship images
610 m_pos_image_red = &m_os_image_red_day;
611 m_pos_image_yellow = &m_os_image_yellow_day;
612 m_pos_image_grey = &m_os_image_grey_day;
613
614 SetUserOwnship();
615
616 m_pBrightPopup = NULL;
617
618#ifdef ocpnUSE_GL
619 if (!g_bdisable_opengl) m_pQuilt->EnableHighDefinitionZoom(true);
620#endif
621
622 SetupGridFont();
623
624 m_Piano = new Piano(this);
625
626 m_bShowCompassWin = true;
627 m_Compass = new ocpnCompass(this);
628 m_Compass->SetScaleFactor(g_compass_scalefactor);
629 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
630
631 if (IsPrimaryCanvas()) {
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 = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
783
784 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
785 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
786 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
787 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
788 wxImage ICursorPencil =
789 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
790 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
791
792#if !defined(__WXMSW__) && !defined(__WXQT__)
793 ICursorLeft.ConvertAlphaToMask(128);
794 ICursorRight.ConvertAlphaToMask(128);
795 ICursorUp.ConvertAlphaToMask(128);
796 ICursorDown.ConvertAlphaToMask(128);
797 ICursorPencil.ConvertAlphaToMask(10);
798 ICursorCross.ConvertAlphaToMask(10);
799#endif
800
801 if (ICursorLeft.Ok()) {
802 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
803 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
804 pCursorLeft = new wxCursor(ICursorLeft);
805 } else
806 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
807
808 if (ICursorRight.Ok()) {
809 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
810 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
811 pCursorRight = new wxCursor(ICursorRight);
812 } else
813 pCursorRight = new wxCursor(wxCURSOR_ARROW);
814
815 if (ICursorUp.Ok()) {
816 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
817 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
818 pCursorUp = new wxCursor(ICursorUp);
819 } else
820 pCursorUp = new wxCursor(wxCURSOR_ARROW);
821
822 if (ICursorDown.Ok()) {
823 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
824 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
825 pCursorDown = new wxCursor(ICursorDown);
826 } else
827 pCursorDown = new wxCursor(wxCURSOR_ARROW);
828
829 if (ICursorPencil.Ok()) {
830 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
831 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
832 pCursorPencil = new wxCursor(ICursorPencil);
833 } else
834 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
835
836 if (ICursorCross.Ok()) {
837 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
838 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
839 pCursorCross = new wxCursor(ICursorCross);
840 } else
841 pCursorCross = new wxCursor(wxCURSOR_ARROW);
842
843 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
844 pPlugIn_Cursor = NULL;
845}
846
847void ChartCanvas::CanvasApplyLocale() {
848 CreateDepthUnitEmbossMaps(m_cs);
849 CreateOZEmbossMapData(m_cs);
850}
851
852void ChartCanvas::SetupGlCanvas() {
853#ifndef __ANDROID__
854#ifdef ocpnUSE_GL
855 if (!g_bdisable_opengl) {
856 if (g_bopengl) {
857 wxLogMessage("Creating glChartCanvas");
858 m_glcc = new glChartCanvas(this);
859
860 // We use one context for all GL windows, so that textures etc will be
861 // automatically shared
862 if (IsPrimaryCanvas()) {
863 // qDebug() << "Creating Primary Context";
864
865 // wxGLContextAttrs ctxAttr;
866 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
867 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
868 // NULL, &ctxAttr);
869 wxGLContext *pctx = new wxGLContext(m_glcc);
870 m_glcc->SetContext(pctx);
871 g_pGLcontext = pctx; // Save a copy of the common context
872 } else {
873#ifdef __WXOSX__
874 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
875#else
876 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
877 // saved common context
878#endif
879 }
880 }
881 }
882#endif
883#endif
884
885#ifdef __ANDROID__ // ocpnUSE_GL
886 if (!g_bdisable_opengl) {
887 if (g_bopengl) {
888 // qDebug() << "SetupGlCanvas";
889 wxLogMessage("Creating glChartCanvas");
890
891 // We use one context for all GL windows, so that textures etc will be
892 // automatically shared
893 if (IsPrimaryCanvas()) {
894 qDebug() << "Creating Primary glChartCanvas";
895
896 // wxGLContextAttrs ctxAttr;
897 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
898 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
899 // NULL, &ctxAttr);
900 m_glcc = new glChartCanvas(this);
901
902 wxGLContext *pctx = new wxGLContext(m_glcc);
903 m_glcc->SetContext(pctx);
904 g_pGLcontext = pctx; // Save a copy of the common context
905 m_glcc->m_pParentCanvas = this;
906 // m_glcc->Reparent(this);
907 } else {
908 qDebug() << "Creating Secondary glChartCanvas";
909 // QGLContext *pctx =
910 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
911 // << "pctx: " << pctx;
912
913 m_glcc = new glChartCanvas(
914 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
915 // m_glcc = new glChartCanvas(this, pctx); //Shared
916 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
917 wxGLContext *pwxctx = new wxGLContext(m_glcc);
918 m_glcc->SetContext(pwxctx);
919 m_glcc->m_pParentCanvas = this;
920 // m_glcc->Reparent(this);
921 }
922 }
923 }
924#endif
925}
926
927void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
928 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
929
930 // On Android, we get a KillFocus on just about every keystroke.
931 // Why?
932#ifdef __ANDROID__
933 return;
934#endif
935
936 // Special logic:
937 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
938 // canvas focus. Why??? Who knows... So, we provide for this case by
939 // starting a timer if required to actually Finish() a route on a legitimate
940 // focus change, but not if the focus is quickly regained ( <20 msec.) on
941 // this canvas.
942#ifdef __WXOSX__
943 if (m_routeState && m_FinishRouteOnKillFocus)
944 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
945#else
946 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
947#endif
948}
949
950void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
951 m_routeFinishTimer.Stop();
952
953 // Try to keep the global top-line menubar selections up to date with the
954 // current "focus" canvas
955 gFrame->UpdateGlobalMenuItems(this);
956
957 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
958}
959
960void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
961 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
962}
963
964#ifdef HAVE_WX_GESTURE_EVENTS
965void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
966#ifdef __ANDROID__
967 /* we defer the popup menu call upon the leftup event
968 else the menu disappears immediately,
969 (see
970 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
971 */
972 m_popupWanted = true;
973#else
974 m_inLongPress = !g_bhide_context_menus;
975
976 // Send a synthetic mouse left-up event to sync the mouse pan logic.
977 m_menuPos = event.GetPosition();
978 wxMouseEvent ev(wxEVT_LEFT_UP);
979 ev.m_x = m_menuPos.x;
980 ev.m_y = m_menuPos.y;
981 wxPostEvent(this, ev);
982
983 // In touch mode, send a "RIGHT CLICK" event, for plugins
984 if (g_btouch) {
985 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
986 ev_right_click.m_x = m_menuPos.x;
987 ev_right_click.m_y = m_menuPos.y;
988 MouseEvent(ev_right_click);
989 }
990#endif
991}
992
993void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
994 // not implemented yet
995}
996
997void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
998
999void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
1000
1001void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1002#ifdef __WXGTK__
1003 long dt = m_sw_left_up.Time() - m_sw_up_time;
1004 m_sw_up_time = m_sw_left_up.Time();
1005
1006 // printf(" dt %ld\n",dt);
1007 if (dt < 5) {
1008 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1009 // ignore it.
1010 return;
1011 }
1012#endif
1013 // printf("Left_UP\n");
1014
1015 wxPoint pos = event.GetPosition();
1016
1017 m_leftdown = false;
1018
1019 if (!m_popupWanted) {
1020 wxMouseEvent ev(wxEVT_LEFT_UP);
1021 ev.m_x = pos.x;
1022 ev.m_y = pos.y;
1023 MouseEvent(ev);
1024 return;
1025 }
1026
1027 m_popupWanted = false;
1028
1029 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1030 ev.m_x = pos.x;
1031 ev.m_y = pos.y;
1032
1033 MouseEvent(ev);
1034}
1035
1036void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1037 m_leftdown = true;
1038
1039 // Detect and manage multiple left-downs coming from GTK mouse emulation
1040#ifdef __WXGTK__
1041 long dt = m_sw_left_down.Time() - m_sw_down_time;
1042 m_sw_down_time = m_sw_left_down.Time();
1043
1044 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1045
1046 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1047 // The timing between the two events is dependent upon the wxWidgets
1048 // message queue status, and the processing time required for intervening
1049 // events.
1050 // We detect and remove the duplicate events by measuring the elapsed time
1051 // between arrival of events.
1052 // Choose a duplicate detection time long enough to catch worst case time lag
1053 // between duplicating events, but considerably shorter than the nominal
1054 // "intentional double-click" time interval defined generally as 350 msec.
1055 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1056 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1057 // ignore it.
1058 return;
1059 }
1060#endif
1061
1062 // printf("Left_DOWN\n");
1063
1064 // detect and manage double-tap
1065#ifdef __WXGTK__
1066 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1067 2; // Use system setting for distance
1068 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1069 m_lastTapPos.y - max_double_click_distance,
1070 max_double_click_distance * 2, max_double_click_distance * 2);
1071
1072 // A new tap has started, check if it's close enough and in time
1073 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1074 // printf(" TapBump 1\n");
1075 m_tap_count += 1;
1076 } else {
1077 // printf(" TapSet 1\n");
1078 m_tap_count = 1;
1079 m_lastTapPos = event.GetPosition();
1080 m_tap_timer.StartOnce(
1081 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1082 }
1083
1084 if (m_tap_count == 2) {
1085 // printf(" Doubletap detected\n");
1086 m_tap_count = 0; // Reset after a double-tap
1087
1088 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1089 ev.m_x = event.m_x;
1090 ev.m_y = event.m_y;
1091 // wxPostEvent(this, ev);
1092 MouseEvent(ev);
1093 return;
1094 }
1095
1096#endif
1097
1098 MouseEvent(event);
1099}
1100
1101void ChartCanvas::OnMotion(wxMouseEvent &event) {
1102 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1103 dragging, upon simple click, and without the OnLeftDown event before Thus,
1104 this consists in skiping it, and setting the leftdown bit according to a
1105 status that we trust */
1106 event.m_leftDown = m_leftdown;
1107 MouseEvent(event);
1108}
1109
1110void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1111 /* there are spurious end zoom events upon right-click */
1112 if (event.IsGestureEnd()) return;
1113
1114 double factor = event.GetZoomFactor();
1115
1116 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1117 m_oldVPSScale = GetVPScale();
1118 }
1119
1120 double current_vps = GetVPScale();
1121 double wanted_factor = m_oldVPSScale / current_vps * factor;
1122
1123 ZoomCanvas(wanted_factor, true, false);
1124
1125 // Allow combined zoom/pan operation
1126 if (event.IsGestureStart()) {
1127 m_zoomStartPoint = event.GetPosition();
1128 } else {
1129 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1130 PanCanvas(-delta.x, -delta.y);
1131 m_zoomStartPoint = event.GetPosition();
1132 }
1133}
1134
1135void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1136
1137void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1138 DoRotateCanvas(0.0);
1139}
1140#endif /* HAVE_WX_GESTURE_EVENTS */
1141
1142void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1143 // printf("tap timer %d\n", m_tap_count);
1144 m_tap_count = 0;
1145}
1146
1147void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1148 m_FinishRouteOnKillFocus = false;
1149 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1150 m_FinishRouteOnKillFocus = true;
1151}
1152
1153void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1154 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1155 m_vLat = pcc->iLat;
1156 m_vLon = pcc->iLon;
1157
1158 m_restore_dbindex = pcc->DBindex;
1159 m_bFollow = pcc->bFollow;
1160 if (pcc->GroupID < 0) pcc->GroupID = 0;
1161
1162 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1163 m_groupIndex = 0;
1164 else
1165 m_groupIndex = pcc->GroupID;
1166
1167 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1168
1169 ShowTides(pcc->bShowTides);
1170 ShowCurrents(pcc->bShowCurrents);
1171
1172 SetShowDepthUnits(pcc->bShowDepthUnits);
1173 SetShowGrid(pcc->bShowGrid);
1174 SetShowOutlines(pcc->bShowOutlines);
1175
1176 SetShowAIS(pcc->bShowAIS);
1177 SetAttenAIS(pcc->bAttenAIS);
1178
1179 // ENC options
1180 SetShowENCText(pcc->bShowENCText);
1181 m_encDisplayCategory = pcc->nENCDisplayCategory;
1182 m_encShowDepth = pcc->bShowENCDepths;
1183 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1184 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1185 m_encShowLights = pcc->bShowENCLights;
1186 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1187 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1188 m_encShowDataQual = pcc->bShowENCDataQuality;
1189
1190 bool courseUp = pcc->bCourseUp;
1191 bool headUp = pcc->bHeadUp;
1192 m_upMode = NORTH_UP_MODE;
1193 if (courseUp)
1194 m_upMode = COURSE_UP_MODE;
1195 else if (headUp)
1196 m_upMode = HEAD_UP_MODE;
1197
1198 m_bLookAhead = pcc->bLookahead;
1199
1200 m_singleChart = NULL;
1201}
1202
1203void ChartCanvas::ApplyGlobalSettings() {
1204 // GPS compas window
1205 if (m_Compass) {
1206 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1207 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1208 }
1209 if (m_notification_button) m_notification_button->UpdateStatus();
1210}
1211
1212void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1213 bool groupOK = CheckGroup(m_groupIndex);
1214
1215 if (!groupOK) {
1216 SetGroupIndex(m_groupIndex, true);
1217 }
1218}
1219
1220void ChartCanvas::SetShowGPS(bool bshow) {
1221 if (m_bShowGPS != bshow) {
1222 delete m_Compass;
1223 m_Compass = new ocpnCompass(this, bshow);
1224 m_Compass->SetScaleFactor(g_compass_scalefactor);
1225 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1226 }
1227 m_bShowGPS = bshow;
1228}
1229
1230void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1231 m_bShowCompassWin = bshow;
1232 if (m_Compass) {
1233 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1234 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1235 }
1236}
1237
1238int ChartCanvas::GetPianoHeight() {
1239 int height = 0;
1240 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1241
1242 return height;
1243}
1244
1245void ChartCanvas::ConfigureChartBar() {
1246 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1247
1248 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1249 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1250
1251 if (GetQuiltMode()) {
1252 m_Piano->SetRoundedRectangles(true);
1253 }
1254 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1255 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1256 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1257}
1258
1259void ChartCanvas::ShowTides(bool bShow) {
1260 gFrame->LoadHarmonics();
1261
1262 if (ptcmgr->IsReady()) {
1263 SetbShowTide(bShow);
1264
1265 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1266 } else {
1267 wxLogMessage("Chart1::Event...TCMgr Not Available");
1268 SetbShowTide(false);
1269 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1270 }
1271
1272 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1273 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1274
1275 // TODO
1276 // if( GetbShowTide() ) {
1277 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1278 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1279 // update
1280 // } else
1281 // FrameTCTimer.Stop();
1282}
1283
1284void ChartCanvas::ShowCurrents(bool bShow) {
1285 gFrame->LoadHarmonics();
1286
1287 if (ptcmgr->IsReady()) {
1288 SetbShowCurrent(bShow);
1289 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1290 } else {
1291 wxLogMessage("Chart1::Event...TCMgr Not Available");
1292 SetbShowCurrent(false);
1293 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1294 }
1295
1296 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1297 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1298
1299 // TODO
1300 // if( GetbShowCurrent() ) {
1301 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1302 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1303 // update
1304 // } else
1305 // FrameTCTimer.Stop();
1306}
1307
1308// TODO
1309static ChartDummy *pDummyChart;
1310
1313
1314void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1315
1316void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1317 SetAlertString("");
1318
1319 int new_index = index;
1320 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1321
1322 bool bgroup_override = false;
1323 int old_group_index = new_index;
1324
1325 if (!CheckGroup(new_index)) {
1326 new_index = 0;
1327 bgroup_override = true;
1328 }
1329
1330 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1331 new_index = index;
1332
1333 // Get the currently displayed chart native scale, and the current ViewPort
1334 int current_chart_native_scale = GetCanvasChartNativeScale();
1335 ViewPort vp = GetVP();
1336
1337 m_groupIndex = new_index;
1338
1339 // Are there ENCs in this group
1340 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1341
1342 // Update the MUIBar for ENC availability
1343 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1344
1345 // Allow the chart database to pre-calculate the MBTile inclusion test
1346 // boolean...
1347 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1348
1349 // Invalidate the "sticky" chart on group change, since it might not be in
1350 // the new group
1351 g_sticky_chart = -1;
1352
1353 // We need a chartstack and quilt to figure out which chart to open in the
1354 // new group
1355 UpdateCanvasOnGroupChange();
1356
1357 int dbi_now = -1;
1358 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1359
1360 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1361
1362 // If a new reference chart is indicated, set a good scale for it.
1363 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1364 double best_scale = GetBestStartScale(dbi_hint, vp);
1365 SetVPScale(best_scale);
1366 }
1367
1368 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1369
1370 // Refresh the canvas, selecting the "best" chart,
1371 // applying the prior ViewPort exactly
1372 canvasChartsRefresh(dbi_hint);
1373
1374 UpdateCanvasControlBar();
1375
1376 if (!autoSwitch && bgroup_override) {
1377 // show a short timed message box
1378 wxString msg(_("Group \""));
1379
1380 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1381 msg += pGroup->m_group_name;
1382
1383 msg += _("\" is empty.");
1384
1385 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1386
1387 return;
1388 }
1389
1390 // Message box is deferred so that canvas refresh occurs properly before
1391 // dialog
1392 if (bgroup_override) {
1393 wxString msg(_("Group \""));
1394
1395 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1396 msg += pGroup->m_group_name;
1397
1398 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1399
1400 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1401 }
1402}
1403
1404bool ChartCanvas::CheckGroup(int igroup) {
1405 if (!ChartData) return true; // Not known yet...
1406
1407 if (igroup == 0) return true; // "all charts" is always OK
1408
1409 if (igroup < 0) // negative group is an error
1410 return false;
1411
1412 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1413
1414 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1415 // and auto-shift to group 0
1416 return false;
1417
1418 for (const auto &elem : pGroup->m_element_array) {
1419 for (unsigned int ic = 0;
1420 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1421 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1422 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1423
1424 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1425 }
1426 }
1427
1428 // If necessary, check for GSHHS
1429 for (const auto &elem : pGroup->m_element_array) {
1430 const wxString &element_root = elem.m_element_name;
1431 wxString test_string = "GSHH";
1432 if (element_root.Upper().Contains(test_string)) return true;
1433 }
1434
1435 return false;
1436}
1437
1438void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1439 if (!ChartData) return;
1440
1441 AbstractPlatform::ShowBusySpinner();
1442
1443 double old_scale = GetVPScale();
1444 InvalidateQuilt();
1445 SetQuiltRefChart(-1);
1446
1447 m_singleChart = NULL;
1448
1449 // delete m_pCurrentStack;
1450 // m_pCurrentStack = NULL;
1451
1452 // Build a new ChartStack
1453 if (!m_pCurrentStack) {
1454 m_pCurrentStack = new ChartStack;
1455 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1456 }
1457
1458 if (-1 != dbi_hint) {
1459 if (GetQuiltMode()) {
1460 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1461 SetQuiltRefChart(dbi_hint);
1462 } else {
1463 // Open the saved chart
1464 ChartBase *pTentative_Chart;
1465 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1466
1467 if (pTentative_Chart) {
1468 /* m_singleChart is always NULL here, (set above) should this go before
1469 * that? */
1470 if (m_singleChart) m_singleChart->Deactivate();
1471
1472 m_singleChart = pTentative_Chart;
1473 m_singleChart->Activate();
1474
1475 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1476 GetpCurrentStack(), m_singleChart->GetFullPath());
1477 }
1478 }
1479
1480 // refresh_Piano();
1481 } else {
1482 // Select reference chart from the stack, as though clicked by user
1483 // Make it the smallest scale chart on the stack
1484 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1485 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1486 SetQuiltRefChart(selected_index);
1487 }
1488
1489 // Validate the correct single chart, or set the quilt mode as appropriate
1490 SetupCanvasQuiltMode();
1491 if (!GetQuiltMode() && m_singleChart == 0) {
1492 // use a dummy like in DoChartUpdate
1493 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1494 m_singleChart = pDummyChart;
1495 SetVPScale(old_scale);
1496 }
1497
1498 ReloadVP();
1499
1500 UpdateCanvasControlBar();
1501 UpdateGPSCompassStatusBox(true);
1502
1503 SetCursor(wxCURSOR_ARROW);
1504
1505 AbstractPlatform::HideBusySpinner();
1506}
1507
1508bool ChartCanvas::DoCanvasUpdate() {
1509 double tLat, tLon; // Chart Stack location
1510 double vpLat, vpLon; // ViewPort location
1511 bool blong_jump = false;
1512 meters_to_shift = 0;
1513 dir_to_shift = 0;
1514
1515 bool bNewChart = false;
1516 bool bNewView = false;
1517 bool bCanvasChartAutoOpen = true; // debugging
1518
1519 bool bNewPiano = false;
1520 bool bOpenSpecified;
1521 ChartStack LastStack;
1522 ChartBase *pLast_Ch;
1523
1524 ChartStack WorkStack;
1525
1526 if (bDBUpdateInProgress) return false;
1527 if (!ChartData) return false;
1528
1529 if (ChartData->IsBusy()) return false;
1530 if (m_chart_drag_inertia_active) return false;
1531
1532 // Startup case:
1533 // Quilting is enabled, but the last chart seen was not quiltable
1534 // In this case, drop to single chart mode, set persistence flag,
1535 // And open the specified chart
1536 // TODO implement this
1537 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1538 // if( GetQuiltMode() ) {
1539 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1540 // gFrame->ToggleQuiltMode();
1541 // m_bpersistent_quilt = true;
1542 // m_singleChart = NULL;
1543 // }
1544 // }
1545 // }
1546
1547 // If in auto-follow mode, use the current glat,glon to build chart
1548 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1549 // other means
1550
1551 if (m_bFollow) {
1552 tLat = gLat;
1553 tLon = gLon;
1554
1555 // Set the ViewPort center based on the OWNSHIP offset
1556 double dx = m_OSoffsetx;
1557 double dy = m_OSoffsety;
1558 double d_east = dx / GetVP().view_scale_ppm;
1559 double d_north = dy / GetVP().view_scale_ppm;
1560
1561 if (GetUpMode() == NORTH_UP_MODE) {
1562 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1563 } else {
1564 double offset_angle = atan2(d_north, d_east);
1565 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1566 double chart_angle = GetVPRotation();
1567 double target_angle = chart_angle + offset_angle;
1568 double d_east_mod = offset_distance * cos(target_angle);
1569 double d_north_mod = offset_distance * sin(target_angle);
1570 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1571 }
1572
1573 // on lookahead mode, adjust the vp center point
1574 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1575 double cog_to_use = gCog;
1576 if (g_btenhertz &&
1577 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1578 cog_to_use = gCog_gt;
1579 blong_jump = true;
1580 }
1581 if (!g_btenhertz) cog_to_use = g_COGAvg;
1582
1583 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1584
1585 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1586 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1587
1588 double pixel_delta_tent =
1589 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1590
1591 double pixel_delta = 0;
1592
1593 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1594 // avoid jumping of the vp center point during slow maneuvering, or at
1595 // anchor....
1596 if (!std::isnan(gSog)) {
1597 if (gSog < 2.0)
1598 pixel_delta = 0.;
1599 else
1600 pixel_delta = pixel_delta_tent;
1601 }
1602
1603 meters_to_shift = 0;
1604 dir_to_shift = 0;
1605 if (!std::isnan(gCog)) {
1606 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1607 dir_to_shift = cog_to_use;
1608 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1609 &vpLon);
1610 } else {
1611 vpLat = gLat;
1612 vpLon = gLon;
1613 }
1614 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1615 m_OSoffsetx = 0; // center ownship on loss of GPS
1616 m_OSoffsety = 0;
1617 vpLat = gLat;
1618 vpLon = gLon;
1619 }
1620
1621 } else {
1622 tLat = m_vLat;
1623 tLon = m_vLon;
1624 vpLat = m_vLat;
1625 vpLon = m_vLon;
1626 }
1627
1628 if (GetQuiltMode()) {
1629 int current_db_index = -1;
1630 if (m_pCurrentStack)
1631 current_db_index =
1632 m_pCurrentStack
1633 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1634 // chart dbIndex
1635 else
1636 m_pCurrentStack = new ChartStack;
1637
1638 // This logic added to enable opening a chart when there is no
1639 // previous chart indication, either from inital startup, or from adding
1640 // new chart directory
1641 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1642 m_pCurrentStack) {
1643 if (m_pCurrentStack->nEntry) {
1644 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1645 1); // smallest scale
1646 SelectQuiltRefdbChart(new_dbIndex, true);
1647 m_bautofind = false;
1648 }
1649 }
1650
1651 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1652 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1653
1654 if (m_bFirstAuto) {
1655 // Allow the chart database to pre-calculate the MBTile inclusion test
1656 // boolean...
1657 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1658
1659 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1660 // physical pixels. On standard DPI displays where logical = physical
1661 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1662 // logical pixels, this ratio would be 0.5.
1663 double proposed_scale_onscreen =
1664 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1665
1666 int initial_db_index = m_restore_dbindex;
1667 if (initial_db_index < 0) {
1668 if (m_pCurrentStack->nEntry) {
1669 initial_db_index =
1670 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1671 } else
1672 m_bautofind = true; // initial_db_index = 0;
1673 }
1674
1675 if (m_pCurrentStack->nEntry) {
1676 int initial_type = ChartData->GetDBChartType(initial_db_index);
1677
1678 // Check to see if the target new chart is quiltable as a reference
1679 // chart
1680
1681 if (!IsChartQuiltableRef(initial_db_index)) {
1682 // If it is not quiltable, then walk the stack up looking for a
1683 // satisfactory chart i.e. one that is quiltable and of the same type
1684 // XXX if there's none?
1685 int stack_index = 0;
1686
1687 if (stack_index >= 0) {
1688 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1689 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1690 if (IsChartQuiltableRef(test_db_index) &&
1691 (initial_type ==
1692 ChartData->GetDBChartType(initial_db_index))) {
1693 initial_db_index = test_db_index;
1694 break;
1695 }
1696 stack_index++;
1697 }
1698 }
1699 }
1700
1701 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1702 if (pc) {
1703 SetQuiltRefChart(initial_db_index);
1704 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1705 }
1706 }
1707 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1708 // just GetVPScale(), so I'm not sure why it's necessary to define the
1709 // proposed_scale_onscreen variable.
1710 bNewView |= SetViewPoint(vpLat, vpLon,
1711 GetCanvasScaleFactor() / proposed_scale_onscreen,
1712 0, GetVPRotation());
1713 }
1714 // Measure rough jump distance if in bfollow mode
1715 // No good reason to do smooth pan for
1716 // jump distance more than one screen width at scale.
1717 bool super_jump = false;
1718 if (m_bFollow) {
1719 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1720 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1721 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1722 }
1723#if 0
1724 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1725 int nstep = 5;
1726 if (blong_jump) nstep = 20;
1727 StartTimedMovementVP(vpLat, vpLon, nstep);
1728 } else
1729#endif
1730 {
1731 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1732 }
1733
1734 goto update_finish;
1735 }
1736
1737 // Single Chart Mode from here....
1738 pLast_Ch = m_singleChart;
1739 ChartTypeEnum new_open_type;
1740 ChartFamilyEnum new_open_family;
1741 if (pLast_Ch) {
1742 new_open_type = pLast_Ch->GetChartType();
1743 new_open_family = pLast_Ch->GetChartFamily();
1744 } else {
1745 new_open_type = CHART_TYPE_KAP;
1746 new_open_family = CHART_FAMILY_RASTER;
1747 }
1748
1749 bOpenSpecified = m_bFirstAuto;
1750
1751 // Make sure the target stack is valid
1752 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1753
1754 // Build a chart stack based on tLat, tLon
1755 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1756 m_groupIndex)) { // Bogus Lat, Lon?
1757 if (NULL == pDummyChart) {
1758 pDummyChart = new ChartDummy;
1759 bNewChart = true;
1760 }
1761
1762 if (m_singleChart)
1763 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1764
1765 m_singleChart = pDummyChart;
1766
1767 // If the current viewpoint is invalid, set the default scale to
1768 // something reasonable.
1769 double set_scale = GetVPScale();
1770 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1771
1772 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1773
1774 // If the chart stack has just changed, there is new status
1775 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1776 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1777 bNewPiano = true;
1778 bNewChart = true;
1779 }
1780 }
1781
1782 // Copy the new (by definition empty) stack into the target stack
1783 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1784
1785 goto update_finish;
1786 }
1787
1788 // Check to see if Chart Stack has changed
1789 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1790 // New chart stack, so...
1791 bNewPiano = true;
1792
1793 // Save a copy of the current stack
1794 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1795
1796 // Copy the new stack into the target stack
1797 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1798
1799 // Is Current Chart in new stack?
1800
1801 int tEntry = -1;
1802 if (NULL != m_singleChart) // this handles startup case
1803 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1804 m_singleChart->GetFullPath());
1805
1806 if (tEntry != -1) { // m_singleChart is in the new stack
1807 m_pCurrentStack->CurrentStackEntry = tEntry;
1808 bNewChart = false;
1809 }
1810
1811 else // m_singleChart is NOT in new stack
1812 { // So, need to open a new chart
1813 // Find the largest scale raster chart that opens OK
1814
1815 ChartBase *pProposed = NULL;
1816
1817 if (bCanvasChartAutoOpen) {
1818 bool search_direction =
1819 false; // default is to search from lowest to highest
1820 int start_index = 0;
1821
1822 // A special case: If panning at high scale, open largest scale
1823 // chart first
1824 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1825 (LastStack.nEntry == 0)) {
1826 search_direction = true;
1827 start_index = m_pCurrentStack->nEntry - 1;
1828 }
1829
1830 // Another special case, open specified index on program start
1831 if (bOpenSpecified) {
1832 search_direction = false;
1833 start_index = 0;
1834 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1835 start_index = 0;
1836
1837 new_open_type = CHART_TYPE_DONTCARE;
1838 }
1839
1840 pProposed = ChartData->OpenStackChartConditional(
1841 m_pCurrentStack, start_index, search_direction, new_open_type,
1842 new_open_family);
1843
1844 // Try to open other types/families of chart in some priority
1845 if (NULL == pProposed)
1846 pProposed = ChartData->OpenStackChartConditional(
1847 m_pCurrentStack, start_index, search_direction,
1848 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1849
1850 if (NULL == pProposed)
1851 pProposed = ChartData->OpenStackChartConditional(
1852 m_pCurrentStack, start_index, search_direction,
1853 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1854
1855 bNewChart = true;
1856
1857 } // bCanvasChartAutoOpen
1858
1859 else
1860 pProposed = NULL;
1861
1862 // If no go, then
1863 // Open a Dummy Chart
1864 if (NULL == pProposed) {
1865 if (NULL == pDummyChart) {
1866 pDummyChart = new ChartDummy;
1867 bNewChart = true;
1868 }
1869
1870 if (pLast_Ch)
1871 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1872
1873 pProposed = pDummyChart;
1874 }
1875
1876 // Arriving here, pProposed points to an opened chart, or NULL.
1877 if (m_singleChart) m_singleChart->Deactivate();
1878 m_singleChart = pProposed;
1879
1880 if (m_singleChart) {
1881 m_singleChart->Activate();
1882 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1883 m_pCurrentStack, m_singleChart->GetFullPath());
1884 }
1885 } // need new chart
1886
1887 // Arriving here, m_singleChart is opened and OK, or NULL
1888 if (NULL != m_singleChart) {
1889 // Setup the view using the current scale
1890 double set_scale = GetVPScale();
1891
1892 // If the current viewpoint is invalid, set the default scale to
1893 // something reasonable.
1894 if (!GetVP().IsValid())
1895 set_scale = 1. / 20000.;
1896 else { // otherwise, match scale if elected.
1897 double proposed_scale_onscreen;
1898
1899 if (m_bFollow) { // autoset the scale only if in autofollow
1900 double new_scale_ppm =
1901 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1902 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1903 } else
1904 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1905
1906 // This logic will bring a new chart onscreen at roughly twice the true
1907 // paper scale equivalent. Note that first chart opened on application
1908 // startup (bOpenSpecified = true) will open at the config saved scale
1909 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1910 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1911 double equivalent_vp_scale =
1912 GetCanvasScaleFactor() / proposed_scale_onscreen;
1913 double new_scale_ppm =
1914 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1915 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1916 }
1917
1918 if (m_bFollow) { // bounds-check the scale only if in autofollow
1919 proposed_scale_onscreen =
1920 wxMin(proposed_scale_onscreen,
1921 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1922 GetCanvasWidth()));
1923 proposed_scale_onscreen =
1924 wxMax(proposed_scale_onscreen,
1925 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1927 }
1928
1929 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1930 }
1931
1932 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1933 m_singleChart->GetChartSkew() * PI / 180.,
1934 GetVPRotation());
1935 }
1936 } // new stack
1937
1938 else // No change in Chart Stack
1939 {
1940 if ((m_bFollow) && m_singleChart)
1941 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1942 m_singleChart->GetChartSkew() * PI / 180.,
1943 GetVPRotation());
1944 }
1945
1946update_finish:
1947
1948 // TODO
1949 // if( bNewPiano ) UpdateControlBar();
1950
1951 m_bFirstAuto = false; // Auto open on program start
1952
1953 // If we need a Refresh(), do it here...
1954 // But don't duplicate a Refresh() done by SetViewPoint()
1955 if (bNewChart && !bNewView) Refresh(false);
1956
1957#ifdef ocpnUSE_GL
1958 // If a new chart, need to invalidate gl viewport for refresh
1959 // so the fbo gets flushed
1960 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1961#endif
1962
1963 return bNewChart | bNewView;
1964}
1965
1966void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1967 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1968
1969 SetQuiltRefChart(db_index);
1970 if (ChartData) {
1971 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1972 if (pc) {
1973 if (b_autoscale) {
1974 double best_scale_ppm = GetBestVPScale(pc);
1975 SetVPScale(best_scale_ppm);
1976 }
1977 } else
1978 SetQuiltRefChart(-1);
1979 } else
1980 SetQuiltRefChart(-1);
1981}
1982
1983void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1984 std::vector<int> piano_chart_index_array =
1985 GetQuiltExtendedStackdbIndexArray();
1986 int current_db_index = piano_chart_index_array[selected_index];
1987
1988 SelectQuiltRefdbChart(current_db_index);
1989}
1990
1991double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1992 if (pchart) {
1993 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1994
1995 if ((g_bPreserveScaleOnX) ||
1996 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1997 double new_scale_ppm = GetVPScale();
1998 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1999 } else {
2000 // This logic will bring the new chart onscreen at roughly twice the true
2001 // paper scale equivalent.
2002 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2003 double equivalent_vp_scale =
2004 GetCanvasScaleFactor() / proposed_scale_onscreen;
2005 double new_scale_ppm =
2006 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2007 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2008 }
2009
2010 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2011 // set. Otherwise, we get severe performance problems on all platforms
2012
2013 double max_underzoom_multiplier = 2.0;
2014 if (GetVP().b_quilt) {
2015 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2016 pchart->GetChartType(),
2017 pchart->GetChartFamily());
2018 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2019 }
2020
2021 proposed_scale_onscreen = wxMin(
2022 proposed_scale_onscreen,
2023 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2024 max_underzoom_multiplier);
2025
2026 // And, do not allow excessive overzoom either
2027 proposed_scale_onscreen =
2028 wxMax(proposed_scale_onscreen,
2029 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2030
2031 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2032 } else
2033 return 1.0;
2034}
2035
2036void ChartCanvas::SetupCanvasQuiltMode() {
2037 if (GetQuiltMode()) // going to quilt mode
2038 {
2039 ChartData->LockCache();
2040
2041 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2042
2043 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2044
2045 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2046 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2047 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2048 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2049
2050 m_Piano->SetRoundedRectangles(true);
2051
2052 // Select the proper Ref chart
2053 int target_new_dbindex = -1;
2054 if (m_pCurrentStack) {
2055 target_new_dbindex =
2056 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2057
2058 if (-1 != target_new_dbindex) {
2059 if (!IsChartQuiltableRef(target_new_dbindex)) {
2060 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2061 int type = ChartData->GetDBChartType(target_new_dbindex);
2062
2063 // walk the stack up looking for a satisfactory chart
2064 int stack_index = m_pCurrentStack->CurrentStackEntry;
2065
2066 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2067 (stack_index >= 0)) {
2068 int proj_tent = ChartData->GetDBChartProj(
2069 m_pCurrentStack->GetDBIndex(stack_index));
2070 int type_tent = ChartData->GetDBChartType(
2071 m_pCurrentStack->GetDBIndex(stack_index));
2072
2073 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2074 if ((proj == proj_tent) && (type_tent == type)) {
2075 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2076 break;
2077 }
2078 }
2079 stack_index++;
2080 }
2081 }
2082 }
2083 }
2084
2085 if (IsChartQuiltableRef(target_new_dbindex))
2086 SelectQuiltRefdbChart(target_new_dbindex,
2087 false); // Try not to allow a scale change
2088 else
2089 SelectQuiltRefdbChart(-1, false);
2090
2091 m_singleChart = NULL; // Bye....
2092
2093 // Re-qualify the quilt reference chart selection
2094 AdjustQuiltRefChart();
2095
2096 // Restore projection type saved on last quilt mode toggle
2097 // TODO
2098 // if(g_sticky_projection != -1)
2099 // GetVP().SetProjectionType(g_sticky_projection);
2100 // else
2101 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2102 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2103
2104 } else // going to SC Mode
2105 {
2106 std::vector<int> empty_array;
2107 m_Piano->SetActiveKeyArray(empty_array);
2108 m_Piano->SetNoshowIndexArray(empty_array);
2109 m_Piano->SetEclipsedIndexArray(empty_array);
2110
2111 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2112 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2113 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2114 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2115 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2116
2117 m_Piano->SetRoundedRectangles(false);
2118 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2119 }
2120
2121 // When shifting from quilt to single chart mode, select the "best" single
2122 // chart to show
2123 if (!GetQuiltMode()) {
2124 if (ChartData && ChartData->IsValid()) {
2125 UnlockQuilt();
2126
2127 double tLat, tLon;
2128 if (m_bFollow == true) {
2129 tLat = gLat;
2130 tLon = gLon;
2131 } else {
2132 tLat = m_vLat;
2133 tLon = m_vLon;
2134 }
2135
2136 if (!m_singleChart) {
2137 // Build a temporary chart stack based on tLat, tLon
2138 ChartStack TempStack;
2139 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2140 m_groupIndex);
2141
2142 // Iterate over the quilt charts actually shown, looking for the
2143 // largest scale chart that will be in the new chartstack.... This
2144 // will (almost?) always be the reference chart....
2145
2146 ChartBase *Candidate_Chart = NULL;
2147 int cur_max_scale = (int)1e8;
2148
2149 ChartBase *pChart = GetFirstQuiltChart();
2150 while (pChart) {
2151 // Is this pChart in new stack?
2152 int tEntry =
2153 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2154 if (tEntry != -1) {
2155 if (pChart->GetNativeScale() < cur_max_scale) {
2156 Candidate_Chart = pChart;
2157 cur_max_scale = pChart->GetNativeScale();
2158 }
2159 }
2160 pChart = GetNextQuiltChart();
2161 }
2162
2163 m_singleChart = Candidate_Chart;
2164
2165 // If the quilt is empty, there is no "best" chart.
2166 // So, open the smallest scale chart in the current stack
2167 if (NULL == m_singleChart) {
2168 m_singleChart = ChartData->OpenStackChartConditional(
2169 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2170 CHART_FAMILY_DONTCARE);
2171 }
2172 }
2173
2174 // Invalidate all the charts in the quilt,
2175 // as any cached data may be region based and not have fullscreen coverage
2176 InvalidateAllQuiltPatchs();
2177
2178 if (m_singleChart) {
2179 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2180 std::vector<int> one_array;
2181 one_array.push_back(dbi);
2182 m_Piano->SetActiveKeyArray(one_array);
2183 }
2184
2185 if (m_singleChart) {
2186 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2187 }
2188 }
2189 // Invalidate the current stack so that it will be rebuilt on next tick
2190 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2191 }
2192}
2193
2194bool ChartCanvas::IsTempMenuBarEnabled() {
2195#ifdef __WXMSW__
2196 int major;
2197 wxGetOsVersion(&major);
2198 return (major >
2199 5); // For Windows, function is only available on Vista and above
2200#else
2201 return true;
2202#endif
2203}
2204
2205double ChartCanvas::GetCanvasRangeMeters() {
2206 int width, height;
2207 GetSize(&width, &height);
2208 int minDimension = wxMin(width, height);
2209
2210 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2211 range *= cos(GetVP().clat * PI / 180.);
2212 return range;
2213}
2214
2215void ChartCanvas::SetCanvasRangeMeters(double range) {
2216 int width, height;
2217 GetSize(&width, &height);
2218 int minDimension = wxMin(width, height);
2219
2220 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2221 SetVPScale(scale_ppm / 2);
2222}
2223
2224bool ChartCanvas::SetUserOwnship() {
2225 // Look for user defined ownship image
2226 // This may be found in the shared data location along with other user
2227 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2228 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2229 double factor_dusk = 0.5;
2230 double factor_night = 0.25;
2231
2232 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2233 m_pos_image_user_day = new wxImage;
2234 *m_pos_image_user_day = pbmp->ConvertToImage();
2235 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2236
2237 int gimg_width = m_pos_image_user_day->GetWidth();
2238 int gimg_height = m_pos_image_user_day->GetHeight();
2239
2240 // Make dusk and night images
2241 m_pos_image_user_dusk = new wxImage;
2242 m_pos_image_user_night = new wxImage;
2243
2244 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2245 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2246
2247 for (int iy = 0; iy < gimg_height; iy++) {
2248 for (int ix = 0; ix < gimg_width; ix++) {
2249 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2250 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2251 m_pos_image_user_day->GetGreen(ix, iy),
2252 m_pos_image_user_day->GetBlue(ix, iy));
2253 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2254 hsv.value = hsv.value * factor_dusk;
2255 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2256 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2257 nrgb.blue);
2258
2259 hsv = wxImage::RGBtoHSV(rgb);
2260 hsv.value = hsv.value * factor_night;
2261 nrgb = wxImage::HSVtoRGB(hsv);
2262 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2263 nrgb.blue);
2264 }
2265 }
2266 }
2267
2268 // Make some alternate greyed out day/dusk/night images
2269 m_pos_image_user_grey_day = new wxImage;
2270 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2271
2272 m_pos_image_user_grey_dusk = new wxImage;
2273 m_pos_image_user_grey_night = new wxImage;
2274
2275 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2276 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2277
2278 for (int iy = 0; iy < gimg_height; iy++) {
2279 for (int ix = 0; ix < gimg_width; ix++) {
2280 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2281 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2282 m_pos_image_user_grey_day->GetGreen(ix, iy),
2283 m_pos_image_user_grey_day->GetBlue(ix, iy));
2284 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2285 hsv.value = hsv.value * factor_dusk;
2286 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2287 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2288 nrgb.blue);
2289
2290 hsv = wxImage::RGBtoHSV(rgb);
2291 hsv.value = hsv.value * factor_night;
2292 nrgb = wxImage::HSVtoRGB(hsv);
2293 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2294 nrgb.blue);
2295 }
2296 }
2297 }
2298
2299 // Make a yellow image for rendering under low accuracy chart conditions
2300 m_pos_image_user_yellow_day = new wxImage;
2301 m_pos_image_user_yellow_dusk = new wxImage;
2302 m_pos_image_user_yellow_night = new wxImage;
2303
2304 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2305 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2306 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2307
2308 for (int iy = 0; iy < gimg_height; iy++) {
2309 for (int ix = 0; ix < gimg_width; ix++) {
2310 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2311 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2312 m_pos_image_user_grey_day->GetGreen(ix, iy),
2313 m_pos_image_user_grey_day->GetBlue(ix, iy));
2314
2315 // Simply remove all "blue" from the greyscaled image...
2316 // so, what is not black becomes yellow.
2317 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2318 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2319 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2320
2321 hsv = wxImage::RGBtoHSV(rgb);
2322 hsv.value = hsv.value * factor_dusk;
2323 nrgb = wxImage::HSVtoRGB(hsv);
2324 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2325
2326 hsv = wxImage::RGBtoHSV(rgb);
2327 hsv.value = hsv.value * factor_night;
2328 nrgb = wxImage::HSVtoRGB(hsv);
2329 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2330 0);
2331 }
2332 }
2333 }
2334
2335 return true;
2336 } else
2337 return false;
2338}
2339
2341 m_display_size_mm = size;
2342
2343 // int sx, sy;
2344 // wxDisplaySize( &sx, &sy );
2345
2346 // Calculate logical pixels per mm for later reference.
2347 wxSize sd = g_Platform->getDisplaySize();
2348 double horizontal = sd.x;
2349 // Set DPI (Win) scale factor
2350 g_scaler = g_Platform->GetDisplayDIPMult(this);
2351
2352 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2353 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2354
2355 if (ps52plib) {
2356 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2357 ps52plib->SetPPMM(m_pix_per_mm);
2358 }
2359
2360 wxString msg;
2361 msg.Printf(
2362 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2363 "%d:%d ",
2364 m_display_size_mm, sd.x, sd.y);
2365 wxLogDebug(msg);
2366
2367 int ssx, ssy;
2368 ssx = g_monitor_info[g_current_monitor].width;
2369 ssy = g_monitor_info[g_current_monitor].height;
2370 msg.Printf("monitor size: %d %d", ssx, ssy);
2371 wxLogDebug(msg);
2372
2373 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2374}
2375#if 0
2376void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2377{
2378 wxString msg(event.m_string.c_str(), wxConvUTF8);
2379 // if cpus are removed between runs
2380 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2381 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2382 }
2383
2384 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2385 {
2386 compress_msg_array.RemoveAt(event.thread);
2387 compress_msg_array.Insert( msg, event.thread);
2388 }
2389 else
2390 compress_msg_array.Add(msg);
2391
2392
2393 wxString combined_msg;
2394 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2395 combined_msg += compress_msg_array[i];
2396 combined_msg += "\n";
2397 }
2398
2399 bool skip = false;
2400 pprog->Update(pprog_count, combined_msg, &skip );
2401 pprog->SetSize(pprog_size);
2402 if(skip)
2403 b_skipout = skip;
2404}
2405#endif
2406void ChartCanvas::InvalidateGL() {
2407 if (!m_glcc) return;
2408#ifdef ocpnUSE_GL
2409 if (g_bopengl) m_glcc->Invalidate();
2410#endif
2411 if (m_Compass) m_Compass->UpdateStatus(true);
2412}
2413
2414int ChartCanvas::GetCanvasChartNativeScale() {
2415 int ret = 1;
2416 if (!VPoint.b_quilt) {
2417 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2418 } else
2419 ret = (int)m_pQuilt->GetRefNativeScale();
2420
2421 return ret;
2422}
2423
2424ChartBase *ChartCanvas::GetChartAtCursor() {
2425 ChartBase *target_chart;
2426 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2427 target_chart = m_singleChart;
2428 else if (VPoint.b_quilt)
2429 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2430 else
2431 target_chart = NULL;
2432 return target_chart;
2433}
2434
2435ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2436 ChartBase *target_chart;
2437 if (VPoint.b_quilt)
2438 target_chart =
2439 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2440 else
2441 target_chart = NULL;
2442 return target_chart;
2443}
2444
2445int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2446 int new_dbIndex = -1;
2447 if (!VPoint.b_quilt) {
2448 if (m_pCurrentStack) {
2449 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2450 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2451 if (sc >= scale) {
2452 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2453 break;
2454 }
2455 }
2456 }
2457 } else {
2458 // Using the current quilt, select a useable reference chart
2459 // Said chart will be in the extended (possibly full-screen) stack,
2460 // And will have a scale equal to or just greater than the stipulated
2461 // value
2462 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2463 if (im > 0) {
2464 for (unsigned int is = 0; is < im; is++) {
2465 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2466 m_pQuilt->GetExtendedStackIndexArray()[is]);
2467 if ((m.Scale_ge(
2468 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2469 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2470 break;
2471 }
2472 }
2473 }
2474 }
2475
2476 return new_dbIndex;
2477}
2478
2479void ChartCanvas::EnablePaint(bool b_enable) {
2480 m_b_paint_enable = b_enable;
2481#ifdef ocpnUSE_GL
2482 if (m_glcc) m_glcc->EnablePaint(b_enable);
2483#endif
2484}
2485
2486bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2487
2488void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2489
2490std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2491 return m_pQuilt->GetQuiltIndexArray();
2492 ;
2493}
2494
2495void ChartCanvas::SetQuiltMode(bool b_quilt) {
2496 VPoint.b_quilt = b_quilt;
2497 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2498}
2499
2500bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2501
2502int ChartCanvas::GetQuiltReferenceChartIndex() {
2503 return m_pQuilt->GetRefChartdbIndex();
2504}
2505
2506void ChartCanvas::InvalidateAllQuiltPatchs() {
2507 m_pQuilt->InvalidateAllQuiltPatchs();
2508}
2509
2510ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2511 return m_pQuilt->GetLargestScaleChart();
2512}
2513
2514ChartBase *ChartCanvas::GetFirstQuiltChart() {
2515 return m_pQuilt->GetFirstChart();
2516}
2517
2518ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2519
2520int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2521
2522void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2523 m_pQuilt->SetHiliteIndex(dbIndex);
2524}
2525
2526void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2527 m_pQuilt->SetHiliteIndexArray(hilite_array);
2528}
2529
2530void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2531 m_pQuilt->ClearHiliteIndexArray();
2532}
2533
2534std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2535 bool flag2) {
2536 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2537}
2538
2539int ChartCanvas::GetQuiltRefChartdbIndex() {
2540 return m_pQuilt->GetRefChartdbIndex();
2541}
2542
2543std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2544 return m_pQuilt->GetExtendedStackIndexArray();
2545}
2546
2547std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2548 return m_pQuilt->GetFullscreenIndexArray();
2549}
2550
2551std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2552 return m_pQuilt->GetEclipsedStackIndexArray();
2553}
2554
2555void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2556
2557double ChartCanvas::GetQuiltMaxErrorFactor() {
2558 return m_pQuilt->GetMaxErrorFactor();
2559}
2560
2561bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2562 return m_pQuilt->IsChartQuiltableRef(db_index);
2563}
2564
2565bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2566 double chartMaxScale =
2567 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2568 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2569}
2570
2571void ChartCanvas::StartMeasureRoute() {
2572 if (!m_routeState) { // no measure tool if currently creating route
2573 if (m_bMeasure_Active) {
2574 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2575 m_pMeasureRoute = NULL;
2576 }
2577
2578 m_bMeasure_Active = true;
2579 m_nMeasureState = 1;
2580 m_bDrawingRoute = false;
2581
2582 SetCursor(*pCursorPencil);
2583 Refresh();
2584 }
2585}
2586
2587void ChartCanvas::CancelMeasureRoute() {
2588 m_bMeasure_Active = false;
2589 m_nMeasureState = 0;
2590 m_bDrawingRoute = false;
2591
2592 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2593 m_pMeasureRoute = NULL;
2594
2595 SetCursor(*pCursorArrow);
2596}
2597
2598ViewPort &ChartCanvas::GetVP() { return VPoint; }
2599
2600void ChartCanvas::SetVP(ViewPort &vp) {
2601 VPoint = vp;
2602 VPoint.SetPixelScale(m_displayScale);
2603}
2604
2605// void ChartCanvas::SetFocus()
2606// {
2607// printf("set %d\n", m_canvasIndex);
2608// //wxWindow:SetFocus();
2609// }
2610
2611void ChartCanvas::TriggerDeferredFocus() {
2612 // #if defined(__WXGTK__) || defined(__WXOSX__)
2613
2614 m_deferredFocusTimer.Start(20, true);
2615
2616#if defined(__WXGTK__) || defined(__WXOSX__)
2617 gFrame->Raise();
2618#endif
2619
2620 // gFrame->Raise();
2621 // #else
2622 // SetFocus();
2623 // Refresh(true);
2624 // #endif
2625}
2626
2627void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2628 SetFocus();
2629 Refresh(true);
2630}
2631
2632void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2633 if (SendKeyEventToPlugins(event))
2634 return; // PlugIn did something, and does not want the canvas to do
2635 // anything else
2636
2637 int key_char = event.GetKeyCode();
2638 switch (key_char) {
2639 case '?':
2640 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2641 break;
2642 case '+':
2643 ZoomCanvas(g_plus_minus_zoom_factor, false);
2644 break;
2645 case '-':
2646 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2647 break;
2648 default:
2649 break;
2650 }
2651 if (g_benable_rotate) {
2652 switch (key_char) {
2653 case ']':
2654 RotateCanvas(1);
2655 Refresh();
2656 break;
2657
2658 case '[':
2659 RotateCanvas(-1);
2660 Refresh();
2661 break;
2662
2663 case '\\':
2664 DoRotateCanvas(0);
2665 break;
2666 }
2667 }
2668
2669 event.Skip();
2670}
2671
2672void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2673 if (SendKeyEventToPlugins(event))
2674 return; // PlugIn did something, and does not want the canvas to do
2675 // anything else
2676
2677 bool b_handled = false;
2678
2679 m_modkeys = event.GetModifiers();
2680
2681 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2682
2683#ifdef OCPN_ALT_MENUBAR
2684#ifndef __WXOSX__
2685 // If the permanent menubar is disabled, we show it temporarily when Alt is
2686 // pressed or when Alt + a letter is presssed (for the top-menu-level
2687 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2688 // some special cases.
2689 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2690 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2691 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2692 if (!g_bTempShowMenuBar) {
2693 g_bTempShowMenuBar = true;
2694 parent_frame->ApplyGlobalSettings(false);
2695 }
2696 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2697 event.Skip();
2698 return;
2699 }
2700 // If another key is pressed while Alt is down, do NOT toggle the menus when
2701 // Alt is released
2702 if (event.GetKeyCode() != WXK_ALT) {
2703 m_bMayToggleMenuBar = false;
2704 }
2705 }
2706#endif
2707#endif
2708
2709 // HOTKEYS
2710 switch (event.GetKeyCode()) {
2711 case WXK_TAB:
2712 // parent_frame->SwitchKBFocus( this );
2713 break;
2714
2715 case WXK_MENU:
2716 int x, y;
2717 event.GetPosition(&x, &y);
2718 m_FinishRouteOnKillFocus = false;
2719 CallPopupMenu(x, y);
2720 m_FinishRouteOnKillFocus = true;
2721 break;
2722
2723 case WXK_ALT:
2724 m_modkeys |= wxMOD_ALT;
2725 break;
2726
2727 case WXK_CONTROL:
2728 m_modkeys |= wxMOD_CONTROL;
2729 break;
2730
2731#ifdef __WXOSX__
2732 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2733 case WXK_RAW_CONTROL:
2734 m_modkeys |= wxMOD_RAW_CONTROL;
2735 break;
2736#endif
2737
2738 case WXK_LEFT:
2739 if (m_modkeys == wxMOD_CONTROL)
2740 parent_frame->DoStackDown(this);
2741 else if (g_bsmoothpanzoom) {
2742 StartTimedMovement();
2743 m_panx = -1;
2744 } else {
2745 PanCanvas(-panspeed, 0);
2746 }
2747 b_handled = true;
2748 break;
2749
2750 case WXK_UP:
2751 if (g_bsmoothpanzoom) {
2752 StartTimedMovement();
2753 m_pany = -1;
2754 } else
2755 PanCanvas(0, -panspeed);
2756 b_handled = true;
2757 break;
2758
2759 case WXK_RIGHT:
2760 if (m_modkeys == wxMOD_CONTROL)
2761 parent_frame->DoStackUp(this);
2762 else if (g_bsmoothpanzoom) {
2763 StartTimedMovement();
2764 m_panx = 1;
2765 } else
2766 PanCanvas(panspeed, 0);
2767 b_handled = true;
2768
2769 break;
2770
2771 case WXK_DOWN:
2772 if (g_bsmoothpanzoom) {
2773 StartTimedMovement();
2774 m_pany = 1;
2775 } else
2776 PanCanvas(0, panspeed);
2777 b_handled = true;
2778 break;
2779
2780 case WXK_F2: {
2781 // TogglebFollow();
2782 if (event.ShiftDown()) {
2783 double scale = GetVP().view_scale_ppm;
2784 auto current_family = m_pQuilt->GetRefFamily();
2785 auto target_family = CHART_FAMILY_UNKNOWN;
2786 if (current_family == CHART_FAMILY_RASTER)
2787 target_family = CHART_FAMILY_VECTOR;
2788 else
2789 target_family = CHART_FAMILY_RASTER;
2790
2791 std::shared_ptr<HostApi> host_api;
2792 host_api = GetHostApi();
2793 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2794
2795 if (api_121)
2796 api_121->SelectChartFamily(m_canvasIndex,
2797 (ChartFamilyEnumPI)target_family);
2798
2799 } else
2800 TogglebFollow();
2801 break;
2802 }
2803 case WXK_F3: {
2804 SetShowENCText(!GetShowENCText());
2805 Refresh(true);
2806 InvalidateGL();
2807 break;
2808 }
2809 case WXK_F4:
2810 if (!m_bMeasure_Active) {
2811 if (event.ShiftDown())
2812 m_bMeasure_DistCircle = true;
2813 else
2814 m_bMeasure_DistCircle = false;
2815
2816 StartMeasureRoute();
2817 } else {
2818 CancelMeasureRoute();
2819
2820 SetCursor(*pCursorArrow);
2821
2822 // SurfaceToolbar();
2823 InvalidateGL();
2824 Refresh(false);
2825 }
2826
2827 break;
2828
2829 case WXK_F5:
2830 parent_frame->ToggleColorScheme();
2831 gFrame->Raise();
2832 TriggerDeferredFocus();
2833 break;
2834
2835 case WXK_F6: {
2836 int mod = m_modkeys & wxMOD_SHIFT;
2837 if (mod != m_brightmod) {
2838 m_brightmod = mod;
2839 m_bbrightdir = !m_bbrightdir;
2840 }
2841
2842 if (!m_bbrightdir) {
2843 g_nbrightness -= 10;
2844 if (g_nbrightness <= MIN_BRIGHT) {
2845 g_nbrightness = MIN_BRIGHT;
2846 m_bbrightdir = true;
2847 }
2848 } else {
2849 g_nbrightness += 10;
2850 if (g_nbrightness >= MAX_BRIGHT) {
2851 g_nbrightness = MAX_BRIGHT;
2852 m_bbrightdir = false;
2853 }
2854 }
2855
2856 SetScreenBrightness(g_nbrightness);
2857 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2858
2859 SetFocus(); // just in case the external program steals it....
2860 gFrame->Raise(); // And reactivate the application main
2861
2862 break;
2863 }
2864
2865 case WXK_F7:
2866 parent_frame->DoStackDown(this);
2867 break;
2868
2869 case WXK_F8:
2870 parent_frame->DoStackUp(this);
2871 break;
2872
2873#ifndef __WXOSX__
2874 case WXK_F9: {
2875 ToggleCanvasQuiltMode();
2876 break;
2877 }
2878#endif
2879
2880 case WXK_F11:
2881 parent_frame->ToggleFullScreen();
2882 b_handled = true;
2883 break;
2884
2885 case WXK_F12: {
2886 if (m_modkeys == wxMOD_ALT) {
2887 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2888 } else {
2889 ToggleChartOutlines();
2890 }
2891 break;
2892 }
2893
2894 case WXK_PAUSE: // Drop MOB
2895 parent_frame->ActivateMOB();
2896 break;
2897
2898 // NUMERIC PAD
2899 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2900 case WXK_PAGEUP: {
2901 ZoomCanvas(g_plus_minus_zoom_factor, false);
2902 break;
2903 }
2904 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2905 case WXK_PAGEDOWN: {
2906 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2907 break;
2908 }
2909 case WXK_DELETE:
2910 case WXK_BACK:
2911 if (m_bMeasure_Active) {
2912 if (m_nMeasureState > 2) {
2913 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2914 m_pMeasureRoute->m_lastMousePointIndex =
2915 m_pMeasureRoute->GetnPoints();
2916 m_nMeasureState--;
2917 gFrame->RefreshAllCanvas();
2918 } else {
2919 CancelMeasureRoute();
2920 StartMeasureRoute();
2921 }
2922 }
2923 break;
2924 default:
2925 break;
2926 }
2927
2928 if (event.GetKeyCode() < 128) // ascii
2929 {
2930 int key_char = event.GetKeyCode();
2931
2932 // Handle both QWERTY and AZERTY keyboard separately for a few control
2933 // codes
2934 if (!g_b_assume_azerty) {
2935#ifdef __WXMAC__
2936 if (g_benable_rotate) {
2937 switch (key_char) {
2938 // On other platforms these are handled in OnKeyChar, which
2939 // (apparently) works better in some locales. On OS X it is better
2940 // to handle them here, since pressing Alt (which should change the
2941 // rotation speed) changes the key char and so prevents the keys
2942 // from working.
2943 case ']':
2944 RotateCanvas(1);
2945 b_handled = true;
2946 break;
2947
2948 case '[':
2949 RotateCanvas(-1);
2950 b_handled = true;
2951 break;
2952
2953 case '\\':
2954 DoRotateCanvas(0);
2955 b_handled = true;
2956 break;
2957 }
2958 }
2959#endif
2960 } else { // AZERTY
2961 switch (key_char) {
2962 case 43:
2963 ZoomCanvas(g_plus_minus_zoom_factor, false);
2964 break;
2965
2966 case 54: // '-' alpha/num pad
2967 // case 56: // '_' alpha/num pad
2968 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2969 break;
2970 }
2971 }
2972
2973#ifdef __WXOSX__
2974 // Ctrl+Cmd+F toggles fullscreen on macOS
2975 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2976 m_modkeys & wxMOD_RAW_CONTROL) {
2977 parent_frame->ToggleFullScreen();
2978 return;
2979 }
2980#endif
2981
2982 if (event.ControlDown()) key_char -= 64;
2983
2984 if (key_char >= '0' && key_char <= '9')
2985 SetGroupIndex(key_char - '0');
2986 else
2987
2988 switch (key_char) {
2989 case 'A':
2990 SetShowENCAnchor(!GetShowENCAnchor());
2991 ReloadVP();
2992
2993 break;
2994
2995 case 'C':
2996 parent_frame->ToggleColorScheme();
2997 break;
2998
2999 case 'D': {
3000 int x, y;
3001 event.GetPosition(&x, &y);
3002 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3003 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3004 // First find out what kind of chart is being used
3005 if (!pPopupDetailSlider) {
3006 if (VPoint.b_quilt) {
3007 if (m_pQuilt) {
3008 if (m_pQuilt->GetChartAtPix(
3009 VPoint,
3010 wxPoint(
3011 x, y))) // = null if no chart loaded for this point
3012 {
3013 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3014 ->GetChartType();
3015 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3016 ->GetChartFamily();
3017 }
3018 }
3019 } else {
3020 if (m_singleChart) {
3021 ChartType = m_singleChart->GetChartType();
3022 ChartFam = m_singleChart->GetChartFamily();
3023 }
3024 }
3025 // If a charttype is found show the popupslider
3026 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3027 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3029 this, -1, ChartType, ChartFam,
3030 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3031 wxDefaultSize, wxSIMPLE_BORDER, "");
3033 }
3034 } else //( !pPopupDetailSlider ) close popupslider
3035 {
3037 pPopupDetailSlider = NULL;
3038 }
3039 break;
3040 }
3041
3042 case 'E':
3043 m_nmea_log->Show();
3044 m_nmea_log->Raise();
3045 break;
3046
3047 case 'L':
3048 SetShowENCLights(!GetShowENCLights());
3049 ReloadVP();
3050
3051 break;
3052
3053 case 'M':
3054 if (event.ShiftDown())
3055 m_bMeasure_DistCircle = true;
3056 else
3057 m_bMeasure_DistCircle = false;
3058
3059 StartMeasureRoute();
3060 break;
3061
3062 case 'N':
3063 if (g_bInlandEcdis && ps52plib) {
3064 SetENCDisplayCategory((_DisCat)STANDARD);
3065 }
3066 break;
3067
3068 case 'O':
3069 ToggleChartOutlines();
3070 break;
3071
3072 case 'Q':
3073 ToggleCanvasQuiltMode();
3074 break;
3075
3076 case 'P':
3077 parent_frame->ToggleTestPause();
3078 break;
3079 case 'R':
3080 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3081 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3082 g_iNavAidRadarRingsNumberVisible = 1;
3083 else if (!g_bNavAidRadarRingsShown &&
3084 g_iNavAidRadarRingsNumberVisible == 1)
3085 g_iNavAidRadarRingsNumberVisible = 0;
3086 break;
3087 case 'S':
3088 SetShowENCDepth(!m_encShowDepth);
3089 ReloadVP();
3090 break;
3091
3092 case 'T':
3093 SetShowENCText(!GetShowENCText());
3094 ReloadVP();
3095 break;
3096
3097 case 'U':
3098 SetShowENCDataQual(!GetShowENCDataQual());
3099 ReloadVP();
3100 break;
3101
3102 case 'V':
3103 m_bShowNavobjects = !m_bShowNavobjects;
3104 Refresh(true);
3105 break;
3106
3107 case 'W': // W Toggle CPA alarm
3108 ToggleCPAWarn();
3109
3110 break;
3111
3112 case 1: // Ctrl A
3113 TogglebFollow();
3114
3115 break;
3116
3117 case 2: // Ctrl B
3118 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3119 break;
3120
3121 case 13: // Ctrl M // Drop Marker at cursor
3122 {
3123 if (event.ControlDown()) gFrame->DropMarker(false);
3124 break;
3125 }
3126
3127 case 14: // Ctrl N - Activate next waypoint in a route
3128 {
3129 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3130 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3131 if ((indexActive + 1) <= r->GetnPoints()) {
3133 InvalidateGL();
3134 Refresh(false);
3135 }
3136 }
3137 break;
3138 }
3139
3140 case 15: // Ctrl O - Drop Marker at boat's position
3141 {
3142 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3143 break;
3144 }
3145
3146 case 32: // Special needs use space bar
3147 {
3148 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3149 break;
3150 }
3151
3152 case -32: // Ctrl Space // Drop MOB
3153 {
3154 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3155
3156 break;
3157 }
3158
3159 case -20: // Ctrl ,
3160 {
3161 parent_frame->DoSettings();
3162 break;
3163 }
3164 case 17: // Ctrl Q
3165 parent_frame->Close();
3166 return;
3167
3168 case 18: // Ctrl R
3169 StartRoute();
3170 return;
3171
3172 case 20: // Ctrl T
3173 if (NULL == pGoToPositionDialog) // There is one global instance of
3174 // the Go To Position Dialog
3176 pGoToPositionDialog->SetCanvas(this);
3177 pGoToPositionDialog->Show();
3178 break;
3179
3180 case 25: // Ctrl Y
3181 if (undo->AnythingToRedo()) {
3182 undo->RedoNextAction();
3183 InvalidateGL();
3184 Refresh(false);
3185 }
3186 break;
3187
3188 case 26:
3189 if (event.ShiftDown()) { // Shift-Ctrl-Z
3190 if (undo->AnythingToRedo()) {
3191 undo->RedoNextAction();
3192 InvalidateGL();
3193 Refresh(false);
3194 }
3195 } else { // Ctrl Z
3196 if (undo->AnythingToUndo()) {
3197 undo->UndoLastAction();
3198 InvalidateGL();
3199 Refresh(false);
3200 }
3201 }
3202 break;
3203
3204 case 27:
3205 // Generic break
3206 if (m_bMeasure_Active) {
3207 CancelMeasureRoute();
3208
3209 SetCursor(*pCursorArrow);
3210
3211 // SurfaceToolbar();
3212 gFrame->RefreshAllCanvas();
3213 }
3214
3215 if (m_routeState) // creating route?
3216 {
3217 FinishRoute();
3218 // SurfaceToolbar();
3219 InvalidateGL();
3220 Refresh(false);
3221 }
3222
3223 break;
3224
3225 case 7: // Ctrl G
3226 switch (gamma_state) {
3227 case (0):
3228 r_gamma_mult = 0;
3229 g_gamma_mult = 1;
3230 b_gamma_mult = 0;
3231 gamma_state = 1;
3232 break;
3233 case (1):
3234 r_gamma_mult = 1;
3235 g_gamma_mult = 0;
3236 b_gamma_mult = 0;
3237 gamma_state = 2;
3238 break;
3239 case (2):
3240 r_gamma_mult = 1;
3241 g_gamma_mult = 1;
3242 b_gamma_mult = 1;
3243 gamma_state = 0;
3244 break;
3245 }
3246 SetScreenBrightness(g_nbrightness);
3247
3248 break;
3249
3250 case 9: // Ctrl I
3251 if (event.ControlDown()) {
3252 m_bShowCompassWin = !m_bShowCompassWin;
3253 SetShowGPSCompassWindow(m_bShowCompassWin);
3254 Refresh(false);
3255 }
3256 break;
3257
3258 default:
3259 break;
3260
3261 } // switch
3262 }
3263
3264 // Allow OnKeyChar to catch the key events too.
3265 if (!b_handled) {
3266 event.Skip();
3267 }
3268}
3269
3270void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3271 if (SendKeyEventToPlugins(event))
3272 return; // PlugIn did something, and does not want the canvas to do
3273 // anything else
3274
3275 switch (event.GetKeyCode()) {
3276 case WXK_TAB:
3277 parent_frame->SwitchKBFocus(this);
3278 break;
3279
3280 case WXK_LEFT:
3281 case WXK_RIGHT:
3282 m_panx = 0;
3283 if (!m_pany) m_panspeed = 0;
3284 break;
3285
3286 case WXK_UP:
3287 case WXK_DOWN:
3288 m_pany = 0;
3289 if (!m_panx) m_panspeed = 0;
3290 break;
3291
3292 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3293 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3294 case WXK_PAGEUP:
3295 case WXK_PAGEDOWN:
3296 if (m_mustmove) DoMovement(m_mustmove);
3297
3298 m_zoom_factor = 1;
3299 break;
3300
3301 case WXK_ALT:
3302 m_modkeys &= ~wxMOD_ALT;
3303#ifdef OCPN_ALT_MENUBAR
3304#ifndef __WXOSX__
3305 // If the permanent menu bar is disabled, and we are not in the middle of
3306 // another key combo, then show the menu bar temporarily when Alt is
3307 // released (or hide it if already visible).
3308 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3309 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3310 parent_frame->ApplyGlobalSettings(false);
3311 }
3312 m_bMayToggleMenuBar = true;
3313#endif
3314#endif
3315 break;
3316
3317 case WXK_CONTROL:
3318 m_modkeys &= ~wxMOD_CONTROL;
3319 break;
3320 }
3321
3322 if (event.GetKeyCode() < 128) // ascii
3323 {
3324 int key_char = event.GetKeyCode();
3325
3326 // Handle both QWERTY and AZERTY keyboard separately for a few control
3327 // codes
3328 if (!g_b_assume_azerty) {
3329 switch (key_char) {
3330 case '+':
3331 case '=':
3332 case '-':
3333 case '_':
3334 case 54:
3335 case 56: // '_' alpha/num pad
3336 DoMovement(m_mustmove);
3337
3338 // m_zoom_factor = 1;
3339 break;
3340 case '[':
3341 case ']':
3342 DoMovement(m_mustmove);
3343 m_rotation_speed = 0;
3344 break;
3345 }
3346 } else {
3347 switch (key_char) {
3348 case 43:
3349 case 54: // '-' alpha/num pad
3350 case 56: // '_' alpha/num pad
3351 DoMovement(m_mustmove);
3352
3353 m_zoom_factor = 1;
3354 break;
3355 }
3356 }
3357 }
3358 event.Skip();
3359}
3360
3361void ChartCanvas::ToggleChartOutlines() {
3362 m_bShowOutlines = !m_bShowOutlines;
3363
3364 Refresh(false);
3365
3366#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3367 // needs a full refresh
3368 if (g_bopengl) InvalidateGL();
3369#endif
3370}
3371
3372void ChartCanvas::ToggleLookahead() {
3373 m_bLookAhead = !m_bLookAhead;
3374 m_OSoffsetx = 0; // center ownship
3375 m_OSoffsety = 0;
3376}
3377
3378void ChartCanvas::SetUpMode(int mode) {
3379 m_upMode = mode;
3380
3381 if (mode != NORTH_UP_MODE) {
3382 // Stuff the COGAvg table in case COGUp is selected
3383 double stuff = 0;
3384 if (!std::isnan(gCog)) stuff = gCog;
3385
3386 if (g_COGAvgSec > 0) {
3387 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3388 }
3389 g_COGAvg = stuff;
3390 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3391 } else {
3392 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3393 SetVPRotation(GetVPSkew());
3394 else
3395 SetVPRotation(0); /* reset to north up */
3396 }
3397
3398 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3399 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3400
3401 UpdateGPSCompassStatusBox(true);
3402 gFrame->DoChartUpdate();
3403}
3404
3405bool ChartCanvas::DoCanvasCOGSet() {
3406 if (GetUpMode() == NORTH_UP_MODE) return false;
3407 double cog_use = g_COGAvg;
3408 if (g_btenhertz) cog_use = gCog;
3409
3410 double rotation = 0;
3411 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3412 rotation = -gHdt * PI / 180.;
3413 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3414 rotation = -cog_use * PI / 180.;
3415
3416 SetVPRotation(rotation);
3417 return true;
3418}
3419
3420double easeOutCubic(double t) {
3421 // Starts quickly and slows down toward the end
3422 return 1.0 - pow(1.0 - t, 3.0);
3423}
3424
3425void ChartCanvas::StartChartDragInertia() {
3426 m_bChartDragging = false;
3427
3428 // Set some parameters
3429 m_chart_drag_inertia_time = 750; // msec
3430 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3431 m_last_elapsed = 0;
3432
3433 // Calculate ending drag velocity
3434 size_t n_vel = 10;
3435 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3436 int xacc = 0;
3437 int yacc = 0;
3438 double tacc = 0;
3439 size_t length = m_drag_vec_t.size();
3440 for (size_t i = 0; i < n_vel; i++) {
3441 xacc += m_drag_vec_x.at(length - 1 - i);
3442 yacc += m_drag_vec_y.at(length - 1 - i);
3443 tacc += m_drag_vec_t.at(length - 1 - i);
3444 }
3445
3446 if (tacc == 0) return;
3447
3448 double drag_velocity_x = xacc / tacc;
3449 double drag_velocity_y = yacc / tacc;
3450 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3451 // drag_velocity_y);
3452
3453 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3454 // touch tap.
3455 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3456
3457 m_chart_drag_velocity_x = drag_velocity_x;
3458 m_chart_drag_velocity_y = drag_velocity_y;
3459
3460 m_chart_drag_inertia_active = true;
3461 // First callback as fast as possible.
3462 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3463}
3464
3465void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3466 if (!m_chart_drag_inertia_active) return;
3467 // Calculate time fraction from 0..1
3468 wxLongLong now = wxGetLocalTimeMillis();
3469 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3470 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3471 if (t > 1.0) t = 1.0;
3472 double e = 1.0 - easeOutCubic(t); // 0..1
3473
3474 double dx =
3475 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3476 double dy =
3477 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3478
3479 m_last_elapsed = elapsed;
3480
3481 // Ensure that target destination lies on whole-pixel boundary
3482 // This allows the render engine to use a faster FBO copy method for drawing
3483 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3484 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3485 double inertia_lat, inertia_lon;
3486 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3487 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3488 // Check if ownship has moved off-screen
3489 if (!IsOwnshipOnScreen()) {
3490 m_bFollow = false; // update the follow flag
3491 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3492 UpdateFollowButtonState();
3493 m_OSoffsetx = 0;
3494 m_OSoffsety = 0;
3495 } else {
3496 m_OSoffsetx += dx;
3497 m_OSoffsety -= dy;
3498 }
3499
3500 Refresh(false);
3501
3502 // Stop condition
3503 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3504 m_chart_drag_inertia_timer.Stop();
3505
3506 // Disable chart pan movement logic
3507 m_target_lat = GetVP().clat;
3508 m_target_lon = GetVP().clon;
3509 m_pan_drag.x = m_pan_drag.y = 0;
3510 m_panx = m_pany = 0;
3511 m_chart_drag_inertia_active = false;
3512 DoCanvasUpdate();
3513
3514 } else {
3515 int target_redraw_interval = 40; // msec
3516 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3517 }
3518}
3519
3520void ChartCanvas::StopMovement() {
3521 m_panx = m_pany = 0;
3522 m_panspeed = 0;
3523 m_zoom_factor = 1;
3524 m_rotation_speed = 0;
3525 m_mustmove = 0;
3526#if 0
3527#if !defined(__WXGTK__) && !defined(__WXQT__)
3528 SetFocus();
3529 gFrame->Raise();
3530#endif
3531#endif
3532}
3533
3534/* instead of integrating in timer callbacks
3535 (which do not always get called fast enough)
3536 we can perform the integration of movement
3537 at each render frame based on the time change */
3538bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3539 // Start/restart the stop movement timer
3540 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3541
3542 if (!pMovementTimer->IsRunning()) {
3543 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3544 }
3545
3546 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3547 // already moving, gets called again because of key-repeat event
3548 return false;
3549 }
3550
3551 m_last_movement_time = wxDateTime::UNow();
3552
3553 return true;
3554}
3555void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3556 int nstep) {
3557 // Save the target
3558 m_target_lat = target_lat;
3559 m_target_lon = target_lon;
3560
3561 // Save the start point
3562 m_start_lat = GetVP().clat;
3563 m_start_lon = GetVP().clon;
3564
3565 m_VPMovementTimer.Start(1, true); // oneshot
3566 m_timed_move_vp_active = true;
3567 m_stvpc = 0;
3568 m_timedVP_step = nstep;
3569}
3570
3571void ChartCanvas::DoTimedMovementVP() {
3572 if (!m_timed_move_vp_active) return; // not active
3573 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3574 StopMovement();
3575 return;
3576 }
3577 // Stop condition
3578 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3579 double d2 =
3580 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3581 d2 = pow(d2, 0.5);
3582
3583 if (d2 < one_pix) {
3584 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3585 StopMovementVP();
3586 return;
3587 }
3588
3589 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3590 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3591 // StopMovementVP();
3592 // return;
3593 // }
3594
3595 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3596 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3597
3598 m_run_lat = new_lat;
3599 m_run_lon = new_lon;
3600
3601 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3602}
3603
3604void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3605
3606void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3607
3608void ChartCanvas::StartTimedMovementTarget() {}
3609
3610void ChartCanvas::DoTimedMovementTarget() {}
3611
3612void ChartCanvas::StopMovementTarget() {}
3613int ntm;
3614
3615void ChartCanvas::DoTimedMovement() {
3616 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3617 !m_rotation_speed)
3618 return; /* not moving */
3619
3620 wxDateTime now = wxDateTime::UNow();
3621 long dt = 0;
3622 if (m_last_movement_time.IsValid())
3623 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3624
3625 m_last_movement_time = now;
3626
3627 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3628 dt = 500;
3629
3630 DoMovement(dt);
3631}
3632
3634 /* if we get here quickly assume 1ms so that some movement occurs */
3635 if (dt == 0) dt = 1;
3636
3637 m_mustmove -= dt;
3638 if (m_mustmove < 0) m_mustmove = 0;
3639
3640 if (!m_inPinch) { // this stops compound zoom/pan
3641 if (m_pan_drag.x || m_pan_drag.y) {
3642 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3643 m_pan_drag.x = m_pan_drag.y = 0;
3644 }
3645
3646 if (m_panx || m_pany) {
3647 const double slowpan = .1, maxpan = 2;
3648 if (m_modkeys == wxMOD_ALT)
3649 m_panspeed = slowpan;
3650 else {
3651 m_panspeed += (double)dt / 500; /* apply acceleration */
3652 m_panspeed = wxMin(maxpan, m_panspeed);
3653 }
3654 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3655 }
3656 }
3657 if (m_zoom_factor != 1) {
3658 double alpha = 400, beta = 1.5;
3659 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3660
3661 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3662
3663 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3664
3665 // Try to hit the zoom target exactly.
3666 // if(m_wheelzoom_stop_oneshot > 0)
3667 {
3668 if (zoom_factor > 1) {
3669 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3670 zoom_factor = VPoint.chart_scale / m_zoom_target;
3671 }
3672
3673 else if (zoom_factor < 1) {
3674 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3675 zoom_factor = VPoint.chart_scale / m_zoom_target;
3676 }
3677 }
3678
3679 if (fabs(zoom_factor - 1) > 1e-4) {
3680 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3681 } else {
3682 StopMovement();
3683 }
3684
3685 if (m_wheelzoom_stop_oneshot > 0) {
3686 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3687 m_wheelzoom_stop_oneshot = 0;
3688 StopMovement();
3689 }
3690
3691 // Don't overshoot the zoom target.
3692 if (zoom_factor > 1) {
3693 if (VPoint.chart_scale <= m_zoom_target) {
3694 m_wheelzoom_stop_oneshot = 0;
3695 StopMovement();
3696 }
3697 } else if (zoom_factor < 1) {
3698 if (VPoint.chart_scale >= m_zoom_target) {
3699 m_wheelzoom_stop_oneshot = 0;
3700 StopMovement();
3701 }
3702 }
3703 }
3704 }
3705
3706 if (m_rotation_speed) { /* in degrees per second */
3707 double speed = m_rotation_speed;
3708 if (m_modkeys == wxMOD_ALT) speed /= 10;
3709 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3710 }
3711}
3712
3713void ChartCanvas::SetColorScheme(ColorScheme cs) {
3714 SetAlertString("");
3715
3716 // Setup ownship image pointers
3717 switch (cs) {
3718 case GLOBAL_COLOR_SCHEME_DAY:
3719 m_pos_image_red = &m_os_image_red_day;
3720 m_pos_image_grey = &m_os_image_grey_day;
3721 m_pos_image_yellow = &m_os_image_yellow_day;
3722 m_pos_image_user = m_pos_image_user_day;
3723 m_pos_image_user_grey = m_pos_image_user_grey_day;
3724 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3725 m_cTideBitmap = m_bmTideDay;
3726 m_cCurrentBitmap = m_bmCurrentDay;
3727
3728 break;
3729 case GLOBAL_COLOR_SCHEME_DUSK:
3730 m_pos_image_red = &m_os_image_red_dusk;
3731 m_pos_image_grey = &m_os_image_grey_dusk;
3732 m_pos_image_yellow = &m_os_image_yellow_dusk;
3733 m_pos_image_user = m_pos_image_user_dusk;
3734 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3735 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3736 m_cTideBitmap = m_bmTideDusk;
3737 m_cCurrentBitmap = m_bmCurrentDusk;
3738 break;
3739 case GLOBAL_COLOR_SCHEME_NIGHT:
3740 m_pos_image_red = &m_os_image_red_night;
3741 m_pos_image_grey = &m_os_image_grey_night;
3742 m_pos_image_yellow = &m_os_image_yellow_night;
3743 m_pos_image_user = m_pos_image_user_night;
3744 m_pos_image_user_grey = m_pos_image_user_grey_night;
3745 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3746 m_cTideBitmap = m_bmTideNight;
3747 m_cCurrentBitmap = m_bmCurrentNight;
3748 break;
3749 default:
3750 m_pos_image_red = &m_os_image_red_day;
3751 m_pos_image_grey = &m_os_image_grey_day;
3752 m_pos_image_yellow = &m_os_image_yellow_day;
3753 m_pos_image_user = m_pos_image_user_day;
3754 m_pos_image_user_grey = m_pos_image_user_grey_day;
3755 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3756 m_cTideBitmap = m_bmTideDay;
3757 m_cCurrentBitmap = m_bmCurrentDay;
3758 break;
3759 }
3760
3761 CreateDepthUnitEmbossMaps(cs);
3762 CreateOZEmbossMapData(cs);
3763
3764 // Set up fog effect base color
3765 m_fog_color = wxColor(
3766 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3767 float dim = 1.0;
3768 switch (cs) {
3769 case GLOBAL_COLOR_SCHEME_DUSK:
3770 dim = 0.5;
3771 break;
3772 case GLOBAL_COLOR_SCHEME_NIGHT:
3773 dim = 0.25;
3774 break;
3775 default:
3776 break;
3777 }
3778 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3779 m_fog_color.Blue() * dim);
3780
3781 // Really dark
3782#if 0
3783 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3784 SetBackgroundColour( wxColour(0,0,0) );
3785
3786 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3787 }
3788 else{
3789 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3790#ifndef __WXMAC__
3791 SetBackgroundColour( wxNullColour );
3792#endif
3793 }
3794#endif
3795
3796 // UpdateToolbarColorScheme(cs);
3797
3798 m_Piano->SetColorScheme(cs);
3799
3800 m_Compass->SetColorScheme(cs);
3801
3802 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3803
3804 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3805
3806 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3807 if (m_notification_button) {
3808 m_notification_button->SetColorScheme(cs);
3809 }
3810
3811#ifdef ocpnUSE_GL
3812 if (g_bopengl && m_glcc) {
3813 m_glcc->SetColorScheme(cs);
3814 g_glTextureManager->ClearAllRasterTextures();
3815 // m_glcc->FlushFBO();
3816 }
3817#endif
3818 SetbTCUpdate(true); // force re-render of tide/current locators
3819 m_brepaint_piano = true;
3820
3821 ReloadVP();
3822
3823 m_cs = cs;
3824}
3825
3826wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3827 wxImage img = Bitmap.ConvertToImage();
3828 int sx = img.GetWidth();
3829 int sy = img.GetHeight();
3830
3831 wxImage new_img(img);
3832
3833 for (int i = 0; i < sx; i++) {
3834 for (int j = 0; j < sy; j++) {
3835 if (!img.IsTransparent(i, j)) {
3836 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3837 (unsigned char)(img.GetGreen(i, j) * factor),
3838 (unsigned char)(img.GetBlue(i, j) * factor));
3839 }
3840 }
3841 }
3842
3843 wxBitmap ret = wxBitmap(new_img);
3844
3845 return ret;
3846}
3847
3848void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3849 int max) {
3850 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3851 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3852
3853 if (!m_pBrightPopup) {
3854 // Calculate size
3855 int x, y;
3856 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3857
3858 m_pBrightPopup = new TimedPopupWin(this, 3);
3859
3860 m_pBrightPopup->SetSize(x, y);
3861 m_pBrightPopup->Move(120, 120);
3862 }
3863
3864 int bmpsx = m_pBrightPopup->GetSize().x;
3865 int bmpsy = m_pBrightPopup->GetSize().y;
3866
3867 wxBitmap bmp(bmpsx, bmpsx);
3868 wxMemoryDC mdc(bmp);
3869
3870 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3871 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3872 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3873 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3874 mdc.Clear();
3875
3876 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3877
3878 mdc.SetFont(*pfont);
3879 wxString val;
3880
3881 if (brightness == max)
3882 val = "MAX";
3883 else if (brightness == min)
3884 val = "MIN";
3885 else
3886 val.Printf("%3d", brightness);
3887
3888 mdc.DrawText(val, 0, 0);
3889
3890 mdc.SelectObject(wxNullBitmap);
3891
3892 m_pBrightPopup->SetBitmap(bmp);
3893 m_pBrightPopup->Show();
3894 m_pBrightPopup->Refresh();
3895}
3896
3897void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3898 m_b_rot_hidef = true;
3899 ReloadVP();
3900}
3901
3902void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3903 if (!g_bRollover) return;
3904
3905 bool b_need_refresh = false;
3906
3907 wxSize win_size = GetSize() * m_displayScale;
3908 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3909
3910 // Handle the AIS Rollover Window first
3911 bool showAISRollover = false;
3912 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3913 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3914 SelectItem *pFind = pSelectAIS->FindSelection(
3915 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3916 if (pFind) {
3917 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3918 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3919
3920 if (ptarget) {
3921 showAISRollover = true;
3922
3923 if (NULL == m_pAISRolloverWin) {
3924 m_pAISRolloverWin = new RolloverWin(this);
3925 m_pAISRolloverWin->IsActive(false);
3926 b_need_refresh = true;
3927 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3928 m_AISRollover_MMSI != FoundAIS_MMSI) {
3929 // Sometimes the mouse moves fast enough to get over a new AIS
3930 // target before the one-shot has fired to remove the old target.
3931 // Result: wrong target data is shown.
3932 // Detect this case,close the existing rollover ASAP, and restart
3933 // the timer.
3934 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3935 m_pAISRolloverWin->IsActive(false);
3936 m_AISRollover_MMSI = 0;
3937 Refresh();
3938 return;
3939 }
3940
3941 m_AISRollover_MMSI = FoundAIS_MMSI;
3942
3943 if (!m_pAISRolloverWin->IsActive()) {
3944 wxString s = ptarget->GetRolloverString();
3945 m_pAISRolloverWin->SetString(s);
3946
3947 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3948 AIS_ROLLOVER, win_size);
3949 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3950 m_pAISRolloverWin->IsActive(true);
3951 b_need_refresh = true;
3952 }
3953 }
3954 } else {
3955 m_AISRollover_MMSI = 0;
3956 showAISRollover = false;
3957 }
3958 }
3959
3960 // Maybe turn the rollover off
3961 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3962 m_pAISRolloverWin->IsActive(false);
3963 m_AISRollover_MMSI = 0;
3964 b_need_refresh = true;
3965 }
3966
3967 // Now the Route info rollover
3968 // Show the route segment info
3969 bool showRouteRollover = false;
3970
3971 if (NULL == m_pRolloverRouteSeg) {
3972 // Get a list of all selectable sgements, and search for the first
3973 // visible segment as the rollover target.
3974
3975 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3976 SelectableItemList SelList = pSelect->FindSelectionList(
3977 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3978 auto node = SelList.begin();
3979 while (node != SelList.end()) {
3980 SelectItem *pFindSel = *node;
3981
3982 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3983
3984 if (pr && pr->IsVisible()) {
3985 m_pRolloverRouteSeg = pFindSel;
3986 showRouteRollover = true;
3987
3988 if (NULL == m_pRouteRolloverWin) {
3989 m_pRouteRolloverWin = new RolloverWin(this, 10);
3990 m_pRouteRolloverWin->IsActive(false);
3991 }
3992
3993 if (!m_pRouteRolloverWin->IsActive()) {
3994 wxString s;
3995 RoutePoint *segShow_point_a =
3996 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3997 RoutePoint *segShow_point_b =
3998 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3999
4000 double brg, dist;
4001 DistanceBearingMercator(
4002 segShow_point_b->m_lat, segShow_point_b->m_lon,
4003 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4004
4005 if (!pr->m_bIsInLayer)
4006 s.Append(_("Route") + ": ");
4007 else
4008 s.Append(_("Layer Route: "));
4009
4010 if (pr->m_RouteNameString.IsEmpty())
4011 s.Append(_("(unnamed)"));
4012 else
4013 s.Append(pr->m_RouteNameString);
4014
4015 s << "\n"
4016 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4017 << "\n"
4018 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4019 << segShow_point_b->GetName() << "\n";
4020
4021 if (g_bShowTrue)
4022 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4023 (int)floor(brg + 0.5), 0x00B0);
4024 if (g_bShowMag) {
4025 double latAverage =
4026 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4027 double lonAverage =
4028 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4029 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4030
4031 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4032 (int)floor(varBrg + 0.5), 0x00B0);
4033 }
4034
4035 s << FormatDistanceAdaptive(dist);
4036
4037 // Compute and display cumulative distance from route start point to
4038 // current leg end point and RNG,TTG,ETA from ship to current leg end
4039 // point for active route
4040 double shiptoEndLeg = 0.;
4041 bool validActive = false;
4042 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4043 validActive = true;
4044
4045 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4046 auto node = pr->pRoutePointList->begin();
4047 RoutePoint *prp;
4048 float dist_to_endleg = 0;
4049 wxString t;
4050
4051 for (++node; node != pr->pRoutePointList->end(); ++node) {
4052 prp = *node;
4053 if (validActive)
4054 shiptoEndLeg += prp->m_seg_len;
4055 else if (prp->m_bIsActive)
4056 validActive = true;
4057 dist_to_endleg += prp->m_seg_len;
4058 if (prp->IsSame(segShow_point_a)) break;
4059 }
4060 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4061 }
4062 // write from ship to end selected leg point data if the route is
4063 // active
4064 if (validActive) {
4065 s << "\n"
4066 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4067 shiptoEndLeg +=
4069 ->GetCurrentRngToActivePoint(); // add distance from ship
4070 // to active point
4071 shiptoEndLeg +=
4072 segShow_point_b
4073 ->m_seg_len; // add the lenght of the selected leg
4074 s << FormatDistanceAdaptive(shiptoEndLeg);
4075 // ensure sog/cog are valid and vmg is positive to keep data
4076 // coherent
4077 double vmg = 0.;
4078 if (!std::isnan(gCog) && !std::isnan(gSog))
4079 vmg = gSog *
4080 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4081 PI / 180.);
4082 if (vmg > 0.) {
4083 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4084 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4085 s << " - "
4086 << wxString(ttg_sec > SECONDS_PER_DAY
4087 ? ttg_span.Format(_("%Dd %H:%M"))
4088 : ttg_span.Format(_("%H:%M")));
4089 wxDateTime dtnow, eta;
4090 eta = dtnow.SetToCurrent().Add(ttg_span);
4091 s << " - " << eta.Format("%b").Mid(0, 4)
4092 << eta.Format(" %d %H:%M");
4093 } else
4094 s << " ---- ----";
4095 }
4096 m_pRouteRolloverWin->SetString(s);
4097
4098 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4099 LEG_ROLLOVER, win_size);
4100 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4101 m_pRouteRolloverWin->IsActive(true);
4102 b_need_refresh = true;
4103 showRouteRollover = true;
4104 break;
4105 }
4106 } else {
4107 ++node;
4108 }
4109 }
4110 } else {
4111 // Is the cursor still in select radius, and not timed out?
4112 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4113 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4114 m_pRolloverRouteSeg))
4115 showRouteRollover = false;
4116 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4117 showRouteRollover = false;
4118 else
4119 showRouteRollover = true;
4120 }
4121
4122 // If currently creating a route, do not show this rollover window
4123 if (m_routeState) showRouteRollover = false;
4124
4125 // Similar for AIS target rollover window
4126 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4127 showRouteRollover = false;
4128
4129 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4130 !showRouteRollover) {
4131 m_pRouteRolloverWin->IsActive(false);
4132 m_pRolloverRouteSeg = NULL;
4133 m_pRouteRolloverWin->Destroy();
4134 m_pRouteRolloverWin = NULL;
4135 b_need_refresh = true;
4136 } else if (m_pRouteRolloverWin && showRouteRollover) {
4137 m_pRouteRolloverWin->IsActive(true);
4138 b_need_refresh = true;
4139 }
4140
4141 // Now the Track info rollover
4142 // Show the track segment info
4143 bool showTrackRollover = false;
4144
4145 if (NULL == m_pRolloverTrackSeg) {
4146 // Get a list of all selectable sgements, and search for the first
4147 // visible segment as the rollover target.
4148
4149 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4150 SelectableItemList SelList = pSelect->FindSelectionList(
4151 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4152
4153 auto node = SelList.begin();
4154 while (node != SelList.end()) {
4155 SelectItem *pFindSel = *node;
4156
4157 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4158
4159 if (pt && pt->IsVisible()) {
4160 m_pRolloverTrackSeg = pFindSel;
4161 showTrackRollover = true;
4162
4163 if (NULL == m_pTrackRolloverWin) {
4164 m_pTrackRolloverWin = new RolloverWin(this, 10);
4165 m_pTrackRolloverWin->IsActive(false);
4166 }
4167
4168 if (!m_pTrackRolloverWin->IsActive()) {
4169 wxString s;
4170 TrackPoint *segShow_point_a =
4171 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4172 TrackPoint *segShow_point_b =
4173 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4174
4175 double brg, dist;
4176 DistanceBearingMercator(
4177 segShow_point_b->m_lat, segShow_point_b->m_lon,
4178 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4179
4180 if (!pt->m_bIsInLayer)
4181 s.Append(_("Track") + ": ");
4182 else
4183 s.Append(_("Layer Track: "));
4184
4185 if (pt->GetName().IsEmpty())
4186 s.Append(_("(unnamed)"));
4187 else
4188 s.Append(pt->GetName());
4189 double tlenght = pt->Length();
4190 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4191 if (pt->GetLastPoint()->GetTimeString() &&
4192 pt->GetPoint(0)->GetTimeString()) {
4193 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4194 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4195 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4196 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4197 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4198 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4199 << getUsrSpeedUnit();
4200 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4201 : ttime.Format(" %H:%M"));
4202 }
4203 }
4204
4205 if (g_bShowTrackPointTime &&
4206 strlen(segShow_point_b->GetTimeString())) {
4207 wxString stamp = segShow_point_b->GetTimeString();
4208 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4209 if (timestamp.IsValid()) {
4210 // Format track rollover timestamp to OCPN global TZ setting
4213 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4214 }
4215 s << "\n" << _("Segment Created: ") << stamp;
4216 }
4217
4218 s << "\n";
4219 if (g_bShowTrue)
4220 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4221 0x00B0);
4222
4223 if (g_bShowMag) {
4224 double latAverage =
4225 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4226 double lonAverage =
4227 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4228 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4229
4230 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4231 0x00B0);
4232 }
4233
4234 s << FormatDistanceAdaptive(dist);
4235
4236 if (segShow_point_a->GetTimeString() &&
4237 segShow_point_b->GetTimeString()) {
4238 wxDateTime apoint = segShow_point_a->GetCreateTime();
4239 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4240 if (apoint.IsValid() && bpoint.IsValid()) {
4241 double segmentSpeed = toUsrSpeed(
4242 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4243 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4244 << getUsrSpeedUnit();
4245 }
4246 }
4247
4248 m_pTrackRolloverWin->SetString(s);
4249
4250 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4251 LEG_ROLLOVER, win_size);
4252 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4253 m_pTrackRolloverWin->IsActive(true);
4254 b_need_refresh = true;
4255 showTrackRollover = true;
4256 break;
4257 }
4258 } else {
4259 ++node;
4260 }
4261 }
4262 } else {
4263 // Is the cursor still in select radius, and not timed out?
4264 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4265 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4266 m_pRolloverTrackSeg))
4267 showTrackRollover = false;
4268 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4269 showTrackRollover = false;
4270 else
4271 showTrackRollover = true;
4272 }
4273
4274 // Similar for AIS target rollover window
4275 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4276 showTrackRollover = false;
4277
4278 // Similar for route rollover window
4279 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4280 showTrackRollover = false;
4281
4282 // TODO We onlt show tracks on primary canvas....
4283 // if(!IsPrimaryCanvas())
4284 // showTrackRollover = false;
4285
4286 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4287 !showTrackRollover) {
4288 m_pTrackRolloverWin->IsActive(false);
4289 m_pRolloverTrackSeg = NULL;
4290 m_pTrackRolloverWin->Destroy();
4291 m_pTrackRolloverWin = NULL;
4292 b_need_refresh = true;
4293 } else if (m_pTrackRolloverWin && showTrackRollover) {
4294 m_pTrackRolloverWin->IsActive(true);
4295 b_need_refresh = true;
4296 }
4297
4298 if (b_need_refresh) Refresh();
4299}
4300
4301void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4302 if ((GetShowENCLights() || m_bsectors_shown) &&
4303 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4304 extendedSectorLegs)) {
4305 if (!m_bsectors_shown) {
4306 ReloadVP(false);
4307 m_bsectors_shown = true;
4308 }
4309 } else {
4310 if (m_bsectors_shown) {
4311 ReloadVP(false);
4312 m_bsectors_shown = false;
4313 }
4314 }
4315
4316// This is here because GTK status window update is expensive..
4317// cairo using pango rebuilds the font every time so is very
4318// inefficient
4319// Anyway, only update the status bar when this timer expires
4320#if defined(__WXGTK__) || defined(__WXQT__)
4321 {
4322 // Check the absolute range of the cursor position
4323 // There could be a window wherein the chart geoereferencing is not
4324 // valid....
4325 double cursor_lat, cursor_lon;
4326 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4327
4328 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4329 while (cursor_lon < -180.) cursor_lon += 360.;
4330
4331 while (cursor_lon > 180.) cursor_lon -= 360.;
4332
4333 SetCursorStatus(cursor_lat, cursor_lon);
4334 }
4335 }
4336#endif
4337}
4338
4339void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4340 if (!parent_frame->m_pStatusBar) return;
4341
4342 wxString s1;
4343 s1 += " ";
4344 s1 += toSDMM(1, cursor_lat);
4345 s1 += " ";
4346 s1 += toSDMM(2, cursor_lon);
4347
4348 if (STAT_FIELD_CURSOR_LL >= 0)
4349 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4350
4351 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4352
4353 double brg, dist;
4354 wxString sm;
4355 wxString st;
4356 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4357 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4358 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4359
4360 wxString s = st + sm;
4361 s << FormatDistanceAdaptive(dist);
4362
4363 // CUSTOMIZATION - LIVE ETA OPTION
4364 // -------------------------------------------------------
4365 // Calculate an "live" ETA based on route starting from the current
4366 // position of the boat and goes to the cursor of the mouse.
4367 // In any case, an standard ETA will be calculated with a default speed
4368 // of the boat to give an estimation of the route (in particular if GPS
4369 // is off).
4370
4371 // Display only if option "live ETA" is selected in Settings > Display >
4372 // General.
4373 if (g_bShowLiveETA) {
4374 float realTimeETA;
4375 float boatSpeed;
4376 float boatSpeedDefault = g_defaultBoatSpeed;
4377
4378 // Calculate Estimate Time to Arrival (ETA) in minutes
4379 // Check before is value not closed to zero (it will make an very big
4380 // number...)
4381 if (!std::isnan(gSog)) {
4382 boatSpeed = gSog;
4383 if (boatSpeed < 0.5) {
4384 realTimeETA = 0;
4385 } else {
4386 realTimeETA = dist / boatSpeed * 60;
4387 }
4388 } else {
4389 realTimeETA = 0;
4390 }
4391
4392 // Add space after distance display
4393 s << " ";
4394 // Display ETA
4395 s << minutesToHoursDays(realTimeETA);
4396
4397 // In any case, display also an ETA with default speed at 6knts
4398
4399 s << " [@";
4400 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4401 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4402 s << " ";
4403 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4404 s << "]";
4405 }
4406 // END OF - LIVE ETA OPTION
4407
4408 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4409}
4410
4411// CUSTOMIZATION - FORMAT MINUTES
4412// -------------------------------------------------------
4413// New function to format minutes into a more readable format:
4414// * Hours + minutes, or
4415// * Days + hours.
4416wxString minutesToHoursDays(float timeInMinutes) {
4417 wxString s;
4418
4419 if (timeInMinutes == 0) {
4420 s << "--min";
4421 }
4422
4423 // Less than 60min, keep time in minutes
4424 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4425 s << wxString::Format("%d", (int)timeInMinutes);
4426 s << "min";
4427 }
4428
4429 // Between 1h and less than 24h, display time in hours, minutes
4430 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4431 int hours;
4432 int min;
4433 hours = (int)timeInMinutes / 60;
4434 min = (int)timeInMinutes % 60;
4435
4436 if (min == 0) {
4437 s << wxString::Format("%d", hours);
4438 s << "h";
4439 } else {
4440 s << wxString::Format("%d", hours);
4441 s << "h";
4442 s << wxString::Format("%d", min);
4443 s << "min";
4444 }
4445
4446 }
4447
4448 // More than 24h, display time in days, hours
4449 else if (timeInMinutes > 24 * 60) {
4450 int days;
4451 int hours;
4452 days = (int)(timeInMinutes / 60) / 24;
4453 hours = (int)(timeInMinutes / 60) % 24;
4454
4455 if (hours == 0) {
4456 s << wxString::Format("%d", days);
4457 s << "d";
4458 } else {
4459 s << wxString::Format("%d", days);
4460 s << "d";
4461 s << wxString::Format("%d", hours);
4462 s << "h";
4463 }
4464 }
4465
4466 return s;
4467}
4468
4469// END OF CUSTOMIZATION - FORMAT MINUTES
4470// Thanks open source code ;-)
4471// -------------------------------------------------------
4472
4473void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4474 double clat, clon;
4475 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4476 *lat = clat;
4477 *lon = clon;
4478}
4479
4480void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4481 wxPoint2DDouble *r) {
4482 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4483}
4484
4486 double rlon, wxPoint2DDouble *r) {
4487 // If the Current Chart is a raster chart, and the
4488 // requested lat/long is within the boundaries of the chart,
4489 // and the VP is not rotated,
4490 // then use the embedded BSB chart georeferencing algorithm
4491 // for greater accuracy
4492 // Additionally, use chart embedded georef if the projection is TMERC
4493 // i.e. NOT MERCATOR and NOT POLYCONIC
4494
4495 // If for some reason the chart rejects the request by returning an error,
4496 // then fall back to Viewport Projection estimate from canvas parameters
4497 if (!g_bopengl && m_singleChart &&
4498 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4499 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4500 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4501 (m_singleChart->GetChartProjectionType() !=
4502 PROJECTION_TRANSVERSE_MERCATOR) &&
4503 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4504 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4505 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4506 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4507 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4508 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4509 // Cur_BSB_Ch->GetCOVRTablenPoints
4510 // ( 0 ), rlon,
4511 // rlat );
4512 // bInside = true;
4513 // if ( bInside )
4514 if (Cur_BSB_Ch) {
4515 // This is a Raster chart....
4516 // If the VP is changing, the raster chart parameters may not yet be
4517 // setup So do that before accessing the chart's embedded
4518 // georeferencing
4519 Cur_BSB_Ch->SetVPRasterParms(vp);
4520 double rpixxd, rpixyd;
4521 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4522 r->m_x = rpixxd;
4523 r->m_y = rpixyd;
4524 return;
4525 }
4526 }
4527 }
4528
4529 // if needed, use the VPoint scaling estimator,
4530 *r = vp.GetDoublePixFromLL(rlat, rlon);
4531}
4532
4533// This routine might be deleted and all of the rendering improved
4534// to have floating point accuracy
4535bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4536 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4537}
4538
4539bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4540 wxPoint *r) {
4541 wxPoint2DDouble p;
4542 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4543
4544 // some projections give nan values when invisible values (other side of
4545 // world) are requested we should stop using integer coordinates or return
4546 // false here (and test it everywhere)
4547 if (std::isnan(p.m_x)) {
4548 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4549 return false;
4550 }
4551
4552 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4553 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4554 else
4555 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4556
4557 return true;
4558}
4559
4560void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4561 double &lon) {
4562 // If the Current Chart is a raster chart, and the
4563 // requested x,y is within the boundaries of the chart,
4564 // and the VP is not rotated,
4565 // then use the embedded BSB chart georeferencing algorithm
4566 // for greater accuracy
4567 // Additionally, use chart embedded georef if the projection is TMERC
4568 // i.e. NOT MERCATOR and NOT POLYCONIC
4569
4570 // If for some reason the chart rejects the request by returning an error,
4571 // then fall back to Viewport Projection estimate from canvas parameters
4572 bool bUseVP = true;
4573
4574 if (!g_bopengl && m_singleChart &&
4575 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4576 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4577 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4578 (m_singleChart->GetChartProjectionType() !=
4579 PROJECTION_TRANSVERSE_MERCATOR) &&
4580 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4581 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4582 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4583 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4584
4585 // TODO maybe need iterative process to validate bInside
4586 // first pass is mercator, then check chart boundaries
4587
4588 if (Cur_BSB_Ch) {
4589 // This is a Raster chart....
4590 // If the VP is changing, the raster chart parameters may not yet be
4591 // setup So do that before accessing the chart's embedded
4592 // georeferencing
4593 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4594
4595 double slat, slon;
4596 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4597 lat = slat;
4598
4599 if (slon < -180.)
4600 slon += 360.;
4601 else if (slon > 180.)
4602 slon -= 360.;
4603
4604 lon = slon;
4605 bUseVP = false;
4606 }
4607 }
4608 }
4609
4610 // if needed, use the VPoint scaling estimator
4611 if (bUseVP) {
4612 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4613 }
4614}
4615
4617 StopMovement();
4618 DoZoomCanvas(factor, false);
4619 extendedSectorLegs.clear();
4620}
4621
4622void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4623 bool stoptimer) {
4624 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4625
4626 if (g_bsmoothpanzoom) {
4627 if (StartTimedMovement(stoptimer)) {
4628 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4629 m_zoom_factor = factor;
4630 }
4631
4632 m_zoom_target = VPoint.chart_scale / factor;
4633 } else {
4634 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4635
4636 DoZoomCanvas(factor, can_zoom_to_cursor);
4637 }
4638
4639 extendedSectorLegs.clear();
4640}
4641
4642void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4643 // possible on startup
4644 if (!ChartData) return;
4645 if (!m_pCurrentStack) return;
4646
4647 /* TODO: queue the quilted loading code to a background thread
4648 so yield is never called from here, and also rendering is not delayed */
4649
4650 // Cannot allow Yield() re-entrancy here
4651 if (m_bzooming) return;
4652 m_bzooming = true;
4653
4654 double old_ppm = GetVP().view_scale_ppm;
4655
4656 // Capture current cursor position for zoom to cursor
4657 double zlat = m_cursor_lat;
4658 double zlon = m_cursor_lon;
4659
4660 double proposed_scale_onscreen =
4661 GetVP().chart_scale /
4662 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4663 bool b_do_zoom = false;
4664
4665 if (factor > 1) {
4666 b_do_zoom = true;
4667
4668 // double zoom_factor = factor;
4669
4670 ChartBase *pc = NULL;
4671
4672 if (!VPoint.b_quilt) {
4673 pc = m_singleChart;
4674 } else {
4675 if (!m_disable_adjust_on_zoom) {
4676 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4677 if (new_db_index >= 0)
4678 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4679 else { // for whatever reason, no reference chart is known
4680 // Choose the smallest scale chart on the current stack
4681 // and then adjust for scale range
4682 int current_ref_stack_index = -1;
4683 if (m_pCurrentStack->nEntry) {
4684 int trial_index =
4685 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4686 m_pQuilt->SetReferenceChart(trial_index);
4687 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4688 if (new_db_index >= 0)
4689 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4690 }
4691 }
4692
4693 if (m_pCurrentStack)
4694 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4695 new_db_index); // highlite the correct bar entry
4696 }
4697 }
4698
4699 if (pc) {
4700 // double target_scale_ppm = GetVPScale() * zoom_factor;
4701 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4702 // target_scale_ppm;
4703
4704 // Query the chart to determine the appropriate zoom range
4705 double min_allowed_scale =
4706 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4707
4708 if (proposed_scale_onscreen < min_allowed_scale) {
4709 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4710 m_zoom_factor = 1; /* stop zooming */
4711 b_do_zoom = false;
4712 } else
4713 proposed_scale_onscreen = min_allowed_scale;
4714 }
4715
4716 } else {
4717 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4718 }
4719
4720 } else if (factor < 1) {
4721 b_do_zoom = true;
4722
4723 ChartBase *pc = NULL;
4724
4725 bool b_smallest = false;
4726
4727 if (!VPoint.b_quilt) { // not quilted
4728 pc = m_singleChart;
4729
4730 if (pc) {
4731 // If m_singleChart is not on the screen, unbound the zoomout
4732 LLBBox viewbox = VPoint.GetBBox();
4733 // BoundingBox chart_box;
4734 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4735 double max_allowed_scale;
4736
4737 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4738
4739 // We can allow essentially unbounded zoomout in single chart mode
4740 // if( ChartData->GetDBBoundingBox( current_index,
4741 // &chart_box ) &&
4742 // !viewbox.IntersectOut( chart_box ) )
4743 // // Clamp the minimum scale zoom-out to the value
4744 // specified by the chart max_allowed_scale =
4745 // wxMin(max_allowed_scale, 4.0 *
4746 // pc->GetNormalScaleMax(
4747 // GetCanvasScaleFactor(),
4748 // GetCanvasWidth() ) );
4749 if (proposed_scale_onscreen > max_allowed_scale) {
4750 m_zoom_factor = 1; /* stop zooming */
4751 proposed_scale_onscreen = max_allowed_scale;
4752 }
4753 }
4754
4755 } else {
4756 if (!m_disable_adjust_on_zoom) {
4757 int new_db_index =
4758 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4759 if (new_db_index >= 0)
4760 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4761
4762 if (m_pCurrentStack)
4763 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4764 new_db_index); // highlite the correct bar entry
4765
4766 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4767
4768 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4769 proposed_scale_onscreen =
4770 wxMin(proposed_scale_onscreen,
4771 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4772 }
4773
4774 // set a minimum scale
4775 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4776 m_absolute_min_scale_ppm)
4777 proposed_scale_onscreen =
4778 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4779 }
4780 }
4781 double new_scale =
4782 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4783
4784 if (b_do_zoom) {
4785 // Disable ZTC if lookahead is ON, and currently b_follow is active
4786 bool b_allow_ztc = true;
4787 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4788 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4789 if (m_bLookAhead) {
4790 double brg, distance;
4791 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4792 &distance);
4793 dir_to_shift = brg;
4794 meters_to_shift = distance * 1852;
4795 }
4796 // Arrange to combine the zoom and pan into one operation for smoother
4797 // appearance
4798 SetVPScale(new_scale, false); // adjust, but deferred refresh
4799 wxPoint r;
4800 GetCanvasPointPix(zlat, zlon, &r);
4801 // this will emit the Refresh()
4802 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4803 } else {
4804 SetVPScale(new_scale);
4805 if (m_bFollow) DoCanvasUpdate();
4806 }
4807 }
4808
4809 m_bzooming = false;
4810}
4811
4812void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4813 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4814 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4815}
4816
4817int rot;
4818void ChartCanvas::RotateCanvas(double dir) {
4819 // SetUpMode(NORTH_UP_MODE);
4820
4821 if (g_bsmoothpanzoom) {
4822 if (StartTimedMovement()) {
4823 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4824 m_rotation_speed = dir * 60;
4825 }
4826 } else {
4827 double speed = dir * 10;
4828 if (m_modkeys == wxMOD_ALT) speed /= 20;
4829 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4830 }
4831}
4832
4833void ChartCanvas::DoRotateCanvas(double rotation) {
4834 while (rotation < 0) rotation += 2 * PI;
4835 while (rotation > 2 * PI) rotation -= 2 * PI;
4836
4837 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4838
4839 SetVPRotation(rotation);
4840 parent_frame->UpdateRotationState(VPoint.rotation);
4841}
4842
4843void ChartCanvas::DoTiltCanvas(double tilt) {
4844 while (tilt < 0) tilt = 0;
4845 while (tilt > .95) tilt = .95;
4846
4847 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4848
4849 VPoint.tilt = tilt;
4850 Refresh(false);
4851}
4852
4853void ChartCanvas::TogglebFollow() {
4854 if (!m_bFollow)
4855 SetbFollow();
4856 else
4857 ClearbFollow();
4858}
4859
4860void ChartCanvas::ClearbFollow() {
4861 m_bFollow = false; // update the follow flag
4862
4863 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4864
4865 UpdateFollowButtonState();
4866
4867 DoCanvasUpdate();
4868 ReloadVP();
4869 parent_frame->SetChartUpdatePeriod();
4870}
4871
4872void ChartCanvas::SetbFollow() {
4873 // Is the OWNSHIP on-screen?
4874 // If not, then reset the OWNSHIP offset to 0 (center screen)
4875 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4876 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4877 m_OSoffsetx = 0;
4878 m_OSoffsety = 0;
4879 }
4880
4881 // Apply the present b_follow offset values to ship position
4882 wxPoint2DDouble p;
4884 p.m_x += m_OSoffsetx;
4885 p.m_y -= m_OSoffsety;
4886
4887 // compute the target center screen lat/lon
4888 double dlat, dlon;
4889 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4890
4891 JumpToPosition(dlat, dlon, GetVPScale());
4892 m_bFollow = true;
4893
4894 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4895 UpdateFollowButtonState();
4896
4897 if (!g_bSmoothRecenter) {
4898 DoCanvasUpdate();
4899 ReloadVP();
4900 }
4901 parent_frame->SetChartUpdatePeriod();
4902}
4903
4904void ChartCanvas::UpdateFollowButtonState() {
4905 if (m_muiBar) {
4906 if (!m_bFollow)
4907 m_muiBar->SetFollowButtonState(0);
4908 else {
4909 if (m_bLookAhead)
4910 m_muiBar->SetFollowButtonState(2);
4911 else
4912 m_muiBar->SetFollowButtonState(1);
4913 }
4914 }
4915
4916#ifdef __ANDROID__
4917 if (!m_bFollow)
4918 androidSetFollowTool(0);
4919 else {
4920 if (m_bLookAhead)
4921 androidSetFollowTool(2);
4922 else
4923 androidSetFollowTool(1);
4924 }
4925#endif
4926
4927 // Look for plugin using API-121 or later
4928 // If found, make the follow state callback.
4929 if (g_pi_manager) {
4930 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4931 if (pic->m_enabled && pic->m_init_state) {
4932 switch (pic->m_api_version) {
4933 case 121: {
4934 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4935 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4936 break;
4937 }
4938 default:
4939 break;
4940 }
4941 }
4942 }
4943 }
4944}
4945
4946void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4947 if (g_bSmoothRecenter && !m_routeState) {
4948 if (StartSmoothJump(lat, lon, scale_ppm))
4949 return;
4950 else {
4951 // move closer to the target destination, and try again
4952 double gcDist, gcBearingEnd;
4953 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4954 &gcBearingEnd);
4955 gcBearingEnd += 180;
4956 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4957 GetCanvasWidth() / GetVPScale(); // meters
4958 double lon_offset =
4959 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4960 double new_lat = lat + (lat_offset / (1852 * 60));
4961 double new_lon = lon + (lon_offset / (1852 * 60));
4962 SetViewPoint(new_lat, new_lon);
4963 ReloadVP();
4964 StartSmoothJump(lat, lon, scale_ppm);
4965 return;
4966 }
4967 }
4968
4969 if (lon > 180.0) lon -= 360.0;
4970 m_vLat = lat;
4971 m_vLon = lon;
4972 StopMovement();
4973 m_bFollow = false;
4974
4975 if (!GetQuiltMode()) {
4976 double skew = 0;
4977 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4978 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4979 } else {
4980 if (scale_ppm != GetVPScale()) {
4981 // XXX should be done in SetViewPoint
4982 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4983 AdjustQuiltRefChart();
4984 }
4985 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4986 }
4987
4988 ReloadVP();
4989
4990 UpdateFollowButtonState();
4991
4992 // TODO
4993 // if( g_pi_manager ) {
4994 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4995 // }
4996}
4997
4998bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4999 // Check distance to jump, in pixels at current chart scale
5000 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5001 // width.
5002 double gcDist;
5003 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5004 double distance_pixels = gcDist * GetVPScale();
5005 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5006 // Jump is too far, try again
5007 return false;
5008 }
5009
5010 // Save where we're coming from
5011 m_startLat = m_vLat;
5012 m_startLon = m_vLon;
5013 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5014
5015 // Save where we want to end up
5016 m_endLat = lat;
5017 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5018 m_endScale = scale_ppm;
5019
5020 // Setup timing
5021 m_animationDuration = 600; // ms
5022 m_animationStart = wxGetLocalTimeMillis();
5023
5024 // Stop any previous movement, ensure no conflicts
5025 StopMovement();
5026 m_bFollow = false;
5027
5028 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5029 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5030 m_animationActive = true;
5031
5032 return true;
5033}
5034
5035void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5036 // Calculate time fraction from 0..1
5037 wxLongLong now = wxGetLocalTimeMillis();
5038 double elapsed = (now - m_animationStart).ToDouble();
5039 double t = elapsed / m_animationDuration.ToDouble();
5040 if (t > 1.0) t = 1.0;
5041
5042 // Ease function for smoother movement
5043 double e = easeOutCubic(t);
5044
5045 // Interpolate lat/lon/scale
5046 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5047 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5048 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5049
5050 // Update viewpoint
5051 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5052 // portion)
5053 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5054 ReloadVP();
5055
5056 // If we reached the end, stop the timer and finalize
5057 if (t >= 1.0) {
5058 m_easeTimer.Stop();
5059 m_animationActive = false;
5060 UpdateFollowButtonState();
5061 ZoomCanvasSimple(1.0001);
5062 DoCanvasUpdate();
5063 ReloadVP();
5064 }
5065}
5066
5067bool ChartCanvas::PanCanvas(double dx, double dy) {
5068 if (!ChartData) return false;
5069 extendedSectorLegs.clear();
5070
5071 double dlat, dlon;
5072 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5073
5074 int iters = 0;
5075 for (;;) {
5076 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5077
5078 if (iters++ > 5) return false;
5079 if (!std::isnan(dlat)) break;
5080
5081 dx *= .5, dy *= .5;
5082 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5083 }
5084
5085 // avoid overshooting the poles
5086 if (dlat > 90)
5087 dlat = 90;
5088 else if (dlat < -90)
5089 dlat = -90;
5090
5091 if (dlon > 360.) dlon -= 360.;
5092 if (dlon < -360.) dlon += 360.;
5093
5094 // This should not really be necessary, but round-trip georef on some
5095 // charts is not perfect, So we can get creep on repeated unidimensional
5096 // pans, and corrupt chart cacheing.......
5097
5098 // But this only works on north-up projections
5099 // TODO: can we remove this now?
5100 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5101 // .001 ) ) {
5102 //
5103 // if( dx == 0 ) dlon = clon;
5104 // if( dy == 0 ) dlat = clat;
5105 // }
5106
5107 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5108
5109 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5110
5111 if (VPoint.b_quilt) {
5112 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5113 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5114 // Tweak the scale slightly for a new ref chart
5115 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5116 if (pc) {
5117 double tweak_scale_ppm =
5118 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5119 SetVPScale(tweak_scale_ppm);
5120 }
5121 }
5122
5123 if (new_ref_dbIndex == -1) {
5124#pragma GCC diagnostic push
5125#pragma GCC diagnostic ignored "-Warray-bounds"
5126 // The compiler sees a -1 index being used. Does not happen, though.
5127
5128 // for whatever reason, no reference chart is known
5129 // Probably panned out of the coverage region
5130 // If any charts are anywhere on-screen, choose the smallest
5131 // scale chart on the screen to be a new reference chart.
5132 int trial_index = -1;
5133 if (m_pCurrentStack->nEntry) {
5134 int trial_index =
5135 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5136 }
5137
5138 if (trial_index < 0) {
5139 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5140 if (full_screen_array.size())
5141 trial_index = full_screen_array[full_screen_array.size() - 1];
5142 }
5143
5144 if (trial_index >= 0) {
5145 m_pQuilt->SetReferenceChart(trial_index);
5146 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5147 VPoint.rotation);
5148 ReloadVP();
5149 }
5150#pragma GCC diagnostic pop
5151 }
5152 }
5153
5154 // Turn off bFollow only if the ownship has left the screen
5155 if (m_bFollow) {
5156 double offx, offy;
5157 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5158
5159 double offset_angle = atan2(offy, offx);
5160 double offset_distance = sqrt((offy * offy) + (offx * offx));
5161 double chart_angle = GetVPRotation();
5162 double target_angle = chart_angle - offset_angle;
5163 double d_east_mod = offset_distance * cos(target_angle);
5164 double d_north_mod = offset_distance * sin(target_angle);
5165
5166 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5167 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5168
5169 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5170 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5171 m_bFollow = false; // update the follow flag
5172 UpdateFollowButtonState();
5173 }
5174 }
5175
5176 Refresh(false);
5177
5178 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5179
5180 return true;
5181}
5182
5183bool ChartCanvas::IsOwnshipOnScreen() {
5184 wxPoint r;
5186 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5187 ((r.y > 0) && r.y < GetCanvasHeight()))
5188 return true;
5189 else
5190 return false;
5191}
5192
5193void ChartCanvas::ReloadVP(bool b_adjust) {
5194 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5195
5196 LoadVP(VPoint, b_adjust);
5197}
5198
5199void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5200#ifdef ocpnUSE_GL
5201 if (g_bopengl && m_glcc) {
5202 m_glcc->Invalidate();
5203 if (m_glcc->GetSize() != GetSize()) {
5204 m_glcc->SetSize(GetSize());
5205 }
5206 } else
5207#endif
5208 {
5209 m_cache_vp.Invalidate();
5210 m_bm_cache_vp.Invalidate();
5211 }
5212
5213 VPoint.Invalidate();
5214
5215 if (m_pQuilt) m_pQuilt->Invalidate();
5216
5217 // Make sure that the Selected Group is sensible...
5218 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5219 // m_groupIndex = 0;
5220 // if( !CheckGroup( m_groupIndex ) )
5221 // m_groupIndex = 0;
5222
5223 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5224 vp.m_projection_type, b_adjust);
5225}
5226
5227void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5228 m_pQuilt->SetReferenceChart(dbIndex);
5229 VPoint.Invalidate();
5230 m_pQuilt->Invalidate();
5231}
5232
5233double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5234 if (m_pQuilt)
5235 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5236 else
5237 return vp.view_scale_ppm;
5238}
5239
5240// Verify and adjust the current reference chart,
5241// so that it will not lead to excessive overzoom or underzoom onscreen
5242int ChartCanvas::AdjustQuiltRefChart() {
5243 int ret = -1;
5244 if (m_pQuilt) {
5245 wxASSERT(ChartData);
5246 ChartBase *pc =
5247 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5248 if (pc) {
5249 double min_ref_scale =
5250 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5251 double max_ref_scale =
5252 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5253
5254 if (VPoint.chart_scale < min_ref_scale) {
5255 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5256 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5257 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5258 } else {
5259 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5260
5261 if (!brender_ok) {
5262 int target_stack_index = wxNOT_FOUND;
5263 int il = 0;
5264 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5265 if (index == m_pQuilt->GetRefChartdbIndex()) {
5266 target_stack_index = il;
5267 break;
5268 }
5269 il++;
5270 }
5271 if (wxNOT_FOUND == target_stack_index) // should never happen...
5272 target_stack_index = 0;
5273
5274 int ref_family = pc->GetChartFamily();
5275 int extended_array_count =
5276 m_pQuilt->GetExtendedStackIndexArray().size();
5277 while ((!brender_ok) &&
5278 ((int)target_stack_index < (extended_array_count - 1))) {
5279 target_stack_index++;
5280 int test_db_index =
5281 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5282
5283 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5284 IsChartQuiltableRef(test_db_index)) {
5285 // open the target, and check the min_scale
5286 ChartBase *ptest_chart =
5287 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5288 if (ptest_chart) {
5289 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5290 }
5291 }
5292 }
5293
5294 if (brender_ok) { // found a better reference chart
5295 int new_db_index =
5296 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5297 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5298 IsChartQuiltableRef(new_db_index)) {
5299 m_pQuilt->SetReferenceChart(new_db_index);
5300 ret = new_db_index;
5301 } else
5302 ret = m_pQuilt->GetRefChartdbIndex();
5303 } else
5304 ret = m_pQuilt->GetRefChartdbIndex();
5305
5306 } else
5307 ret = m_pQuilt->GetRefChartdbIndex();
5308 }
5309 } else
5310 ret = -1;
5311 }
5312
5313 return ret;
5314}
5315
5316void ChartCanvas::UpdateCanvasOnGroupChange() {
5317 delete m_pCurrentStack;
5318 m_pCurrentStack = new ChartStack;
5319 wxASSERT(ChartData);
5320 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5321 m_groupIndex);
5322
5323 if (m_pQuilt) {
5324 m_pQuilt->Compose(VPoint);
5325 SetFocus();
5326 }
5327}
5328
5329bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5330 double latNE, double lonNE) {
5331 // Center Point
5332 double latc = (latSW + latNE) / 2.0;
5333 double lonc = (lonSW + lonNE) / 2.0;
5334
5335 // Get scale in ppm (latitude)
5336 double ne_easting, ne_northing;
5337 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5338
5339 double sw_easting, sw_northing;
5340 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5341
5342 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5343
5344 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5345}
5346
5347bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5348 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5349 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5350}
5351
5352bool ChartCanvas::SetVPProjection(int projection) {
5353 if (!g_bopengl) // alternative projections require opengl
5354 return false;
5355
5356 // the view scale varies depending on geographic location and projection
5357 // rescale to keep the relative scale on the screen the same
5358 double prev_true_scale_ppm = m_true_scale_ppm;
5359 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5360 VPoint.skew, VPoint.rotation, projection) &&
5361 SetVPScale(wxMax(
5362 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5363 m_absolute_min_scale_ppm));
5364}
5365
5366bool ChartCanvas::SetViewPoint(double lat, double lon) {
5367 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5368 VPoint.rotation);
5369}
5370
5371bool ChartCanvas::SetVPRotation(double angle) {
5372 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5373 VPoint.skew, angle);
5374}
5375bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5376 double skew, double rotation, int projection,
5377 bool b_adjust, bool b_refresh) {
5378 bool b_ret = false;
5379 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5380 skew -= 2 * PI;
5381 // Any sensible change?
5382 if (VPoint.IsValid()) {
5383 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5384 (fabs(VPoint.skew - skew) < 1e-9) &&
5385 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5386 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5387 (VPoint.m_projection_type == projection ||
5388 projection == PROJECTION_UNKNOWN))
5389 return false;
5390 }
5391 if (VPoint.m_projection_type != projection)
5392 VPoint.InvalidateTransformCache(); // invalidate
5393
5394 // Take a local copy of the last viewport
5395 ViewPort last_vp = VPoint;
5396
5397 VPoint.skew = skew;
5398 VPoint.clat = lat;
5399 VPoint.clon = lon;
5400 VPoint.rotation = rotation;
5401 VPoint.view_scale_ppm = scale_ppm;
5402 if (projection != PROJECTION_UNKNOWN)
5403 VPoint.SetProjectionType(projection);
5404 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5405 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5406
5407 // don't allow latitude above 88 for mercator (90 is infinity)
5408 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5409 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5410 if (VPoint.clat > 89.5)
5411 VPoint.clat = 89.5;
5412 else if (VPoint.clat < -89.5)
5413 VPoint.clat = -89.5;
5414 }
5415
5416 // don't zoom out too far for transverse mercator polyconic until we resolve
5417 // issues
5418 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5419 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5420 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5421
5422 // SetVPRotation(rotation);
5423
5424 if (!g_bopengl) // tilt is not possible without opengl
5425 VPoint.tilt = 0;
5426
5427 if ((VPoint.pix_width <= 0) ||
5428 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5429 return false;
5430
5431 bool bwasValid = VPoint.IsValid();
5432 VPoint.Validate(); // Mark this ViewPoint as OK
5433
5434 // Has the Viewport scale changed? If so, invalidate the vp
5435 if (last_vp.view_scale_ppm != scale_ppm) {
5436 m_cache_vp.Invalidate();
5437 InvalidateGL();
5438 }
5439
5440 // A preliminary value, may be tweaked below
5441 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5442
5443 // recompute cursor position
5444 // and send to interested plugins if the mouse is actually in this window
5445 int mouseX = mouse_x;
5446 int mouseY = mouse_y;
5447 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5448 (mouseY < VPoint.pix_height)) {
5449 double lat, lon;
5450 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5451 m_cursor_lat = lat;
5452 m_cursor_lon = lon;
5453 SendCursorLatLonToAllPlugIns(lat, lon);
5454 }
5455
5456 if (!VPoint.b_quilt && m_singleChart) {
5457 VPoint.SetBoxes();
5458
5459 // Allow the chart to adjust the new ViewPort for performance optimization
5460 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5461 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5462
5463 // If there is a sensible change in the chart render, refresh the whole
5464 // screen
5465 if ((!m_cache_vp.IsValid()) ||
5466 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5467 Refresh(false);
5468 b_ret = true;
5469 } else {
5470 wxPoint cp_last, cp_this;
5471 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5472 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5473
5474 if (cp_last != cp_this) {
5475 Refresh(false);
5476 b_ret = true;
5477 }
5478 }
5479 // Create the stack
5480 if (m_pCurrentStack) {
5481 assert(ChartData != 0);
5482 int current_db_index;
5483 current_db_index =
5484 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5485
5486 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5487 m_groupIndex);
5488 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5489 }
5490
5491 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5492 }
5493
5494 // Handle the quilted case
5495 if (VPoint.b_quilt) {
5496 VPoint.SetBoxes();
5497
5498 if (last_vp.view_scale_ppm != scale_ppm)
5499 m_pQuilt->InvalidateAllQuiltPatchs();
5500
5501 // Create the quilt
5502 if (ChartData /*&& ChartData->IsValid()*/) {
5503 if (!m_pCurrentStack) return false;
5504
5505 int current_db_index;
5506 current_db_index =
5507 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5508
5509 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5510 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5511
5512 // Check to see if the current quilt reference chart is in the new stack
5513 int current_ref_stack_index = -1;
5514 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5515 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5516 current_ref_stack_index = i;
5517 }
5518
5519 if (g_bFullScreenQuilt) {
5520 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5521 }
5522
5523 // We might need a new Reference Chart
5524 bool b_needNewRef = false;
5525
5526 // If the new stack does not contain the current ref chart....
5527 if ((-1 == current_ref_stack_index) &&
5528 (m_pQuilt->GetRefChartdbIndex() >= 0))
5529 b_needNewRef = true;
5530
5531 // Would the current Ref Chart be excessively underzoomed?
5532 // We need to check this here to be sure, since we cannot know where the
5533 // reference chart was assigned. For instance, the reference chart may
5534 // have been selected from the config file, or from a long jump with a
5535 // chart family switch implicit. Anyway, we check to be sure....
5536 bool renderable = true;
5537 ChartBase *referenceChart =
5538 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5539 if (referenceChart) {
5540 double chartMaxScale = referenceChart->GetNormalScaleMax(
5541 GetCanvasScaleFactor(), GetCanvasWidth());
5542 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5543 }
5544 if (!renderable) b_needNewRef = true;
5545
5546 // Need new refchart?
5547 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5548 const ChartTableEntry &cte_ref =
5549 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5550 int target_scale = cte_ref.GetScale();
5551 int target_type = cte_ref.GetChartType();
5552 int candidate_stack_index;
5553
5554 // reset the ref chart in a way that does not lead to excessive
5555 // underzoom, for performance reasons Try to find a chart that is the
5556 // same type, and has a scale of just smaller than the current ref
5557 // chart
5558
5559 candidate_stack_index = 0;
5560 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5561 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5562 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5563 int candidate_scale = cte_candidate.GetScale();
5564 int candidate_type = cte_candidate.GetChartType();
5565
5566 if ((candidate_scale >= target_scale) &&
5567 (candidate_type == target_type)) {
5568 bool renderable = true;
5569 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5570 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5571 if (tentative_referenceChart) {
5572 double chartMaxScale =
5573 tentative_referenceChart->GetNormalScaleMax(
5574 GetCanvasScaleFactor(), GetCanvasWidth());
5575 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5576 }
5577
5578 if (renderable) break;
5579 }
5580
5581 candidate_stack_index++;
5582 }
5583
5584 // If that did not work, look for a chart of just larger scale and
5585 // same type
5586 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5587 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5588 while (candidate_stack_index >= 0) {
5589 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5590 if (idx >= 0) {
5591 const ChartTableEntry &cte_candidate =
5592 ChartData->GetChartTableEntry(idx);
5593 int candidate_scale = cte_candidate.GetScale();
5594 int candidate_type = cte_candidate.GetChartType();
5595
5596 if ((candidate_scale <= target_scale) &&
5597 (candidate_type == target_type))
5598 break;
5599 }
5600 candidate_stack_index--;
5601 }
5602 }
5603
5604 // and if that did not work, chose stack entry 0
5605 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5606 (candidate_stack_index < 0))
5607 candidate_stack_index = 0;
5608
5609 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5610
5611 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5612 }
5613
5614 if (!g_bopengl) {
5615 // Preset the VPoint projection type to match what the quilt projection
5616 // type will be
5617 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5618
5619 // Always keep the default Mercator projection if the reference chart is
5620 // not in the PatchList or the scale is too small for it to render.
5621
5622 bool renderable = true;
5623 ChartBase *referenceChart =
5624 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5625 if (referenceChart) {
5626 double chartMaxScale = referenceChart->GetNormalScaleMax(
5627 GetCanvasScaleFactor(), GetCanvasWidth());
5628 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5629 proj = ChartData->GetDBChartProj(ref_db_index);
5630 } else
5631 proj = PROJECTION_MERCATOR;
5632
5633 VPoint.b_MercatorProjectionOverride =
5634 (m_pQuilt->GetnCharts() == 0 || !renderable);
5635
5636 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5637
5638 VPoint.SetProjectionType(proj);
5639 }
5640
5641 // If this quilt will be a perceptible delta from the existing quilt,
5642 // then refresh the entire screen
5643 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5644 // Allow the quilt to adjust the new ViewPort for performance
5645 // optimization This will normally be only a fractional (i.e.
5646 // sub-pixel) adjustment...
5647 if (b_adjust) {
5648 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5649 }
5650
5651 // ChartData->ClearCacheInUseFlags();
5652 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5653
5654 // wxStopWatch sw;
5655
5656#ifdef __ANDROID__
5657 // This is an optimization for panning on touch screen systems.
5658 // The quilt composition is deferred until the OnPaint() message gets
5659 // finally removed and processed from the message queue.
5660 // Takes advantage of the fact that touch-screen pan gestures are
5661 // usually short in distance,
5662 // so not requiring a full quilt rebuild until the pan gesture is
5663 // complete.
5664 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5665 // qDebug() << "Force compose";
5666 m_pQuilt->Compose(VPoint);
5667 } else {
5668 m_pQuilt->Invalidate();
5669 }
5670#else
5671 m_pQuilt->Compose(VPoint);
5672#endif
5673
5674 // printf("comp time %ld\n", sw.Time());
5675
5676 // If the extended chart stack has changed, invalidate any cached
5677 // render bitmap
5678 // if(m_pQuilt->GetXStackHash() != hash1) {
5679 // m_bm_cache_vp.Invalidate();
5680 // InvalidateGL();
5681 // }
5682
5683 ChartData->PurgeCacheUnusedCharts(0.7);
5684
5685 if (b_refresh) Refresh(false);
5686
5687 b_ret = true;
5688 }
5689 }
5690
5691 VPoint.skew = 0.; // Quilting supports 0 Skew
5692 } else if (!g_bopengl) {
5693 OcpnProjType projection = PROJECTION_UNKNOWN;
5694 if (m_singleChart) // viewport projection must match chart projection
5695 // without opengl
5696 projection = m_singleChart->GetChartProjectionType();
5697 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5698 VPoint.SetProjectionType(projection);
5699 }
5700
5701 // Has the Viewport projection changed? If so, invalidate the vp
5702 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5703 m_cache_vp.Invalidate();
5704 InvalidateGL();
5705 }
5706
5707 UpdateCanvasControlBar(); // Refresh the Piano
5708
5709 VPoint.chart_scale = 1.0; // fallback default value
5710
5711 if (VPoint.GetBBox().GetValid()) {
5712 // Update the viewpoint reference scale
5713 if (m_singleChart)
5714 VPoint.ref_scale = m_singleChart->GetNativeScale();
5715 else {
5716#ifdef __ANDROID__
5717 // This is an optimization for panning on touch screen systems.
5718 // See above.
5719 // Quilt might not be fully composed at this point, so for cm93
5720 // the reference scale may not be known.
5721 // In this case, do not update the VP ref_scale.
5722 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5723 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5724 }
5725#else
5726 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5727#endif
5728 }
5729
5730 // Calculate the on-screen displayed actual scale
5731 // by a simple traverse northward from the center point
5732 // of roughly one eighth of the canvas height
5733 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5734
5735 double delta_check =
5736 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5737 delta_check /= 8.;
5738
5739 double check_point = wxMin(89., VPoint.clat);
5740
5741 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5742
5743 double rhumbDist;
5744 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5745 VPoint.clon, 0, &rhumbDist);
5746
5747 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5748 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5749 // Calculate the distance between r1 and r in physical pixels.
5750 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5751 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5752
5753 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5754
5755 // A fall back in case of very high zoom-out, giving delta_y == 0
5756 // which can probably only happen with vector charts
5757 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5758
5759 // Another fallback, for highly zoomed out charts
5760 // This adjustment makes the displayed TrueScale correspond to the
5761 // same algorithm used to calculate the chart zoom-out limit for
5762 // ChartDummy.
5763 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5764
5765 if (m_true_scale_ppm)
5766 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5767 else
5768 VPoint.chart_scale = 1.0;
5769
5770 // Create a nice renderable string
5771 double round_factor = 1000.;
5772 if (VPoint.chart_scale <= 1000.)
5773 round_factor = 10.;
5774 else if (VPoint.chart_scale <= 10000.)
5775 round_factor = 100.;
5776 else if (VPoint.chart_scale <= 100000.)
5777 round_factor = 1000.;
5778
5779 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5780 double retina_coef = 1;
5781#ifdef ocpnUSE_GL
5782#ifdef __WXOSX__
5783 if (g_bopengl) {
5784 retina_coef = GetContentScaleFactor();
5785 }
5786#endif
5787#endif
5788
5789 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5790 // rounded to the nearest 10, 100 or 1000.
5791 //
5792 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5793 // true_scale_display. That does not make sense. The chart scale should be
5794 // the same as the true scale within the limits of the rounding factor.
5795 double true_scale_display =
5796 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5797 wxString text;
5798
5799 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5800
5801 if (m_displayed_scale_factor > 10.0)
5802 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5803 m_displayed_scale_factor);
5804 else if (m_displayed_scale_factor > 1.0)
5805 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5806 m_displayed_scale_factor);
5807 else if (m_displayed_scale_factor > 0.1) {
5808 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5809 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5810 } else if (m_displayed_scale_factor > 0.01) {
5811 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5812 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5813 } else {
5814 text.Printf(
5815 "%s %4.0f (---)", _("Scale"),
5816 true_scale_display); // Generally, no chart, so no chart scale factor
5817 }
5818
5819 m_scaleValue = true_scale_display;
5820 m_scaleText = text;
5821 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5822
5823 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5824 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5825 // Check to see if the text will fit in the StatusBar field...
5826 bool b_noshow = false;
5827 {
5828 int w = 0;
5829 int h;
5830 wxClientDC dc(parent_frame->GetStatusBar());
5831 if (dc.IsOk()) {
5832 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5833 dc.SetFont(*templateFont);
5834 dc.GetTextExtent(text, &w, &h);
5835
5836 // If text is too long for the allocated field, try to reduce the text
5837 // string a bit.
5838 wxRect rect;
5839 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5840 if (w && w > rect.width) {
5841 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5842 }
5843
5844 // Test again...if too big still, then give it up.
5845 dc.GetTextExtent(text, &w, &h);
5846
5847 if (w && w > rect.width) {
5848 b_noshow = true;
5849 }
5850 }
5851 }
5852
5853 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5854 }
5855 }
5856
5857 // Maintain member vLat/vLon
5858 m_vLat = VPoint.clat;
5859 m_vLon = VPoint.clon;
5860
5861 return b_ret;
5862}
5863
5864// Static Icon definitions for some symbols requiring
5865// scaling/rotation/translation Very specific wxDC draw commands are
5866// necessary to properly render these icons...See the code in
5867// ShipDraw()
5868
5869// This icon was adapted and scaled from the S52 Presentation Library
5870// version 3_03.
5871// Symbol VECGND02
5872
5873static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5874
5875// This ownship icon was adapted and scaled from the S52 Presentation
5876// Library version 3_03 Symbol OWNSHP05
5877static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5878 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5879
5880wxColour ChartCanvas::PredColor() {
5881 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5882 // visibility.
5883 if (SHIP_NORMAL == m_ownship_state)
5884 return GetGlobalColor("URED");
5885
5886 else if (SHIP_LOWACCURACY == m_ownship_state)
5887 return GetGlobalColor("YELO1");
5888
5889 return GetGlobalColor("NODTA");
5890}
5891
5892wxColour ChartCanvas::ShipColor() {
5893 // Establish ship color
5894 // It changes color based on GPS and Chart accuracy/availability
5895
5896 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5897
5898 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5899
5900 return GetGlobalColor("URED"); // default is OK
5901}
5902
5903void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5904 wxPoint2DDouble lShipMidPoint) {
5905 dc.SetPen(wxPen(PredColor(), 2));
5906
5907 if (SHIP_NORMAL == m_ownship_state)
5908 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5909 else
5910 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5911
5912 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5913 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5914
5915 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5916 lShipMidPoint.m_y);
5917 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5918 lShipMidPoint.m_y + 12);
5919}
5920
5921void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5922 wxPoint GPSOffsetPixels,
5923 wxPoint2DDouble lGPSPoint) {
5924 // if (m_animationActive) return;
5925 // Develop a uniform length for course predictor line dash length, based on
5926 // physical display size Use this reference length to size all other graphics
5927 // elements
5928 float ref_dim = m_display_size_mm / 24;
5929 ref_dim = wxMin(ref_dim, 12);
5930 ref_dim = wxMax(ref_dim, 6);
5931
5932 wxColour cPred;
5933 cPred.Set(g_cog_predictor_color);
5934 if (cPred == wxNullColour) cPred = PredColor();
5935
5936 // Establish some graphic element line widths dependent on the platform
5937 // display resolution
5938 // double nominal_line_width_pix = wxMax(1.0,
5939 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5940 // not less than 1 pixel
5941 double nominal_line_width_pix = wxMax(
5942 1.0,
5943 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5944
5945 // If the calculated value is greater than the config file spec value, then
5946 // use it.
5947 if (nominal_line_width_pix > g_cog_predictor_width)
5948 g_cog_predictor_width = nominal_line_width_pix;
5949
5950 // Calculate ownship Position Predictor
5951 wxPoint lPredPoint, lHeadPoint;
5952
5953 float pCog = std::isnan(gCog) ? 0 : gCog;
5954 float pSog = std::isnan(gSog) ? 0 : gSog;
5955
5956 double pred_lat, pred_lon;
5957 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5958 &pred_lat, &pred_lon);
5959 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5960
5961 // test to catch the case where COG/HDG line crosses the screen
5962 LLBBox box;
5963
5964 // Should we draw the Head vector?
5965 // Compare the points lHeadPoint and lPredPoint
5966 // If they differ by more than n pixels, and the head vector is valid, then
5967 // render the head vector
5968
5969 float ndelta_pix = 10.;
5970 double hdg_pred_lat, hdg_pred_lon;
5971 bool b_render_hdt = false;
5972 if (!std::isnan(gHdt)) {
5973 // Calculate ownship Heading pointer as a predictor
5974 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5975 &hdg_pred_lon);
5976 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5977 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5978 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5979 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5980 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5981 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5982 }
5983 }
5984
5985 // draw course over ground if they are longer than the ship
5986 wxPoint lShipMidPoint;
5987 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5988 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5989 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5990 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5991
5992 if (lpp >= img_height / 2) {
5993 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5994 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5995 !std::isnan(gSog)) {
5996 // COG Predictor
5997 float dash_length = ref_dim;
5998 wxDash dash_long[2];
5999 dash_long[0] =
6000 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6001 g_cog_predictor_width); // Long dash , in mm <---------+
6002 dash_long[1] = dash_long[0] / 2.0; // Short gap
6003
6004 // On ultra-hi-res displays, do not allow the dashes to be greater than
6005 // 250, since it is defined as (char)
6006 if (dash_length > 250.) {
6007 dash_long[0] = 250. / g_cog_predictor_width;
6008 dash_long[1] = dash_long[0] / 2;
6009 }
6010
6011 wxPen ppPen2(cPred, g_cog_predictor_width,
6012 (wxPenStyle)g_cog_predictor_style);
6013 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6014 ppPen2.SetDashes(2, dash_long);
6015 dc.SetPen(ppPen2);
6016 dc.StrokeLine(
6017 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6018 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6019
6020 if (g_cog_predictor_width > 1) {
6021 float line_width = g_cog_predictor_width / 3.;
6022
6023 wxDash dash_long3[2];
6024 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6025 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6026
6027 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6028 (wxPenStyle)g_cog_predictor_style);
6029 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6030 ppPen3.SetDashes(2, dash_long3);
6031 dc.SetPen(ppPen3);
6032 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6033 lGPSPoint.m_y + GPSOffsetPixels.y,
6034 lPredPoint.x + GPSOffsetPixels.x,
6035 lPredPoint.y + GPSOffsetPixels.y);
6036 }
6037
6038 if (g_cog_predictor_endmarker) {
6039 // Prepare COG predictor endpoint icon
6040 double png_pred_icon_scale_factor = .4;
6041 if (g_ShipScaleFactorExp > 1.0)
6042 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6043 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6044
6045 wxPoint icon[4];
6046
6047 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6048 (float)(lPredPoint.x - lShipMidPoint.x));
6049 cog_rad += (float)PI;
6050
6051 for (int i = 0; i < 4; i++) {
6052 int j = i * 2;
6053 double pxa = (double)(s_png_pred_icon[j]);
6054 double pya = (double)(s_png_pred_icon[j + 1]);
6055
6056 pya *= png_pred_icon_scale_factor;
6057 pxa *= png_pred_icon_scale_factor;
6058
6059 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6060 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6061
6062 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6063 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6064 }
6065
6066 // Render COG endpoint icon
6067 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6068 wxPENSTYLE_SOLID);
6069 dc.SetPen(ppPen1);
6070 dc.SetBrush(wxBrush(cPred));
6071
6072 dc.StrokePolygon(4, icon);
6073 }
6074 }
6075 }
6076
6077 // HDT Predictor
6078 if (b_render_hdt) {
6079 float hdt_dash_length = ref_dim * 0.4;
6080
6081 cPred.Set(g_ownship_HDTpredictor_color);
6082 if (cPred == wxNullColour) cPred = PredColor();
6083 float hdt_width =
6084 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6085 : g_cog_predictor_width * 0.8);
6086 wxDash dash_short[2];
6087 dash_short[0] =
6088 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6089 hdt_width); // Short dash , in mm <---------+
6090 dash_short[1] =
6091 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6092 hdt_width); // Short gap |
6093
6094 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6095 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6096 ppPen2.SetDashes(2, dash_short);
6097
6098 dc.SetPen(ppPen2);
6099 dc.StrokeLine(
6100 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6101 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6102
6103 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6104 dc.SetPen(ppPen1);
6105 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6106
6107 if (g_ownship_HDTpredictor_endmarker) {
6108 double nominal_circle_size_pixels = wxMax(
6109 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6110
6111 // Scale the circle to ChartScaleFactor, slightly softened....
6112 if (g_ShipScaleFactorExp > 1.0)
6113 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6114
6115 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6116 lHeadPoint.y + GPSOffsetPixels.y,
6117 nominal_circle_size_pixels / 2);
6118 }
6119 }
6120
6121 // Draw radar rings if activated
6122 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6123 double factor = 1.00;
6124 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6125 factor = 1 / 1.852;
6126 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6127 if (std::isnan(gSog))
6128 factor = 0.0;
6129 else
6130 factor = gSog / 60;
6131 }
6132 factor *= g_fNavAidRadarRingsStep;
6133
6134 double tlat, tlon;
6135 wxPoint r;
6136 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6137 GetCanvasPointPix(tlat, tlon, &r);
6138
6139 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6140 pow((double)(lGPSPoint.m_y - r.y), 2));
6141 int pix_radius = (int)lpp;
6142
6143 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6144
6145 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6146
6147 dc.SetPen(ppPen1);
6148 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6149
6150 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6151 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6152 }
6153}
6154
6155void ChartCanvas::ComputeShipScaleFactor(
6156 float icon_hdt, int ownShipWidth, int ownShipLength,
6157 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6158 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6159 float screenResolution = m_pix_per_mm;
6160
6161 // Calculate the true ship length in exact pixels
6162 double ship_bow_lat, ship_bow_lon;
6163 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6164 &ship_bow_lat, &ship_bow_lon);
6165 wxPoint lShipBowPoint;
6166 wxPoint2DDouble b_point =
6167 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6168 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6169
6170 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6171 powf((float)(b_point.m_y - a_point.m_y), 2));
6172
6173 // And in mm
6174 float shipLength_mm = shipLength_px / screenResolution;
6175
6176 // Set minimum ownship drawing size
6177 float ownship_min_mm = g_n_ownship_min_mm;
6178 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6179
6180 // Calculate Nautical Miles distance from midships to gps antenna
6181 float hdt_ant = icon_hdt + 180.;
6182 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6183 float dx = g_n_gps_antenna_offset_x / 1852.;
6184 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6185 {
6186 hdt_ant = icon_hdt;
6187 dy = -dy;
6188 }
6189
6190 // If the drawn ship size is going to be clamped, adjust the gps antenna
6191 // offsets
6192 if (shipLength_mm < ownship_min_mm) {
6193 dy /= shipLength_mm / ownship_min_mm;
6194 dx /= shipLength_mm / ownship_min_mm;
6195 }
6196
6197 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6198
6199 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6200 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6201 &ship_mid_lon1);
6202
6203 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6204 &lShipMidPoint);
6205
6206 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6207 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6208
6209 float scale_factor = shipLength_px / ownShipLength;
6210
6211 // Calculate a scale factor that would produce a reasonably sized icon
6212 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6213
6214 // And choose the correct one
6215 scale_factor = wxMax(scale_factor, scale_factor_min);
6216
6217 scale_factor_y = scale_factor;
6218 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6219 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6220}
6221
6222void ChartCanvas::ShipDraw(ocpnDC &dc) {
6223 if (!GetVP().IsValid()) return;
6224
6225 wxPoint GPSOffsetPixels(0, 0);
6226 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6227
6228 // COG/SOG may be undefined in NMEA data stream
6229 float pCog = std::isnan(gCog) ? 0 : gCog;
6230 float pSog = std::isnan(gSog) ? 0 : gSog;
6231
6232 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6233
6234 lShipMidPoint = lGPSPoint;
6235
6236 // Draw the icon rotated to the COG
6237 // or to the Hdt if available
6238 float icon_hdt = pCog;
6239 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6240
6241 // COG may be undefined in NMEA data stream
6242 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6243
6244 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6245 // predictor
6246 double osd_head_lat, osd_head_lon;
6247 wxPoint osd_head_point;
6248
6249 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6250 &osd_head_lon);
6251
6252 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6253
6254 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6255 (float)(osd_head_point.x - lShipMidPoint.m_x));
6256 icon_rad += (float)PI;
6257
6258 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6259
6260 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6261 // nominal size and is just barely outside the viewport ....
6262 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6263
6264 // TODO: fix to include actual size of boat that will be rendered
6265 int img_height = 0;
6266 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6267 if (GetVP().chart_scale >
6268 300000) // According to S52, this should be 50,000
6269 {
6270 ShipDrawLargeScale(dc, lShipMidPoint);
6271 img_height = 20;
6272 } else {
6273 wxImage pos_image;
6274
6275 // Substitute user ownship image if found
6276 if (m_pos_image_user)
6277 pos_image = m_pos_image_user->Copy();
6278 else if (SHIP_NORMAL == m_ownship_state)
6279 pos_image = m_pos_image_red->Copy();
6280 if (SHIP_LOWACCURACY == m_ownship_state)
6281 pos_image = m_pos_image_yellow->Copy();
6282 else if (SHIP_NORMAL != m_ownship_state)
6283 pos_image = m_pos_image_grey->Copy();
6284
6285 // Substitute user ownship image if found
6286 if (m_pos_image_user) {
6287 pos_image = m_pos_image_user->Copy();
6288
6289 if (SHIP_LOWACCURACY == m_ownship_state)
6290 pos_image = m_pos_image_user_yellow->Copy();
6291 else if (SHIP_NORMAL != m_ownship_state)
6292 pos_image = m_pos_image_user_grey->Copy();
6293 }
6294
6295 img_height = pos_image.GetHeight();
6296
6297 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6298 g_OwnShipIconType > 0) // use large ship
6299 {
6300 int ownShipWidth = 22; // Default values from s_ownship_icon
6301 int ownShipLength = 84;
6302 if (g_OwnShipIconType == 1) {
6303 ownShipWidth = pos_image.GetWidth();
6304 ownShipLength = pos_image.GetHeight();
6305 }
6306
6307 float scale_factor_x, scale_factor_y;
6308 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6309 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6310 scale_factor_x, scale_factor_y);
6311
6312 if (g_OwnShipIconType == 1) { // Scaled bitmap
6313 pos_image.Rescale(ownShipWidth * scale_factor_x,
6314 ownShipLength * scale_factor_y,
6315 wxIMAGE_QUALITY_HIGH);
6316 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6317 wxImage rot_image =
6318 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6319
6320 // Simple sharpening algorithm.....
6321 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6322 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6323 if (rot_image.GetAlpha(ip, jp) > 64)
6324 rot_image.SetAlpha(ip, jp, 255);
6325
6326 wxBitmap os_bm(rot_image);
6327
6328 int w = os_bm.GetWidth();
6329 int h = os_bm.GetHeight();
6330 img_height = h;
6331
6332 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6333 lShipMidPoint.m_y - h / 2, true);
6334
6335 // Maintain dirty box,, missing in __WXMSW__ library
6336 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6337 lShipMidPoint.m_y - h / 2);
6338 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6339 lShipMidPoint.m_y - h / 2 + h);
6340 }
6341
6342 else if (g_OwnShipIconType == 2) { // Scaled Vector
6343 wxPoint ownship_icon[10];
6344
6345 for (int i = 0; i < 10; i++) {
6346 int j = i * 2;
6347 float pxa = (float)(s_ownship_icon[j]);
6348 float pya = (float)(s_ownship_icon[j + 1]);
6349 pya *= scale_factor_y;
6350 pxa *= scale_factor_x;
6351
6352 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6353 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6354
6355 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6356 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6357 }
6358
6359 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6360 dc.SetPen(ppPen1);
6361 dc.SetBrush(wxBrush(ShipColor()));
6362
6363 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6364
6365 // draw reference point (midships) cross
6366 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6367 ownship_icon[7].y);
6368 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6369 ownship_icon[9].y);
6370 }
6371
6372 img_height = ownShipLength * scale_factor_y;
6373
6374 // Reference point, where the GPS antenna is
6375 int circle_rad = 3;
6376 if (m_pos_image_user) circle_rad = 1;
6377
6378 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6379 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6380 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6381 } else { // Fixed bitmap icon.
6382 /* non opengl, or suboptimal opengl via ocpndc: */
6383 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6384 wxImage rot_image =
6385 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6386
6387 // Simple sharpening algorithm.....
6388 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6389 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6390 if (rot_image.GetAlpha(ip, jp) > 64)
6391 rot_image.SetAlpha(ip, jp, 255);
6392
6393 wxBitmap os_bm(rot_image);
6394
6395 if (g_ShipScaleFactorExp > 1) {
6396 wxImage scaled_image = os_bm.ConvertToImage();
6397 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6398 1.0; // soften the scale factor a bit
6399 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6400 scaled_image.GetHeight() * factor,
6401 wxIMAGE_QUALITY_HIGH));
6402 }
6403 int w = os_bm.GetWidth();
6404 int h = os_bm.GetHeight();
6405 img_height = h;
6406
6407 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6408 lShipMidPoint.m_y - h / 2, true);
6409
6410 // Reference point, where the GPS antenna is
6411 int circle_rad = 3;
6412 if (m_pos_image_user) circle_rad = 1;
6413
6414 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6415 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6416 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6417
6418 // Maintain dirty box,, missing in __WXMSW__ library
6419 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6420 lShipMidPoint.m_y - h / 2);
6421 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6422 lShipMidPoint.m_y - h / 2 + h);
6423 }
6424 } // ownship draw
6425 }
6426
6427 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6428}
6429
6430/* @ChartCanvas::CalcGridSpacing
6431 **
6432 ** Calculate the major and minor spacing between the lat/lon grid
6433 **
6434 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6435 *window
6436 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6437 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6438 ** @return [void]
6439 */
6440void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6441 float &MinorSpacing) {
6442 // table for calculating the distance between the grids
6443 // [0] view_scale ppm
6444 // [1] spacing between major grid lines in degrees
6445 // [2] spacing between minor grid lines in degrees
6446 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6447 {.000001f, 45.0f, 15.0f},
6448 {.0002f, 30.0f, 10.0f},
6449 {.0003f, 10.0f, 2.0f},
6450 {.0008f, 5.0f, 1.0f},
6451 {.001f, 2.0f, 30.0f / 60.0f},
6452 {.003f, 1.0f, 20.0f / 60.0f},
6453 {.006f, 0.5f, 10.0f / 60.0f},
6454 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6455 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6456 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6457 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6458 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6459 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6460 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6461 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6462
6463 unsigned int tabi;
6464 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6465 if (view_scale_ppm < lltab[tabi][0]) break;
6466 MajorSpacing = lltab[tabi][1]; // major latitude distance
6467 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6468 return;
6469}
6470/* @ChartCanvas::CalcGridText *************************************
6471 **
6472 ** Calculates text to display at the major grid lines
6473 **
6474 ** @param [r] latlon [float] latitude or longitude of grid line
6475 ** @param [r] spacing [float] distance between two major grid lines
6476 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6477 **
6478 ** @return
6479 */
6480
6481wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6482 int deg = (int)fabs(latlon); // degrees
6483 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6484 char postfix;
6485
6486 // calculate postfix letter (NSEW)
6487 if (latlon > 0.0) {
6488 if (bPostfix) {
6489 postfix = 'N';
6490 } else {
6491 postfix = 'E';
6492 }
6493 } else if (latlon < 0.0) {
6494 if (bPostfix) {
6495 postfix = 'S';
6496 } else {
6497 postfix = 'W';
6498 }
6499 } else {
6500 postfix = ' '; // no postfix for equator and greenwich
6501 }
6502 // calculate text, display minutes only if spacing is smaller than one degree
6503
6504 wxString ret;
6505 if (spacing >= 1.0) {
6506 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6507 } else if (spacing >= (1.0 / 60.0)) {
6508 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6509 } else {
6510 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6511 }
6512
6513 return ret;
6514}
6515
6516/* @ChartCanvas::GridDraw *****************************************
6517 **
6518 ** Draws major and minor Lat/Lon Grid on the chart
6519 ** - distance between Grid-lm ines are calculated automatic
6520 ** - major grid lines will be across the whole chart window
6521 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6522 **
6523 ** @param [w] dc [wxDC&] the wx drawing context
6524 **
6525 ** @return [void]
6526 ************************************************************************/
6527void ChartCanvas::GridDraw(ocpnDC &dc) {
6528 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6529
6530 double nlat, elon, slat, wlon;
6531 float lat, lon;
6532 float dlon;
6533 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6534 wxCoord w, h;
6535 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6536 dc.SetPen(GridPen);
6537 if (!m_pgridFont) SetupGridFont();
6538 dc.SetFont(*m_pgridFont);
6539 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6540
6541 w = m_canvas_width;
6542 h = m_canvas_height;
6543
6544 GetCanvasPixPoint(0, 0, nlat,
6545 wlon); // get lat/lon of upper left point of the window
6546 GetCanvasPixPoint(w, h, slat,
6547 elon); // get lat/lon of lower right point of the window
6548 dlon =
6549 elon -
6550 wlon; // calculate how many degrees of longitude are shown in the window
6551 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6552 {
6553 dlon = dlon + 360.0;
6554 }
6555 // calculate distance between latitude grid lines
6556 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6557
6558 // calculate position of first major latitude grid line
6559 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6560
6561 // Draw Major latitude grid lines and text
6562 while (lat < nlat) {
6563 wxPoint r;
6564 wxString st =
6565 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6566 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6567 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6568 dc.DrawText(st, 0, r.y); // draw text
6569 lat = lat + gridlatMajor;
6570
6571 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6572 }
6573
6574 // calculate position of first minor latitude grid line
6575 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6576
6577 // Draw minor latitude grid lines
6578 while (lat < nlat) {
6579 wxPoint r;
6580 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6581 dc.DrawLine(0, r.y, 10, r.y, false);
6582 dc.DrawLine(w - 10, r.y, w, r.y, false);
6583 lat = lat + gridlatMinor;
6584 }
6585
6586 // calculate distance between grid lines
6587 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6588
6589 // calculate position of first major latitude grid line
6590 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6591
6592 // draw major longitude grid lines
6593 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6594 wxPoint r;
6595 wxString st = CalcGridText(lon, gridlonMajor, false);
6596 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6597 dc.DrawLine(r.x, 0, r.x, h, false);
6598 dc.DrawText(st, r.x, 0);
6599 lon = lon + gridlonMajor;
6600 if (lon > 180.0) {
6601 lon = lon - 360.0;
6602 }
6603
6604 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6605 }
6606
6607 // calculate position of first minor longitude grid line
6608 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6609 // draw minor longitude grid lines
6610 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6611 wxPoint r;
6612 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6613 dc.DrawLine(r.x, 0, r.x, 10, false);
6614 dc.DrawLine(r.x, h - 10, r.x, h, false);
6615 lon = lon + gridlonMinor;
6616 if (lon > 180.0) {
6617 lon = lon - 360.0;
6618 }
6619 }
6620}
6621
6622void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6623 if (0 ) {
6624 double blat, blon, tlat, tlon;
6625 wxPoint r;
6626
6627 int x_origin = m_bDisplayGrid ? 60 : 20;
6628 int y_origin = m_canvas_height - 50;
6629
6630 float dist;
6631 int count;
6632 wxPen pen1, pen2;
6633
6634 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6635 {
6636 dist = 10.0;
6637 count = 5;
6638 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6639 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6640 } else // Draw 1 mile scale as SCALEB10
6641 {
6642 dist = 1.0;
6643 count = 10;
6644 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6645 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6646 }
6647
6648 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6649 double rotation = -VPoint.rotation;
6650
6651 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6652 GetCanvasPointPix(tlat, tlon, &r);
6653 int l1 = (y_origin - r.y) / count;
6654
6655 for (int i = 0; i < count; i++) {
6656 int y = l1 * i;
6657 if (i & 1)
6658 dc.SetPen(pen1);
6659 else
6660 dc.SetPen(pen2);
6661
6662 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6663 }
6664 } else {
6665 double blat, blon, tlat, tlon;
6666
6667 int x_origin = 5.0 * GetPixPerMM();
6668 int chartbar_height = GetChartbarHeight();
6669 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6670 // if (style->chartStatusWindowTransparent)
6671 // chartbar_height = 0;
6672 int y_origin = m_canvas_height - chartbar_height - 5;
6673#ifdef __WXOSX__
6674 if (!g_bopengl)
6675 y_origin =
6676 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6677#endif
6678
6679 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6680 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6681
6682 double d;
6683 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6684 d /= 2;
6685
6686 int unit = g_iDistanceFormat;
6687 if (d < .5 &&
6688 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6689 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6690
6691 // nice number
6692 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6693 float places = floor(logdist), rem = logdist - places;
6694 dist = pow(10, places);
6695
6696 if (rem < .2)
6697 dist /= 5;
6698 else if (rem < .5)
6699 dist /= 2;
6700
6701 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6702 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6703 double rotation = -VPoint.rotation;
6704
6705 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6706 &tlat, &tlon);
6707 wxPoint r;
6708 GetCanvasPointPix(tlat, tlon, &r);
6709 int l1 = r.x - x_origin;
6710
6711 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6712 12); // Store this for later reference
6713
6714 dc.SetPen(pen1);
6715
6716 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6717 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6718 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6719
6720 if (!m_pgridFont) SetupGridFont();
6721 dc.SetFont(*m_pgridFont);
6722 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6723 int w, h;
6724 dc.GetTextExtent(s, &w, &h);
6725 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6726 if (g_bopengl) {
6727 w /= dpi_factor;
6728 h /= dpi_factor;
6729 }
6730 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6731 }
6732}
6733
6734void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6735 // Constants?
6736 double da_min = 2.;
6737 double da_max = 6.;
6738 double ra_min = 0.;
6739 double ra_max = 40.;
6740
6741 wxPen pen_save = dc.GetPen();
6742
6743 wxDateTime now = wxDateTime::Now();
6744
6745 dc.SetPen(pen);
6746
6747 int x0, y0, x1, y1;
6748
6749 x0 = x1 = x + radius; // Start point
6750 y0 = y1 = y;
6751 double angle = 0.;
6752 int i = 0;
6753
6754 while (angle < 360.) {
6755 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6756 angle += da;
6757
6758 if (angle > 360.) angle = 360.;
6759
6760 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6761
6762 double r;
6763 if (i & 1)
6764 r = radius + ra;
6765 else
6766 r = radius - ra;
6767
6768 x1 = (int)(x + cos(angle * PI / 180.) * r);
6769 y1 = (int)(y + sin(angle * PI / 180.) * r);
6770
6771 dc.DrawLine(x0, y0, x1, y1);
6772
6773 x0 = x1;
6774 y0 = y1;
6775
6776 i++;
6777 }
6778
6779 dc.DrawLine(x + radius, y, x1, y1); // closure
6780
6781 dc.SetPen(pen_save);
6782}
6783
6784static bool bAnchorSoundPlaying = false;
6785
6786static void onAnchorSoundFinished(void *ptr) {
6787 o_sound::g_anchorwatch_sound->UnLoad();
6788 bAnchorSoundPlaying = false;
6789}
6790
6791void ChartCanvas::AlertDraw(ocpnDC &dc) {
6792 using namespace o_sound;
6793 // Visual and audio alert for anchorwatch goes here
6794 bool play_sound = false;
6795 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6796 if (AnchorAlertOn1) {
6797 wxPoint TargetPoint;
6799 &TargetPoint);
6800 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6801 TargetPoint.y, 100);
6802 play_sound = true;
6803 }
6804 } else
6805 AnchorAlertOn1 = false;
6806
6807 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6808 if (AnchorAlertOn2) {
6809 wxPoint TargetPoint;
6811 &TargetPoint);
6812 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6813 TargetPoint.y, 100);
6814 play_sound = true;
6815 }
6816 } else
6817 AnchorAlertOn2 = false;
6818
6819 if (play_sound) {
6820 if (!bAnchorSoundPlaying) {
6821 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6822 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6823 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6824 if (g_anchorwatch_sound->IsOk()) {
6825 bAnchorSoundPlaying = true;
6826 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6827 g_anchorwatch_sound->Play();
6828 }
6829 }
6830 }
6831}
6832
6833void ChartCanvas::UpdateShips() {
6834 // Get the rectangle in the current dc which bounds the "ownship" symbol
6835
6836 wxClientDC dc(this);
6837 if (!dc.IsOk()) return;
6838
6839 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6840 if (!test_bitmap.IsOk()) return;
6841
6842 wxMemoryDC temp_dc(test_bitmap);
6843
6844 temp_dc.ResetBoundingBox();
6845 temp_dc.DestroyClippingRegion();
6846 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6847
6848 // Draw the ownship on the temp_dc
6849 ocpnDC ocpndc = ocpnDC(temp_dc);
6850 ShipDraw(ocpndc);
6851
6852 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6853 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6854 if (p) {
6855 wxPoint px;
6856 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6857 ocpndc.CalcBoundingBox(px.x, px.y);
6858 }
6859 }
6860
6861 ship_draw_rect =
6862 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6863 temp_dc.MaxY() - temp_dc.MinY());
6864
6865 wxRect own_ship_update_rect = ship_draw_rect;
6866
6867 if (!own_ship_update_rect.IsEmpty()) {
6868 // The required invalidate rectangle is the union of the last drawn
6869 // rectangle and this drawn rectangle
6870 own_ship_update_rect.Union(ship_draw_last_rect);
6871 own_ship_update_rect.Inflate(2);
6872 }
6873
6874 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6875
6876 ship_draw_last_rect = ship_draw_rect;
6877
6878 temp_dc.SelectObject(wxNullBitmap);
6879}
6880
6881void ChartCanvas::UpdateAlerts() {
6882 // Get the rectangle in the current dc which bounds the detected Alert
6883 // targets
6884
6885 // Use this dc
6886 wxClientDC dc(this);
6887
6888 // Get dc boundary
6889 int sx, sy;
6890 dc.GetSize(&sx, &sy);
6891
6892 // Need a bitmap
6893 wxBitmap test_bitmap(sx, sy, -1);
6894
6895 // Create a memory DC
6896 wxMemoryDC temp_dc;
6897 temp_dc.SelectObject(test_bitmap);
6898
6899 temp_dc.ResetBoundingBox();
6900 temp_dc.DestroyClippingRegion();
6901 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6902
6903 // Draw the Alert Targets on the temp_dc
6904 ocpnDC ocpndc = ocpnDC(temp_dc);
6905 AlertDraw(ocpndc);
6906
6907 // Retrieve the drawing extents
6908 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6909 temp_dc.MaxX() - temp_dc.MinX(),
6910 temp_dc.MaxY() - temp_dc.MinY());
6911
6912 if (!alert_rect.IsEmpty())
6913 alert_rect.Inflate(2); // clear all drawing artifacts
6914
6915 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6916 // The required invalidate rectangle is the union of the last drawn
6917 // rectangle and this drawn rectangle
6918 wxRect alert_update_rect = alert_draw_rect;
6919 alert_update_rect.Union(alert_rect);
6920
6921 // Invalidate the rectangular region
6922 RefreshRect(alert_update_rect, false);
6923 }
6924
6925 // Save this rectangle for next time
6926 alert_draw_rect = alert_rect;
6927
6928 temp_dc.SelectObject(wxNullBitmap); // clean up
6929}
6930
6931void ChartCanvas::UpdateAIS() {
6932 if (!g_pAIS) return;
6933
6934 // Get the rectangle in the current dc which bounds the detected AIS targets
6935
6936 // Use this dc
6937 wxClientDC dc(this);
6938
6939 // Get dc boundary
6940 int sx, sy;
6941 dc.GetSize(&sx, &sy);
6942
6943 wxRect ais_rect;
6944
6945 // How many targets are there?
6946
6947 // If more than "some number", it will be cheaper to refresh the entire
6948 // screen than to build update rectangles for each target.
6949 if (g_pAIS->GetTargetList().size() > 10) {
6950 ais_rect = wxRect(0, 0, sx, sy); // full screen
6951 } else {
6952 // Need a bitmap
6953 wxBitmap test_bitmap(sx, sy, -1);
6954
6955 // Create a memory DC
6956 wxMemoryDC temp_dc;
6957 temp_dc.SelectObject(test_bitmap);
6958
6959 temp_dc.ResetBoundingBox();
6960 temp_dc.DestroyClippingRegion();
6961 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6962
6963 // Draw the AIS Targets on the temp_dc
6964 ocpnDC ocpndc = ocpnDC(temp_dc);
6965 AISDraw(ocpndc, GetVP(), this);
6966 AISDrawAreaNotices(ocpndc, GetVP(), this);
6967
6968 // Retrieve the drawing extents
6969 ais_rect =
6970 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6971 temp_dc.MaxY() - temp_dc.MinY());
6972
6973 if (!ais_rect.IsEmpty())
6974 ais_rect.Inflate(2); // clear all drawing artifacts
6975
6976 temp_dc.SelectObject(wxNullBitmap); // clean up
6977 }
6978
6979 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6980 // The required invalidate rectangle is the union of the last drawn
6981 // rectangle and this drawn rectangle
6982 wxRect ais_update_rect = ais_draw_rect;
6983 ais_update_rect.Union(ais_rect);
6984
6985 // Invalidate the rectangular region
6986 RefreshRect(ais_update_rect, false);
6987 }
6988
6989 // Save this rectangle for next time
6990 ais_draw_rect = ais_rect;
6991}
6992
6993void ChartCanvas::ToggleCPAWarn() {
6994 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6995 wxString mess;
6996 if (g_bCPAWarn) {
6997 g_bTCPA_Max = true;
6998 mess = _("ON");
6999 } else {
7000 g_bTCPA_Max = false;
7001 mess = _("OFF");
7002 }
7003 // Print to status bar if available.
7004 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7005 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7006 } else {
7007 if (!g_AisFirstTimeUse) {
7008 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7009 _("CPA") + " " + mess, 4, 4);
7010 }
7011 }
7012}
7013
7014void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7015
7016void ChartCanvas::OnSize(wxSizeEvent &event) {
7017 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7018 // GetClientSize returns the size of the canvas area in logical pixels.
7019 GetClientSize(&m_canvas_width, &m_canvas_height);
7020
7021#ifdef __WXOSX__
7022 // Support scaled HDPI displays.
7023 m_displayScale = GetContentScaleFactor();
7024#endif
7025
7026 // Convert to physical pixels.
7027 m_canvas_width *= m_displayScale;
7028 m_canvas_height *= m_displayScale;
7029
7030 // Resize the current viewport
7031 VPoint.pix_width = m_canvas_width;
7032 VPoint.pix_height = m_canvas_height;
7033 VPoint.SetPixelScale(m_displayScale);
7034
7035 // Get some canvas metrics
7036
7037 // Rescale to current value, in order to rebuild VPoint data
7038 // structures for new canvas size
7040
7041 m_absolute_min_scale_ppm =
7042 m_canvas_width /
7043 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7044
7045 // Inform the parent Frame that I am being resized...
7046 gFrame->ProcessCanvasResize();
7047
7048 // if MUIBar is active, size the bar
7049 // if(g_useMUI && !m_muiBar){ // rebuild if
7050 // necessary
7051 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7052 // m_muiBarHOSize = m_muiBar->GetSize();
7053 // }
7054
7055 if (m_muiBar) {
7056 SetMUIBarPosition();
7057 UpdateFollowButtonState();
7058 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7059 }
7060
7061 // Set up the scroll margins
7062 xr_margin = m_canvas_width * 95 / 100;
7063 xl_margin = m_canvas_width * 5 / 100;
7064 yt_margin = m_canvas_height * 5 / 100;
7065 yb_margin = m_canvas_height * 95 / 100;
7066
7067 if (m_pQuilt)
7068 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7069
7070 // Resize the scratch BM
7071 delete pscratch_bm;
7072 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7073 m_brepaint_piano = true;
7074
7075 // Resize the Route Calculation BM
7076 m_dc_route.SelectObject(wxNullBitmap);
7077 delete proute_bm;
7078 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7079 m_dc_route.SelectObject(*proute_bm);
7080
7081 // Resize the saved Bitmap
7082 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7083
7084 // Resize the working Bitmap
7085 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7086
7087 // Rescale again, to capture all the changes for new canvas size
7089
7090#ifdef ocpnUSE_GL
7091 if (/*g_bopengl &&*/ m_glcc) {
7092 // FIXME (dave) This can go away?
7093 m_glcc->OnSize(event);
7094 }
7095#endif
7096
7097 FormatPianoKeys();
7098 // Invalidate the whole window
7099 ReloadVP();
7100}
7101
7102void ChartCanvas::ProcessNewGUIScale() {
7103 // m_muiBar->Hide();
7104 delete m_muiBar;
7105 m_muiBar = 0;
7106
7107 CreateMUIBar();
7108}
7109
7110void ChartCanvas::CreateMUIBar() {
7111 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7112 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7113 m_muiBar->SetColorScheme(m_cs);
7114 m_muiBarHOSize = m_muiBar->m_size;
7115 }
7116
7117 if (m_muiBar) {
7118 // We need to update the m_bENCGroup flag, not least for the initial
7119 // creation of a MUIBar
7120 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7121
7122 SetMUIBarPosition();
7123 UpdateFollowButtonState();
7124 m_muiBar->UpdateDynamicValues();
7125 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7126 }
7127}
7128
7129void ChartCanvas::SetMUIBarPosition() {
7130 // if MUIBar is active, size the bar
7131 if (m_muiBar) {
7132 // We estimate the piano width based on the canvas width
7133 int pianoWidth = GetClientSize().x * 0.6f;
7134 // If the piano already exists, we can use its exact width
7135 // if(m_Piano)
7136 // pianoWidth = m_Piano->GetWidth();
7137
7138 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7139 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7140 delete m_muiBar;
7141 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7142 m_muiBar->SetColorScheme(m_cs);
7143 }
7144 }
7145
7146 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7147 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7148 delete m_muiBar;
7149 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7150 m_muiBar->SetColorScheme(m_cs);
7151 }
7152 }
7153
7154 m_muiBar->SetBestPosition();
7155 }
7156}
7157
7158void ChartCanvas::DestroyMuiBar() {
7159 if (m_muiBar) {
7160 delete m_muiBar;
7161 m_muiBar = NULL;
7162 }
7163}
7164
7165void ChartCanvas::ShowCompositeInfoWindow(
7166 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7167 if (n_charts > 0) {
7168 if (NULL == m_pCIWin) {
7169 m_pCIWin = new ChInfoWin(this);
7170 m_pCIWin->Hide();
7171 }
7172
7173 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7174 wxString s;
7175
7176 s = _("Composite of ");
7177
7178 wxString s1;
7179 s1.Printf("%d ", n_charts);
7180 if (n_charts > 1)
7181 s1 += _("charts");
7182 else
7183 s1 += _("chart");
7184 s += s1;
7185 s += '\n';
7186
7187 s1.Printf(_("Chart scale"));
7188 s1 += ": ";
7189 wxString s2;
7190 s2.Printf("1:%d\n", scale);
7191 s += s1;
7192 s += s2;
7193
7194 s1 = _("Zoom in for more information");
7195 s += s1;
7196 s += '\n';
7197
7198 int char_width = s1.Length();
7199 int char_height = 3;
7200
7201 if (g_bChartBarEx) {
7202 s += '\n';
7203 int j = 0;
7204 for (int i : index_vector) {
7205 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7206 wxString path = cte.GetFullSystemPath();
7207 s += path;
7208 s += '\n';
7209 char_height++;
7210 char_width = wxMax(char_width, path.Length());
7211 if (j++ >= 9) break;
7212 }
7213 if (j >= 9) {
7214 s += " .\n .\n .\n";
7215 char_height += 3;
7216 }
7217 s += '\n';
7218 char_height += 1;
7219
7220 char_width += 4; // Fluff
7221 }
7222
7223 m_pCIWin->SetString(s);
7224
7225 m_pCIWin->FitToChars(char_width, char_height);
7226
7227 wxPoint p;
7228 p.x = x / GetContentScaleFactor();
7229 if ((p.x + m_pCIWin->GetWinSize().x) >
7230 (m_canvas_width / GetContentScaleFactor()))
7231 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7232 m_pCIWin->GetWinSize().x) /
7233 2; // centered
7234
7235 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7236 4 - m_pCIWin->GetWinSize().y;
7237
7238 m_pCIWin->dbIndex = 0;
7239 m_pCIWin->chart_scale = 0;
7240 m_pCIWin->SetPosition(p);
7241 m_pCIWin->SetBitmap();
7242 m_pCIWin->Refresh();
7243 m_pCIWin->Show();
7244 }
7245 } else {
7246 HideChartInfoWindow();
7247 }
7248}
7249
7250void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7251 if (dbIndex >= 0) {
7252 if (NULL == m_pCIWin) {
7253 m_pCIWin = new ChInfoWin(this);
7254 m_pCIWin->Hide();
7255 }
7256
7257 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7258 wxString s;
7259 ChartBase *pc = NULL;
7260
7261 // TOCTOU race but worst case will reload chart.
7262 // need to lock it or the background spooler may evict charts in
7263 // OpenChartFromDBAndLock
7264 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7265 pc = ChartData->OpenChartFromDBAndLock(
7266 dbIndex, FULL_INIT); // this must come from cache
7267
7268 int char_width, char_height;
7269 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7270 if (pc) ChartData->UnLockCacheChart(dbIndex);
7271
7272 m_pCIWin->SetString(s);
7273 m_pCIWin->FitToChars(char_width, char_height);
7274
7275 wxPoint p;
7276 p.x = x / GetContentScaleFactor();
7277 if ((p.x + m_pCIWin->GetWinSize().x) >
7278 (m_canvas_width / GetContentScaleFactor()))
7279 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7280 m_pCIWin->GetWinSize().x) /
7281 2; // centered
7282
7283 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7284 4 - m_pCIWin->GetWinSize().y;
7285
7286 m_pCIWin->dbIndex = dbIndex;
7287 m_pCIWin->SetPosition(p);
7288 m_pCIWin->SetBitmap();
7289 m_pCIWin->Refresh();
7290 m_pCIWin->Show();
7291 }
7292 } else {
7293 HideChartInfoWindow();
7294 }
7295}
7296
7297void ChartCanvas::HideChartInfoWindow() {
7298 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7299 m_pCIWin->Hide();
7300 m_pCIWin->Destroy();
7301 m_pCIWin = NULL;
7302
7303#ifdef __ANDROID__
7304 androidForceFullRepaint();
7305#endif
7306 }
7307}
7308
7309void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7310 wxMouseEvent ev(wxEVT_MOTION);
7311 ev.m_x = mouse_x;
7312 ev.m_y = mouse_y;
7313 ev.m_leftDown = mouse_leftisdown;
7314
7315 wxEvtHandler *evthp = GetEventHandler();
7316
7317 ::wxPostEvent(evthp, ev);
7318}
7319
7320void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7321 if ((m_panx_target_final - m_panx_target_now) ||
7322 (m_pany_target_final - m_pany_target_now)) {
7323 DoTimedMovementTarget();
7324 } else
7325 DoTimedMovement();
7326}
7327
7328void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7329
7330bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7331 int delta) {
7332 if (m_disable_edge_pan) return false;
7333
7334 bool bft = false;
7335 int pan_margin = m_canvas_width * margin / 100;
7336 int pan_timer_set = 200;
7337 double pan_delta = GetVP().pix_width * delta / 100;
7338 int pan_x = 0;
7339 int pan_y = 0;
7340
7341 if (x > m_canvas_width - pan_margin) {
7342 bft = true;
7343 pan_x = pan_delta;
7344 }
7345
7346 else if (x < pan_margin) {
7347 bft = true;
7348 pan_x = -pan_delta;
7349 }
7350
7351 if (y < pan_margin) {
7352 bft = true;
7353 pan_y = -pan_delta;
7354 }
7355
7356 else if (y > m_canvas_height - pan_margin) {
7357 bft = true;
7358 pan_y = pan_delta;
7359 }
7360
7361 // Of course, if dragging, and the mouse left button is not down, we must
7362 // stop the event injection
7363 if (bdragging) {
7364 if (!g_btouch) {
7365 wxMouseState state = ::wxGetMouseState();
7366#if wxCHECK_VERSION(3, 0, 0)
7367 if (!state.LeftIsDown())
7368#else
7369 if (!state.LeftDown())
7370#endif
7371 bft = false;
7372 }
7373 }
7374 if ((bft) && !pPanTimer->IsRunning()) {
7375 PanCanvas(pan_x, pan_y);
7376 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7377 return true;
7378 }
7379
7380 // This mouse event must not be due to pan timer event injector
7381 // Mouse is out of the pan zone, so prevent any orphan event injection
7382 if ((!bft) && pPanTimer->IsRunning()) {
7383 pPanTimer->Stop();
7384 }
7385
7386 return (false);
7387}
7388
7389// Look for waypoints at the current position.
7390// Used to determine what a mouse event should act on.
7391
7392void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7393 bool setBeingEdited) {
7394 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7395 m_pRoutePointEditTarget = NULL;
7396 m_pFoundPoint = NULL;
7397
7398 SelectItem *pFind = NULL;
7399 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7400 SelectableItemList SelList = pSelect->FindSelectionList(
7401 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7402 for (SelectItem *pFind : SelList) {
7403 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7404
7405 // Get an array of all routes using this point
7406 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7407 // TODO: delete m_pEditRouteArray after use?
7408
7409 // Use route array to determine actual visibility for the point
7410 bool brp_viz = false;
7411 if (m_pEditRouteArray) {
7412 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7413 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7414 if (pr->IsVisible()) {
7415 brp_viz = true;
7416 break;
7417 }
7418 }
7419 } else
7420 brp_viz = frp->IsVisible(); // isolated point
7421
7422 if (brp_viz) {
7423 // Use route array to rubberband all affected routes
7424 if (m_pEditRouteArray) // Editing Waypoint as part of route
7425 {
7426 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7427 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7428 pr->m_bIsBeingEdited = setBeingEdited;
7429 }
7430 m_bRouteEditing = setBeingEdited;
7431 } else // editing Mark
7432 {
7433 frp->m_bRPIsBeingEdited = setBeingEdited;
7434 m_bMarkEditing = setBeingEdited;
7435 }
7436
7437 m_pRoutePointEditTarget = frp;
7438 m_pFoundPoint = pFind;
7439 break; // out of the while(node)
7440 }
7441 } // for (SelectItem...
7442}
7443std::shared_ptr<HostApi121::PiPointContext>
7444ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7445 // General Right Click
7446 // Look for selectable objects
7447 double slat, slon;
7448 GetCanvasPixPoint(x, y, slat, slon);
7449
7450 SelectItem *pFindAIS;
7451 SelectItem *pFindRP;
7452 SelectItem *pFindRouteSeg;
7453 SelectItem *pFindTrackSeg;
7454 SelectItem *pFindCurrent = NULL;
7455 SelectItem *pFindTide = NULL;
7456
7457 // Get all the selectable things at the selected point
7458 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7459 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7460 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7461 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7462 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7463
7464 if (m_bShowCurrent)
7465 pFindCurrent =
7466 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7467
7468 if (m_bShowTide) // look for tide stations
7469 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7470
7471 int seltype = 0;
7472
7473 // Try for AIS targets first
7474 int FoundAIS_MMSI = 0;
7475 if (pFindAIS) {
7476 FoundAIS_MMSI = pFindAIS->GetUserData();
7477
7478 // Make sure the target data is available
7479 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7480 seltype |= SELTYPE_AISTARGET;
7481 }
7482
7483 // Now the various Route Parts
7484
7485 RoutePoint *FoundRoutePoint = NULL;
7486 Route *SelectedRoute = NULL;
7487
7488 if (pFindRP) {
7489 RoutePoint *pFirstVizPoint = NULL;
7490 RoutePoint *pFoundActiveRoutePoint = NULL;
7491 RoutePoint *pFoundVizRoutePoint = NULL;
7492 Route *pSelectedActiveRoute = NULL;
7493 Route *pSelectedVizRoute = NULL;
7494
7495 // There is at least one routepoint, so get the whole list
7496 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7497 SelectableItemList SelList =
7498 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7499 for (SelectItem *pFindSel : SelList) {
7500 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7501
7502 // Get an array of all routes using this point
7503 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7504
7505 // Use route array (if any) to determine actual visibility for this point
7506 bool brp_viz = false;
7507 if (proute_array) {
7508 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7509 Route *pr = (Route *)proute_array->Item(ir);
7510 if (pr->IsVisible()) {
7511 brp_viz = true;
7512 break;
7513 }
7514 }
7515 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7516 // but still exists as a waypoint
7517 brp_viz = prp->IsVisible(); // so treat as isolated point
7518
7519 } else
7520 brp_viz = prp->IsVisible(); // isolated point
7521
7522 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7523
7524 // Use route array to choose the appropriate route
7525 // Give preference to any active route, otherwise select the first visible
7526 // route in the array for this point
7527 if (proute_array) {
7528 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7529 Route *pr = (Route *)proute_array->Item(ir);
7530 if (pr->m_bRtIsActive) {
7531 pSelectedActiveRoute = pr;
7532 pFoundActiveRoutePoint = prp;
7533 break;
7534 }
7535 }
7536
7537 if (NULL == pSelectedVizRoute) {
7538 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7539 Route *pr = (Route *)proute_array->Item(ir);
7540 if (pr->IsVisible()) {
7541 pSelectedVizRoute = pr;
7542 pFoundVizRoutePoint = prp;
7543 break;
7544 }
7545 }
7546 }
7547
7548 delete proute_array;
7549 }
7550 }
7551
7552 // Now choose the "best" selections
7553 if (pFoundActiveRoutePoint) {
7554 FoundRoutePoint = pFoundActiveRoutePoint;
7555 SelectedRoute = pSelectedActiveRoute;
7556 } else if (pFoundVizRoutePoint) {
7557 FoundRoutePoint = pFoundVizRoutePoint;
7558 SelectedRoute = pSelectedVizRoute;
7559 } else
7560 // default is first visible point in list
7561 FoundRoutePoint = pFirstVizPoint;
7562
7563 if (SelectedRoute) {
7564 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7565 } else if (FoundRoutePoint) {
7566 seltype |= SELTYPE_MARKPOINT;
7567 }
7568
7569 // Highlight the selected point, to verify the proper right click selection
7570#if 0
7571 if (m_pFoundRoutePoint) {
7572 m_pFoundRoutePoint->m_bPtIsSelected = true;
7573 wxRect wp_rect;
7574 RoutePointGui(*m_pFoundRoutePoint)
7575 .CalculateDCRect(m_dc_route, this, &wp_rect);
7576 RefreshRect(wp_rect, true);
7577 }
7578#endif
7579 }
7580
7581 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7582 // routes But call the popup handler with identifier appropriate to the type
7583 if (pFindRouteSeg) // there is at least one select item
7584 {
7585 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7586 SelectableItemList SelList =
7587 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7588
7589 if (NULL == SelectedRoute) // the case where a segment only is selected
7590 {
7591 // Choose the first visible route containing segment in the list
7592 for (SelectItem *pFindSel : SelList) {
7593 Route *pr = (Route *)pFindSel->m_pData3;
7594 if (pr->IsVisible()) {
7595 SelectedRoute = pr;
7596 break;
7597 }
7598 }
7599 }
7600
7601 if (SelectedRoute) {
7602 if (NULL == FoundRoutePoint)
7603 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7604
7605 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7606 seltype |= SELTYPE_ROUTESEGMENT;
7607 }
7608 }
7609
7610 if (pFindTrackSeg) {
7611 m_pSelectedTrack = NULL;
7612 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7613 SelectableItemList SelList =
7614 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7615
7616 // Choose the first visible track containing segment in the list
7617 for (SelectItem *pFindSel : SelList) {
7618 Track *pt = (Track *)pFindSel->m_pData3;
7619 if (pt->IsVisible()) {
7620 m_pSelectedTrack = pt;
7621 break;
7622 }
7623 }
7624 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7625 }
7626
7627 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7628
7629 // Populate the return struct
7630 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7631 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7632 rstruct->object_ident = "";
7633
7634 if (seltype == SELTYPE_AISTARGET) {
7635 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7636 wxString val;
7637 val.Printf("%d", FoundAIS_MMSI);
7638 rstruct->object_ident = val.ToStdString();
7639 } else if (seltype & SELTYPE_MARKPOINT) {
7640 if (FoundRoutePoint) {
7641 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7642 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7643 }
7644 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7645 if (SelectedRoute) {
7646 rstruct->object_type =
7647 HostApi121::PiContextObjectType::kObjectRoutesegment;
7648 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7649 }
7650 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7651 if (m_pSelectedTrack) {
7652 rstruct->object_type =
7653 HostApi121::PiContextObjectType::kObjectTracksegment;
7654 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7655 }
7656 }
7657
7658 return rstruct;
7659}
7660
7661void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7662 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7663 singleClickEventIsValid = false;
7664 m_DoubleClickTimer->Stop();
7665}
7666
7667bool leftIsDown;
7668
7669bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7670 if (!m_bChartDragging && !m_bDrawingRoute) {
7671 /*
7672 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7673 * mouse event coordinates are in logical pixels.
7674 */
7675 if (m_Compass && m_Compass->IsShown()) {
7676 wxRect logicalRect = m_Compass->GetLogicalRect();
7677 bool isInCompass = logicalRect.Contains(event.GetPosition());
7678 if (isInCompass || m_mouseWasInCompass) {
7679 if (m_Compass->MouseEvent(event)) {
7680 cursor_region = CENTER;
7681 if (!g_btouch) SetCanvasCursor(event);
7682 m_mouseWasInCompass = isInCompass;
7683 return true;
7684 }
7685 }
7686 m_mouseWasInCompass = isInCompass;
7687 }
7688
7689 if (m_notification_button && m_notification_button->IsShown()) {
7690 wxRect logicalRect = m_notification_button->GetLogicalRect();
7691 bool isinButton = logicalRect.Contains(event.GetPosition());
7692 if (isinButton) {
7693 SetCursor(*pCursorArrow);
7694 if (event.LeftDown()) HandleNotificationMouseClick();
7695 return true;
7696 }
7697 }
7698
7699 if (MouseEventToolbar(event)) return true;
7700
7701 if (MouseEventChartBar(event)) return true;
7702
7703 if (MouseEventMUIBar(event)) return true;
7704
7705 if (MouseEventIENCBar(event)) return true;
7706 }
7707 return false;
7708}
7709
7710void ChartCanvas::HandleNotificationMouseClick() {
7711 if (!m_NotificationsList) {
7712 m_NotificationsList = new NotificationsList(this);
7713
7714 // calculate best size for Notification list
7715 m_NotificationsList->RecalculateSize();
7716 m_NotificationsList->Hide();
7717 }
7718
7719 if (m_NotificationsList->IsShown()) {
7720 m_NotificationsList->Hide();
7721 } else {
7722 m_NotificationsList->RecalculateSize();
7723 m_NotificationsList->ReloadNotificationList();
7724 m_NotificationsList->Show();
7725 }
7726}
7727bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7728 if (!g_bShowChartBar) return false;
7729
7730 if (!m_Piano->MouseEvent(event)) return false;
7731
7732 cursor_region = CENTER;
7733 if (!g_btouch) SetCanvasCursor(event);
7734 return true;
7735}
7736
7737bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7738 if (!IsPrimaryCanvas()) return false;
7739
7740 if (g_MainToolbar) {
7741 if (!g_MainToolbar->MouseEvent(event))
7742 return false;
7743 else
7744 g_MainToolbar->RefreshToolbar();
7745 }
7746
7747 cursor_region = CENTER;
7748 if (!g_btouch) SetCanvasCursor(event);
7749 return true;
7750}
7751
7752bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7753 if (!IsPrimaryCanvas()) return false;
7754
7755 if (g_iENCToolbar) {
7756 if (!g_iENCToolbar->MouseEvent(event))
7757 return false;
7758 else {
7759 g_iENCToolbar->RefreshToolbar();
7760 return true;
7761 }
7762 }
7763 return false;
7764}
7765
7766bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7767 if (m_muiBar) {
7768 if (!m_muiBar->MouseEvent(event)) return false;
7769 }
7770
7771 cursor_region = CENTER;
7772 if (!g_btouch) SetCanvasCursor(event);
7773 if (m_muiBar)
7774 return true;
7775 else
7776 return false;
7777}
7778
7779bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7780 int x, y;
7781
7782 bool bret = false;
7783
7784 event.GetPosition(&x, &y);
7785
7786 x *= m_displayScale;
7787 y *= m_displayScale;
7788
7789 m_MouseDragging = event.Dragging();
7790
7791 // Some systems produce null drag events, where the pointer position has not
7792 // changed from the previous value. Detect this case, and abort further
7793 // processing (FS#1748)
7794#ifdef __WXMSW__
7795 if (event.Dragging()) {
7796 if ((x == mouse_x) && (y == mouse_y)) return true;
7797 }
7798#endif
7799
7800 mouse_x = x;
7801 mouse_y = y;
7802 mouse_leftisdown = event.LeftDown();
7804
7805 // Establish the event region
7806 cursor_region = CENTER;
7807
7808 int chartbar_height = GetChartbarHeight();
7809
7810 if (m_Compass && m_Compass->IsShown() &&
7811 m_Compass->GetRect().Contains(event.GetPosition())) {
7812 cursor_region = CENTER;
7813 } else if (x > xr_margin) {
7814 cursor_region = MID_RIGHT;
7815 } else if (x < xl_margin) {
7816 cursor_region = MID_LEFT;
7817 } else if (y > yb_margin - chartbar_height &&
7818 y < m_canvas_height - chartbar_height) {
7819 cursor_region = MID_TOP;
7820 } else if (y < yt_margin) {
7821 cursor_region = MID_BOT;
7822 } else {
7823 cursor_region = CENTER;
7824 }
7825
7826 if (!g_btouch) SetCanvasCursor(event);
7827
7828 // Protect from leftUp's coming from event handlers in child
7829 // windows who return focus to the canvas.
7830 leftIsDown = event.LeftDown();
7831
7832#ifndef __WXOSX__
7833 if (event.LeftDown()) {
7834 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7835 // The menu bar is temporarily visible due to alt having been pressed.
7836 // Clicking will hide it, and do nothing else.
7837 g_bTempShowMenuBar = false;
7838 parent_frame->ApplyGlobalSettings(false);
7839 return (true);
7840 }
7841 }
7842#endif
7843
7844 // Update modifiers here; some window managers never send the key event
7845 m_modkeys = 0;
7846 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7847 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7848
7849#ifdef __WXMSW__
7850 // TODO Test carefully in other platforms, remove ifdef....
7851 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7852 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7853#endif
7854
7855 event.SetEventObject(this);
7856 if (SendMouseEventToPlugins(event))
7857 return (true); // PlugIn did something, and does not want the canvas to
7858 // do anything else
7859
7860 // Capture LeftUp's and time them, unless it already came from the timer.
7861
7862 // Detect end of chart dragging
7863 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7864 StartChartDragInertia();
7865 }
7866
7867 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7868 !singleClickEventIsValid) {
7869 // Ignore the second LeftUp after the DClick.
7870 if (m_DoubleClickTimer->IsRunning()) {
7871 m_DoubleClickTimer->Stop();
7872 return (true);
7873 }
7874
7875 // Save the event for later running if there is no DClick.
7876 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7877 singleClickEvent = event;
7878 singleClickEventIsValid = true;
7879 return (true);
7880 }
7881
7882 // This logic is necessary on MSW to handle the case where
7883 // a context (right-click) menu is dismissed without action
7884 // by clicking on the chart surface.
7885 // We need to avoid an unintentional pan by eating some clicks...
7886#ifdef __WXMSW__
7887 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7888 if (g_click_stop > 0) {
7889 g_click_stop--;
7890 return (true);
7891 }
7892 }
7893#endif
7894
7895 // Kick off the Rotation control timer
7896 if (GetUpMode() == COURSE_UP_MODE) {
7897 m_b_rot_hidef = false;
7898 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7899 } else
7900 pRotDefTimer->Stop();
7901
7902 // Retrigger the route leg / AIS target popup timer
7903 bool bRoll = !g_btouch;
7904#ifdef __ANDROID__
7905 bRoll = g_bRollover;
7906#endif
7907 if (bRoll) {
7908 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7909 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7910 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7911 m_RolloverPopupTimer.Start(
7912 10,
7913 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7914 else
7915 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7916 }
7917
7918 // Retrigger the cursor tracking timer
7919 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7920
7921// Show cursor position on Status Bar, if present
7922// except for GTK, under which status bar updates are very slow
7923// due to Update() call.
7924// In this case, as a workaround, update the status window
7925// after an interval timer (pCurTrackTimer) pops, which will happen
7926// whenever the mouse has stopped moving for specified interval.
7927// See the method OnCursorTrackTimerEvent()
7928#if !defined(__WXGTK__) && !defined(__WXQT__)
7929 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7930#endif
7931
7932 // Send the current cursor lat/lon to all PlugIns requesting it
7933 if (g_pi_manager) {
7934 // Occasionally, MSW will produce nonsense events on right click....
7935 // This results in an error in cursor geo position, so we skip this case
7936 if ((x >= 0) && (y >= 0))
7937 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7938 }
7939
7940 if (!g_btouch) {
7941 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7942 wxPoint p = ClientToScreen(wxPoint(x, y));
7943 }
7944 }
7945
7946 if (1 ) {
7947 // Route Creation Rubber Banding
7948 if (m_routeState >= 2) {
7949 r_rband.x = x;
7950 r_rband.y = y;
7951 m_bDrawingRoute = true;
7952
7953 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7954 Refresh(false);
7955 }
7956
7957 // Measure Tool Rubber Banding
7958 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7959 r_rband.x = x;
7960 r_rband.y = y;
7961 m_bDrawingRoute = true;
7962
7963 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7964 Refresh(false);
7965 }
7966 }
7967 return bret;
7968}
7969
7970int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7971 // On general Right Click
7972 // Look for selectable objects
7973 double slat = lat;
7974 double slon = lon;
7975
7976#if defined(__WXMAC__) || defined(__ANDROID__)
7977 wxScreenDC sdc;
7978 ocpnDC dc(sdc);
7979#else
7980 wxClientDC cdc(GetParent());
7981 ocpnDC dc(cdc);
7982#endif
7983
7984 SelectItem *pFindAIS;
7985 SelectItem *pFindRP;
7986 SelectItem *pFindRouteSeg;
7987 SelectItem *pFindTrackSeg;
7988 SelectItem *pFindCurrent = NULL;
7989 SelectItem *pFindTide = NULL;
7990
7991 // Deselect any current objects
7992 if (m_pSelectedRoute) {
7993 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7994 m_pSelectedRoute->DeSelectRoute();
7995#ifdef ocpnUSE_GL
7996 if (g_bopengl && m_glcc) {
7997 InvalidateGL();
7998 Update();
7999 } else
8000#endif
8001 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8002 }
8003
8004 if (m_pFoundRoutePoint) {
8005 m_pFoundRoutePoint->m_bPtIsSelected = false;
8006 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8007 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8008 }
8009
8012 if (g_btouch && m_pRoutePointEditTarget) {
8013 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8014 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8015 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8016 }
8017
8018 // Get all the selectable things at the cursor
8019 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8020 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8021 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8022 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8023 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8024
8025 if (m_bShowCurrent)
8026 pFindCurrent =
8027 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8028
8029 if (m_bShowTide) // look for tide stations
8030 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8031
8032 int seltype = 0;
8033
8034 // Try for AIS targets first
8035 if (pFindAIS) {
8036 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8037
8038 // Make sure the target data is available
8039 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8040 seltype |= SELTYPE_AISTARGET;
8041 }
8042
8043 // Now examine the various Route parts
8044
8045 m_pFoundRoutePoint = NULL;
8046 if (pFindRP) {
8047 RoutePoint *pFirstVizPoint = NULL;
8048 RoutePoint *pFoundActiveRoutePoint = NULL;
8049 RoutePoint *pFoundVizRoutePoint = NULL;
8050 Route *pSelectedActiveRoute = NULL;
8051 Route *pSelectedVizRoute = NULL;
8052
8053 // There is at least one routepoint, so get the whole list
8054 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8055 SelectableItemList SelList =
8056 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8057 for (SelectItem *pFindSel : SelList) {
8058 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8059
8060 // Get an array of all routes using this point
8061 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8062
8063 // Use route array (if any) to determine actual visibility for this point
8064 bool brp_viz = false;
8065 if (proute_array) {
8066 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8067 Route *pr = (Route *)proute_array->Item(ir);
8068 if (pr->IsVisible()) {
8069 brp_viz = true;
8070 break;
8071 }
8072 }
8073 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8074 // but still exists as a waypoint
8075 brp_viz = prp->IsVisible(); // so treat as isolated point
8076
8077 } else
8078 brp_viz = prp->IsVisible(); // isolated point
8079
8080 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8081
8082 // Use route array to choose the appropriate route
8083 // Give preference to any active route, otherwise select the first visible
8084 // route in the array for this point
8085 m_pSelectedRoute = NULL;
8086 if (proute_array) {
8087 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8088 Route *pr = (Route *)proute_array->Item(ir);
8089 if (pr->m_bRtIsActive) {
8090 pSelectedActiveRoute = pr;
8091 pFoundActiveRoutePoint = prp;
8092 break;
8093 }
8094 }
8095
8096 if (NULL == pSelectedVizRoute) {
8097 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8098 Route *pr = (Route *)proute_array->Item(ir);
8099 if (pr->IsVisible()) {
8100 pSelectedVizRoute = pr;
8101 pFoundVizRoutePoint = prp;
8102 break;
8103 }
8104 }
8105 }
8106
8107 delete proute_array;
8108 }
8109 }
8110
8111 // Now choose the "best" selections
8112 if (pFoundActiveRoutePoint) {
8113 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8114 m_pSelectedRoute = pSelectedActiveRoute;
8115 } else if (pFoundVizRoutePoint) {
8116 m_pFoundRoutePoint = pFoundVizRoutePoint;
8117 m_pSelectedRoute = pSelectedVizRoute;
8118 } else
8119 // default is first visible point in list
8120 m_pFoundRoutePoint = pFirstVizPoint;
8121
8122 if (m_pSelectedRoute) {
8123 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8124 } else if (m_pFoundRoutePoint) {
8125 seltype |= SELTYPE_MARKPOINT;
8126 }
8127
8128 // Highlight the selected point, to verify the proper right click selection
8129 if (m_pFoundRoutePoint) {
8130 m_pFoundRoutePoint->m_bPtIsSelected = true;
8131 wxRect wp_rect;
8132 RoutePointGui(*m_pFoundRoutePoint)
8133 .CalculateDCRect(m_dc_route, this, &wp_rect);
8134 RefreshRect(wp_rect, true);
8135 }
8136 }
8137
8138 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8139 // routes But call the popup handler with identifier appropriate to the type
8140 if (pFindRouteSeg) // there is at least one select item
8141 {
8142 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8143 SelectableItemList SelList =
8144 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8145
8146 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8147 {
8148 // Choose the first visible route containing segment in the list
8149 for (SelectItem *pFindSel : SelList) {
8150 Route *pr = (Route *)pFindSel->m_pData3;
8151 if (pr->IsVisible()) {
8152 m_pSelectedRoute = pr;
8153 break;
8154 }
8155 }
8156 }
8157
8158 if (m_pSelectedRoute) {
8159 if (NULL == m_pFoundRoutePoint)
8160 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8161
8162 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8163 if (m_pSelectedRoute->m_bRtIsSelected) {
8164#ifdef ocpnUSE_GL
8165 if (g_bopengl && m_glcc) {
8166 InvalidateGL();
8167 Update();
8168 } else
8169#endif
8170 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8171 }
8172 seltype |= SELTYPE_ROUTESEGMENT;
8173 }
8174 }
8175
8176 if (pFindTrackSeg) {
8177 m_pSelectedTrack = NULL;
8178 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8179 SelectableItemList SelList =
8180 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8181
8182 // Choose the first visible track containing segment in the list
8183 for (SelectItem *pFindSel : SelList) {
8184 Track *pt = (Track *)pFindSel->m_pData3;
8185 if (pt->IsVisible()) {
8186 m_pSelectedTrack = pt;
8187 break;
8188 }
8189 }
8190 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8191 }
8192
8193#if 0 // disable tide and current graph on right click
8194 {
8195 if (pFindCurrent) {
8196 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8197 seltype |= SELTYPE_CURRENTPOINT;
8198 }
8199
8200 else if (pFindTide) {
8201 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8202 seltype |= SELTYPE_TIDEPOINT;
8203 }
8204 }
8205#endif
8206
8207 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8208
8209 return seltype;
8210}
8211
8212IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8213 // There may be multiple current entries at the same point.
8214 // For example, there often is a current substation (with directions
8215 // specified) co-located with its master. We want to select the
8216 // substation, so that the direction will be properly indicated on the
8217 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8218 // substation)
8219 IDX_entry *pIDX_best_candidate;
8220
8221 SelectItem *pFind = NULL;
8222 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8223 SelectableItemList SelList =
8224 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8225
8226 // Default is first entry
8227 pFind = *SelList.begin();
8228 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8229
8230 auto node = SelList.begin();
8231 if (SelList.size() > 1) {
8232 for (++node; node != SelList.end(); ++node) {
8233 pFind = *node;
8234 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8235 if (pIDX_candidate->IDX_type == 'c') {
8236 pIDX_best_candidate = pIDX_candidate;
8237 break;
8238 }
8239 } // while (node)
8240 } else {
8241 pFind = *SelList.begin();
8242 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8243 }
8244
8245 return pIDX_best_candidate;
8246}
8247void ChartCanvas::CallPopupMenu(int x, int y) {
8248 last_drag.x = x;
8249 last_drag.y = y;
8250 if (m_routeState) { // creating route?
8251 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8252 return;
8253 }
8254
8256
8257 // If tide or current point is selected, then show the TC dialog immediately
8258 // without context menu
8259 if (SELTYPE_CURRENTPOINT == seltype) {
8260 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8261 Refresh(false);
8262 return;
8263 }
8264
8265 if (SELTYPE_TIDEPOINT == seltype) {
8266 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8267 Refresh(false);
8268 return;
8269 }
8270
8271 InvokeCanvasMenu(x, y, seltype);
8272
8273 // Clean up if not deleted in InvokeCanvasMenu
8274 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8275 m_pSelectedRoute->m_bRtIsSelected = false;
8276 }
8277
8278 m_pSelectedRoute = NULL;
8279
8280 if (m_pFoundRoutePoint) {
8281 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8282 m_pFoundRoutePoint->m_bPtIsSelected = false;
8283 }
8284 m_pFoundRoutePoint = NULL;
8285
8286 Refresh(true);
8287 // Refresh(false); // needed for MSW, not GTK Why??
8288}
8289
8290bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8291 // For now just bail out completely if the point clicked is not on the chart
8292 if (std::isnan(m_cursor_lat)) return false;
8293
8294 // Mouse Clicks
8295 bool ret = false; // return true if processed
8296
8297 int x, y, mx, my;
8298 event.GetPosition(&x, &y);
8299 mx = x;
8300 my = y;
8301
8302 // Calculate meaningful SelectRadius
8303 float SelectRadius;
8304 SelectRadius = g_Platform->GetSelectRadiusPix() /
8305 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8306
8308 // We start with Double Click processing. The first left click just starts a
8309 // timer and is remembered, then we actually do something if there is a
8310 // LeftDClick. If there is, the two single clicks are ignored.
8311
8312 if (event.LeftDClick() && (cursor_region == CENTER)) {
8313 m_DoubleClickTimer->Start();
8314 singleClickEventIsValid = false;
8315
8316 double zlat, zlon;
8318 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8319
8320 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8321 if (m_bShowAIS) {
8322 SelectItem *pFindAIS;
8323 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8324
8325 if (pFindAIS) {
8326 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8327 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8328 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8329 }
8330 return true;
8331 }
8332 }
8333
8334 SelectableItemList rpSelList =
8335 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8336 bool b_onRPtarget = false;
8337 for (SelectItem *pFind : rpSelList) {
8338 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8339 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8340 b_onRPtarget = true;
8341 break;
8342 }
8343 }
8344
8345 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8346
8347 // Get and honor the plugin API ContextMenuMask
8348 std::unique_ptr<HostApi> host_api = GetHostApi();
8349 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8350
8351 if (m_pRoutePointEditTarget) {
8352 if (b_onRPtarget) {
8353 if ((api_121->GetContextMenuMask() &
8354 api_121->kContextMenuDisableWaypoint))
8355 return true;
8356 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8357 return true;
8358 } else {
8359 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8360 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8361 if (g_btouch)
8362 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8363 wxRect wp_rect;
8364 RoutePointGui(*m_pRoutePointEditTarget)
8365 .CalculateDCRect(m_dc_route, this, &wp_rect);
8366 m_pRoutePointEditTarget = NULL; // cancel selection
8367 RefreshRect(wp_rect, true);
8368 return true;
8369 }
8370 } else {
8371 auto node = rpSelList.begin();
8372 if (node != rpSelList.end()) {
8373 SelectItem *pFind = *node;
8374 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8375 if (frp) {
8376 wxArrayPtrVoid *proute_array =
8378
8379 // Use route array (if any) to determine actual visibility for this
8380 // point
8381 bool brp_viz = false;
8382 if (proute_array) {
8383 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8384 Route *pr = (Route *)proute_array->Item(ir);
8385 if (pr->IsVisible()) {
8386 brp_viz = true;
8387 break;
8388 }
8389 }
8390 delete proute_array;
8391 if (!brp_viz &&
8392 frp->IsShared()) // is not visible as part of route, but
8393 // still exists as a waypoint
8394 brp_viz = frp->IsVisible(); // so treat as isolated point
8395 } else
8396 brp_viz = frp->IsVisible(); // isolated point
8397
8398 if (brp_viz) {
8399 if ((api_121->GetContextMenuMask() &
8400 api_121->kContextMenuDisableWaypoint))
8401 return true;
8402
8403 ShowMarkPropertiesDialog(frp);
8404 return true;
8405 }
8406 }
8407 }
8408 }
8409
8410 SelectItem *cursorItem;
8411
8412 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8413 if (cursorItem) {
8414 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8415 return true;
8416 Route *pr = (Route *)cursorItem->m_pData3;
8417 if (pr->IsVisible()) {
8418 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8419 return true;
8420 }
8421 }
8422
8423 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8424 if (cursorItem) {
8425 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8426 return true;
8427 Track *pt = (Track *)cursorItem->m_pData3;
8428 if (pt->IsVisible()) {
8429 ShowTrackPropertiesDialog(pt);
8430 return true;
8431 }
8432 }
8433
8434 // Tide and current points
8435 SelectItem *pFindCurrent = NULL;
8436 SelectItem *pFindTide = NULL;
8437
8438 if (m_bShowCurrent) { // look for current stations
8439 pFindCurrent =
8440 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8441 if (pFindCurrent) {
8442 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8443 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8444 Refresh(false);
8445 return true;
8446 }
8447 }
8448
8449 if (m_bShowTide) { // look for tide stations
8450 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8451 if (pFindTide) {
8452 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8453 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8454 Refresh(false);
8455 return true;
8456 }
8457 }
8458
8459 // Found no object to act on, so show chart info.
8460 ShowObjectQueryWindow(x, y, zlat, zlon);
8461 return true;
8462 }
8463
8465 if (event.LeftDown()) {
8466 // This really should not be needed, but....
8467 // on Windows, when using wxAUIManager, sometimes the focus is lost
8468 // when clicking into another pane, e.g.the AIS target list, and then back
8469 // to this pane. Oddly, some mouse events are not lost, however. Like this
8470 // one....
8471 SetFocus();
8472
8473 last_drag.x = mx;
8474 last_drag.y = my;
8475 leftIsDown = true;
8476
8477 if (!g_btouch) {
8478 if (m_routeState) // creating route?
8479 {
8480 double rlat, rlon;
8481 bool appending = false;
8482 bool inserting = false;
8483 Route *tail = 0;
8484
8485 SetCursor(*pCursorPencil);
8486 rlat = m_cursor_lat;
8487 rlon = m_cursor_lon;
8488
8489 m_bRouteEditing = true;
8490
8491 if (m_routeState == 1) {
8492 m_pMouseRoute = new Route();
8493 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8494 pRouteList->push_back(m_pMouseRoute);
8495 r_rband.x = x;
8496 r_rband.y = y;
8497 }
8498
8499 // Check to see if there is a nearby point which may be reused
8500 RoutePoint *pMousePoint = NULL;
8501
8502 // Calculate meaningful SelectRadius
8503 double nearby_radius_meters =
8504 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8505
8506 RoutePoint *pNearbyPoint =
8507 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8508 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8509 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8510 wxArrayPtrVoid *proute_array =
8511 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8512
8513 // Use route array (if any) to determine actual visibility for this
8514 // point
8515 bool brp_viz = false;
8516 if (proute_array) {
8517 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8518 Route *pr = (Route *)proute_array->Item(ir);
8519 if (pr->IsVisible()) {
8520 brp_viz = true;
8521 break;
8522 }
8523 }
8524 delete proute_array;
8525 if (!brp_viz &&
8526 pNearbyPoint->IsShared()) // is not visible as part of route,
8527 // but still exists as a waypoint
8528 brp_viz =
8529 pNearbyPoint->IsVisible(); // so treat as isolated point
8530 } else
8531 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8532
8533 if (brp_viz) {
8534 wxString msg = _("Use nearby waypoint?");
8535 // Don't add a mark without name to the route. Name it if needed
8536 const bool noname(pNearbyPoint->GetName() == "");
8537 if (noname) {
8538 msg =
8539 _("Use nearby nameless waypoint and name it M with"
8540 " a unique number?");
8541 }
8542 // Avoid route finish on focus change for message dialog
8543 m_FinishRouteOnKillFocus = false;
8544 int dlg_return =
8545 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8546 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8547 m_FinishRouteOnKillFocus = true;
8548 if (dlg_return == wxID_YES) {
8549 if (noname) {
8550 if (m_pMouseRoute) {
8551 int last_wp_num = m_pMouseRoute->GetnPoints();
8552 // AP-ECRMB will truncate to 6 characters
8553 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8554 wxString wp_name = wxString::Format(
8555 "M%002i-%s", last_wp_num + 1, guid_short);
8556 pNearbyPoint->SetName(wp_name);
8557 } else
8558 pNearbyPoint->SetName("WPXX");
8559 }
8560 pMousePoint = pNearbyPoint;
8561
8562 // Using existing waypoint, so nothing to delete for undo.
8563 if (m_routeState > 1)
8564 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8565 Undo_HasParent, NULL);
8566
8567 tail =
8568 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8569 bool procede = false;
8570 if (tail) {
8571 procede = true;
8572 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8573 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8574 procede = false;
8575 }
8576
8577 if (procede) {
8578 int dlg_return;
8579 m_FinishRouteOnKillFocus = false;
8580 if (m_routeState ==
8581 1) { // first point in new route, preceeding route to be
8582 // added? Not touch case
8583
8584 wxString dmsg =
8585 _("Insert first part of this route in the new route?");
8586 if (tail->GetIndexOf(pMousePoint) ==
8587 tail->GetnPoints()) // Starting on last point of another
8588 // route?
8589 dmsg = _("Insert this route in the new route?");
8590
8591 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8592 dlg_return = OCPNMessageBox(
8593 this, dmsg, _("OpenCPN Route Create"),
8594 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8595 m_FinishRouteOnKillFocus = true;
8596
8597 if (dlg_return == wxID_YES) {
8598 inserting = true; // part of the other route will be
8599 // preceeding the new route
8600 }
8601 }
8602 } else {
8603 wxString dmsg =
8604 _("Append last part of this route to the new route?");
8605 if (tail->GetIndexOf(pMousePoint) == 1)
8606 dmsg = _(
8607 "Append this route to the new route?"); // Picking the
8608 // first point
8609 // of another
8610 // route?
8611
8612 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8613 dlg_return = OCPNMessageBox(
8614 this, dmsg, _("OpenCPN Route Create"),
8615 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8616 m_FinishRouteOnKillFocus = true;
8617
8618 if (dlg_return == wxID_YES) {
8619 appending = true; // part of the other route will be
8620 // appended to the new route
8621 }
8622 }
8623 }
8624 }
8625
8626 // check all other routes to see if this point appears in any
8627 // other route If it appears in NO other route, then it should e
8628 // considered an isolated mark
8629 if (!FindRouteContainingWaypoint(pMousePoint))
8630 pMousePoint->SetShared(true);
8631 }
8632 }
8633 }
8634
8635 if (NULL == pMousePoint) { // need a new point
8636 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8637 "", wxEmptyString);
8638 pMousePoint->SetNameShown(false);
8639
8640 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8641
8642 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8643
8644 if (m_routeState > 1)
8645 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8646 Undo_IsOrphanded, NULL);
8647 }
8648
8649 if (m_pMouseRoute) {
8650 if (m_routeState == 1) {
8651 // First point in the route.
8652 m_pMouseRoute->AddPoint(pMousePoint);
8653 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8654 } else {
8655 if (m_pMouseRoute->m_NextLegGreatCircle) {
8656 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8657 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8658 &rhumbBearing, &rhumbDist);
8659 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8660 rlat, &gcDist, &gcBearing, NULL);
8661 double gcDistNM = gcDist / 1852.0;
8662
8663 // Empirically found expression to get reasonable route segments.
8664 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8665 pow(rhumbDist - gcDistNM - 1, 0.5);
8666
8667 wxString msg;
8668 msg << _("For this leg the Great Circle route is ")
8669 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8670 << _(" shorter than rhumbline.\n\n")
8671 << _("Would you like include the Great Circle routing points "
8672 "for this leg?");
8673
8674 m_FinishRouteOnKillFocus = false;
8675 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8676 // does not fully capture mouse
8677
8678 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8679 wxYES_NO | wxNO_DEFAULT);
8680
8681 m_disable_edge_pan = false;
8682 m_FinishRouteOnKillFocus = true;
8683
8684 if (answer == wxID_YES) {
8685 RoutePoint *gcPoint;
8686 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8687 wxRealPoint gcCoord;
8688
8689 for (int i = 1; i <= segmentCount; i++) {
8690 double fraction = (double)i * (1.0 / (double)segmentCount);
8691 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8692 gcDist * fraction, gcBearing,
8693 &gcCoord.x, &gcCoord.y, NULL);
8694
8695 if (i < segmentCount) {
8696 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8697 wxEmptyString);
8698 gcPoint->SetNameShown(false);
8699 // pConfig->AddNewWayPoint(gcPoint, -1);
8700 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8701
8702 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8703 gcPoint);
8704 } else {
8705 gcPoint = pMousePoint; // Last point, previously exsisting!
8706 }
8707
8708 m_pMouseRoute->AddPoint(gcPoint);
8709 pSelect->AddSelectableRouteSegment(
8710 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8711 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8712 prevGcPoint = gcPoint;
8713 }
8714
8715 undo->CancelUndoableAction(true);
8716
8717 } else {
8718 m_pMouseRoute->AddPoint(pMousePoint);
8719 pSelect->AddSelectableRouteSegment(
8720 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8721 pMousePoint, m_pMouseRoute);
8722 undo->AfterUndoableAction(m_pMouseRoute);
8723 }
8724 } else {
8725 // Ordinary rhumblinesegment.
8726 m_pMouseRoute->AddPoint(pMousePoint);
8727 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8728 rlon, m_prev_pMousePoint,
8729 pMousePoint, m_pMouseRoute);
8730 undo->AfterUndoableAction(m_pMouseRoute);
8731 }
8732 }
8733 }
8734 m_prev_rlat = rlat;
8735 m_prev_rlon = rlon;
8736 m_prev_pMousePoint = pMousePoint;
8737 if (m_pMouseRoute)
8738 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8739
8740 m_routeState++;
8741
8742 if (appending ||
8743 inserting) { // Appending a route or making a new route
8744 int connect = tail->GetIndexOf(pMousePoint);
8745 if (connect == 1) {
8746 inserting = false; // there is nothing to insert
8747 appending = true; // so append
8748 }
8749 int length = tail->GetnPoints();
8750
8751 int i;
8752 int start, stop;
8753 if (appending) {
8754 start = connect + 1;
8755 stop = length;
8756 } else { // inserting
8757 start = 1;
8758 stop = connect;
8759 m_pMouseRoute->RemovePoint(
8760 m_pMouseRoute
8761 ->GetLastPoint()); // Remove the first and only point
8762 }
8763 for (i = start; i <= stop; i++) {
8764 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8765 if (m_pMouseRoute)
8766 m_pMouseRoute->m_lastMousePointIndex =
8767 m_pMouseRoute->GetnPoints();
8768 m_routeState++;
8769 gFrame->RefreshAllCanvas();
8770 ret = true;
8771 }
8772 m_prev_rlat =
8773 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8774 m_prev_rlon =
8775 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8776 m_pMouseRoute->FinalizeForRendering();
8777 }
8778 gFrame->RefreshAllCanvas();
8779 ret = true;
8780 }
8781
8782 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8783 {
8784 SetCursor(*pCursorPencil);
8785
8786 if (!m_pMeasureRoute) {
8787 m_pMeasureRoute = new Route();
8788 pRouteList->push_back(m_pMeasureRoute);
8789 }
8790
8791 if (m_nMeasureState == 1) {
8792 r_rband.x = x;
8793 r_rband.y = y;
8794 }
8795
8796 RoutePoint *pMousePoint =
8797 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8798 wxEmptyString, wxEmptyString);
8799 pMousePoint->m_bShowName = false;
8800 pMousePoint->SetShowWaypointRangeRings(false);
8801
8802 m_pMeasureRoute->AddPoint(pMousePoint);
8803
8804 m_prev_rlat = m_cursor_lat;
8805 m_prev_rlon = m_cursor_lon;
8806 m_prev_pMousePoint = pMousePoint;
8807 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8808
8809 m_nMeasureState++;
8810 gFrame->RefreshAllCanvas();
8811 ret = true;
8812 }
8813
8814 else {
8815 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8816 }
8817 } // !g_btouch
8818 else { // g_btouch
8819 m_last_touch_down_pos = event.GetPosition();
8820
8821 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8822 // if near screen edge, pan with injection
8823 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8824 // return;
8825 // }
8826 }
8827 }
8828
8829 if (ret) return true;
8830 }
8831
8832 if (event.Dragging()) {
8833 // in touch screen mode ensure the finger/cursor is on the selected point's
8834 // radius to allow dragging
8835 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8836 if (g_btouch) {
8837 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8838 SelectItem *pFind = NULL;
8839 SelectableItemList SelList = pSelect->FindSelectionList(
8840 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8841 for (SelectItem *pFind : SelList) {
8842 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8843 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8844 }
8845 }
8846
8847 // Check for use of dragHandle
8848 if (m_pRoutePointEditTarget &&
8849 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8850 SelectItem *pFind = NULL;
8851 SelectableItemList SelList = pSelect->FindSelectionList(
8852 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8853 for (SelectItem *pFind : SelList) {
8854 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8855 if (m_pRoutePointEditTarget == frp) {
8856 m_bIsInRadius = true;
8857 break;
8858 }
8859 }
8860
8861 if (!m_dragoffsetSet) {
8862 RoutePointGui(*m_pRoutePointEditTarget)
8863 .PresetDragOffset(this, mouse_x, mouse_y);
8864 m_dragoffsetSet = true;
8865 }
8866 }
8867 }
8868
8869 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8870 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8871
8872 if (NULL == g_pMarkInfoDialog) {
8873 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8874 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8875 DraggingAllowed = false;
8876
8877 if (m_pRoutePointEditTarget &&
8878 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8879 DraggingAllowed = false;
8880
8881 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8882
8883 if (DraggingAllowed) {
8884 if (!undo->InUndoableAction()) {
8885 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8886 Undo_NeedsCopy, m_pFoundPoint);
8887 }
8888
8889 // Get the update rectangle for the union of the un-edited routes
8890 wxRect pre_rect;
8891
8892 if (!g_bopengl && m_pEditRouteArray) {
8893 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8894 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8895 // Need to validate route pointer
8896 // Route may be gone due to drgging close to ownship with
8897 // "Delete On Arrival" state set, as in the case of
8898 // navigating to an isolated waypoint on a temporary route
8899 if (g_pRouteMan->IsRouteValid(pr)) {
8900 wxRect route_rect;
8901 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8902 pre_rect.Union(route_rect);
8903 }
8904 }
8905 }
8906
8907 double new_cursor_lat = m_cursor_lat;
8908 double new_cursor_lon = m_cursor_lon;
8909
8910 if (CheckEdgePan(x, y, true, 5, 2))
8911 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8912
8913 // update the point itself
8914 if (g_btouch) {
8915 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8916 // new_cursor_lat, new_cursor_lon);
8917 RoutePointGui(*m_pRoutePointEditTarget)
8918 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8919 // update the Drag Handle entry in the pSelect list
8920 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8921 m_pRoutePointEditTarget,
8922 SELTYPE_DRAGHANDLE);
8923 m_pFoundPoint->m_slat =
8924 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8925 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8926 } else {
8927 m_pRoutePointEditTarget->m_lat =
8928 new_cursor_lat; // update the RoutePoint entry
8929 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8930 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8931 m_pFoundPoint->m_slat =
8932 new_cursor_lat; // update the SelectList entry
8933 m_pFoundPoint->m_slon = new_cursor_lon;
8934 }
8935
8936 // Update the MarkProperties Dialog, if currently shown
8937 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8938 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8939 g_pMarkInfoDialog->UpdateProperties(true);
8940 }
8941
8942 if (g_bopengl) {
8943 // InvalidateGL();
8944 Refresh(false);
8945 } else {
8946 // Get the update rectangle for the edited route
8947 wxRect post_rect;
8948
8949 if (m_pEditRouteArray) {
8950 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8951 ir++) {
8952 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8953 if (g_pRouteMan->IsRouteValid(pr)) {
8954 wxRect route_rect;
8955 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8956 post_rect.Union(route_rect);
8957 }
8958 }
8959 }
8960
8961 // Invalidate the union region
8962 pre_rect.Union(post_rect);
8963 RefreshRect(pre_rect, false);
8964 }
8965 gFrame->RefreshCanvasOther(this);
8966 m_bRoutePoinDragging = true;
8967 }
8968 ret = true;
8969 } // if Route Editing
8970
8971 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8972 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8973
8974 if (NULL == g_pMarkInfoDialog) {
8975 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8976 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8977 DraggingAllowed = false;
8978
8979 if (m_pRoutePointEditTarget &&
8980 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8981 DraggingAllowed = false;
8982
8983 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8984
8985 if (DraggingAllowed) {
8986 if (!undo->InUndoableAction()) {
8987 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8988 Undo_NeedsCopy, m_pFoundPoint);
8989 }
8990
8991 // The mark may be an anchorwatch
8992 double lpp1 = 0.;
8993 double lpp2 = 0.;
8994 double lppmax;
8995
8996 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8997 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8998 }
8999 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
9000 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9001 }
9002 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9003
9004 // Get the update rectangle for the un-edited mark
9005 wxRect pre_rect;
9006 if (!g_bopengl) {
9007 RoutePointGui(*m_pRoutePointEditTarget)
9008 .CalculateDCRect(m_dc_route, this, &pre_rect);
9009 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9010 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9011 (int)(lppmax - (pre_rect.height / 2)));
9012 }
9013
9014 // update the point itself
9015 if (g_btouch) {
9016 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9017 // m_cursor_lat, m_cursor_lon);
9018 RoutePointGui(*m_pRoutePointEditTarget)
9019 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9020 // update the Drag Handle entry in the pSelect list
9021 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9022 m_pRoutePointEditTarget,
9023 SELTYPE_DRAGHANDLE);
9024 m_pFoundPoint->m_slat =
9025 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9026 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9027 } else {
9028 m_pRoutePointEditTarget->m_lat =
9029 m_cursor_lat; // update the RoutePoint entry
9030 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9031 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9032 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9033 m_pFoundPoint->m_slon = m_cursor_lon;
9034 }
9035
9036 // Update the MarkProperties Dialog, if currently shown
9037 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9038 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9039 g_pMarkInfoDialog->UpdateProperties(true);
9040 }
9041
9042 // Invalidate the union region
9043 if (g_bopengl) {
9044 if (!g_btouch) InvalidateGL();
9045 Refresh(false);
9046 } else {
9047 // Get the update rectangle for the edited mark
9048 wxRect post_rect;
9049 RoutePointGui(*m_pRoutePointEditTarget)
9050 .CalculateDCRect(m_dc_route, this, &post_rect);
9051 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9052 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9053 (int)(lppmax - (post_rect.height / 2)));
9054
9055 // Invalidate the union region
9056 pre_rect.Union(post_rect);
9057 RefreshRect(pre_rect, false);
9058 }
9059 gFrame->RefreshCanvasOther(this);
9060 m_bRoutePoinDragging = true;
9061 }
9062 ret = g_btouch ? m_bRoutePoinDragging : true;
9063 }
9064
9065 if (ret) return true;
9066 } // dragging
9067
9068 if (event.LeftUp()) {
9069 bool b_startedit_route = false;
9070 m_dragoffsetSet = false;
9071
9072 if (g_btouch) {
9073 m_bChartDragging = false;
9074 m_bIsInRadius = false;
9075
9076 if (m_routeState) // creating route?
9077 {
9078 if (m_ignore_next_leftup) {
9079 m_ignore_next_leftup = false;
9080 return false;
9081 }
9082
9083 if (m_bedge_pan) {
9084 m_bedge_pan = false;
9085 return false;
9086 }
9087
9088 double rlat, rlon;
9089 bool appending = false;
9090 bool inserting = false;
9091 Route *tail = 0;
9092
9093 rlat = m_cursor_lat;
9094 rlon = m_cursor_lon;
9095
9096 if (m_pRoutePointEditTarget) {
9097 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9098 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9099 if (!g_bopengl) {
9100 wxRect wp_rect;
9101 RoutePointGui(*m_pRoutePointEditTarget)
9102 .CalculateDCRect(m_dc_route, this, &wp_rect);
9103 RefreshRect(wp_rect, true);
9104 }
9105 m_pRoutePointEditTarget = NULL;
9106 }
9107 m_bRouteEditing = true;
9108
9109 if (m_routeState == 1) {
9110 m_pMouseRoute = new Route();
9111 m_pMouseRoute->SetHiLite(50);
9112 pRouteList->push_back(m_pMouseRoute);
9113 r_rband.x = x;
9114 r_rband.y = y;
9115 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9116 }
9117
9118 // Check to see if there is a nearby point which may be reused
9119 RoutePoint *pMousePoint = NULL;
9120
9121 // Calculate meaningful SelectRadius
9122 double nearby_radius_meters =
9123 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9124
9125 RoutePoint *pNearbyPoint =
9126 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9127 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9128 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9129 int dlg_return;
9130#ifndef __WXOSX__
9131 m_FinishRouteOnKillFocus =
9132 false; // Avoid route finish on focus change for message dialog
9133 dlg_return = OCPNMessageBox(
9134 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9135 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9136 m_FinishRouteOnKillFocus = true;
9137#else
9138 dlg_return = wxID_YES;
9139#endif
9140 if (dlg_return == wxID_YES) {
9141 pMousePoint = pNearbyPoint;
9142
9143 // Using existing waypoint, so nothing to delete for undo.
9144 if (m_routeState > 1)
9145 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9146 Undo_HasParent, NULL);
9147 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9148
9149 bool procede = false;
9150 if (tail) {
9151 procede = true;
9152 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9153 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9154 procede = false;
9155 }
9156
9157 if (procede) {
9158 int dlg_return;
9159 m_FinishRouteOnKillFocus = false;
9160 if (m_routeState == 1) { // first point in new route, preceeding
9161 // route to be added? touch case
9162
9163 wxString dmsg =
9164 _("Insert first part of this route in the new route?");
9165 if (tail->GetIndexOf(pMousePoint) ==
9166 tail->GetnPoints()) // Starting on last point of another
9167 // route?
9168 dmsg = _("Insert this route in the new route?");
9169
9170 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9171 dlg_return =
9172 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9173 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9174 m_FinishRouteOnKillFocus = true;
9175
9176 if (dlg_return == wxID_YES) {
9177 inserting = true; // part of the other route will be
9178 // preceeding the new route
9179 }
9180 }
9181 } else {
9182 wxString dmsg =
9183 _("Append last part of this route to the new route?");
9184 if (tail->GetIndexOf(pMousePoint) == 1)
9185 dmsg = _(
9186 "Append this route to the new route?"); // Picking the
9187 // first point of
9188 // another route?
9189
9190 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9191 dlg_return =
9192 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9193 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9194 m_FinishRouteOnKillFocus = true;
9195
9196 if (dlg_return == wxID_YES) {
9197 appending = true; // part of the other route will be
9198 // appended to the new route
9199 }
9200 }
9201 }
9202 }
9203
9204 // check all other routes to see if this point appears in any other
9205 // route If it appears in NO other route, then it should e
9206 // considered an isolated mark
9207 if (!FindRouteContainingWaypoint(pMousePoint))
9208 pMousePoint->SetShared(true);
9209 }
9210 }
9211
9212 if (NULL == pMousePoint) { // need a new point
9213 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9214 "", wxEmptyString);
9215 pMousePoint->SetNameShown(false);
9216
9217 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9218
9219 if (m_routeState > 1)
9220 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9221 Undo_IsOrphanded, NULL);
9222 }
9223
9224 if (m_routeState == 1) {
9225 // First point in the route.
9226 m_pMouseRoute->AddPoint(pMousePoint);
9227 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9228
9229 } else {
9230 if (m_pMouseRoute->m_NextLegGreatCircle) {
9231 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9232 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9233 &rhumbBearing, &rhumbDist);
9234 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9235 &gcDist, &gcBearing, NULL);
9236 double gcDistNM = gcDist / 1852.0;
9237
9238 // Empirically found expression to get reasonable route segments.
9239 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9240 pow(rhumbDist - gcDistNM - 1, 0.5);
9241
9242 wxString msg;
9243 msg << _("For this leg the Great Circle route is ")
9244 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9245 << _(" shorter than rhumbline.\n\n")
9246 << _("Would you like include the Great Circle routing points "
9247 "for this leg?");
9248
9249#ifndef __WXOSX__
9250 m_FinishRouteOnKillFocus = false;
9251 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9252 wxYES_NO | wxNO_DEFAULT);
9253 m_FinishRouteOnKillFocus = true;
9254#else
9255 int answer = wxID_NO;
9256#endif
9257
9258 if (answer == wxID_YES) {
9259 RoutePoint *gcPoint;
9260 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9261 wxRealPoint gcCoord;
9262
9263 for (int i = 1; i <= segmentCount; i++) {
9264 double fraction = (double)i * (1.0 / (double)segmentCount);
9265 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9266 gcDist * fraction, gcBearing,
9267 &gcCoord.x, &gcCoord.y, NULL);
9268
9269 if (i < segmentCount) {
9270 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9271 wxEmptyString);
9272 gcPoint->SetNameShown(false);
9273 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9274 gcPoint);
9275 } else {
9276 gcPoint = pMousePoint; // Last point, previously exsisting!
9277 }
9278
9279 m_pMouseRoute->AddPoint(gcPoint);
9280 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9281
9282 pSelect->AddSelectableRouteSegment(
9283 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9284 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9285 prevGcPoint = gcPoint;
9286 }
9287
9288 undo->CancelUndoableAction(true);
9289
9290 } else {
9291 m_pMouseRoute->AddPoint(pMousePoint);
9292 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9293 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9294 rlon, m_prev_pMousePoint,
9295 pMousePoint, m_pMouseRoute);
9296 undo->AfterUndoableAction(m_pMouseRoute);
9297 }
9298 } else {
9299 // Ordinary rhumblinesegment.
9300 m_pMouseRoute->AddPoint(pMousePoint);
9301 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9302
9303 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9304 rlon, m_prev_pMousePoint,
9305 pMousePoint, m_pMouseRoute);
9306 undo->AfterUndoableAction(m_pMouseRoute);
9307 }
9308 }
9309
9310 m_prev_rlat = rlat;
9311 m_prev_rlon = rlon;
9312 m_prev_pMousePoint = pMousePoint;
9313 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9314
9315 m_routeState++;
9316
9317 if (appending ||
9318 inserting) { // Appending a route or making a new route
9319 int connect = tail->GetIndexOf(pMousePoint);
9320 if (connect == 1) {
9321 inserting = false; // there is nothing to insert
9322 appending = true; // so append
9323 }
9324 int length = tail->GetnPoints();
9325
9326 int i;
9327 int start, stop;
9328 if (appending) {
9329 start = connect + 1;
9330 stop = length;
9331 } else { // inserting
9332 start = 1;
9333 stop = connect;
9334 m_pMouseRoute->RemovePoint(
9335 m_pMouseRoute
9336 ->GetLastPoint()); // Remove the first and only point
9337 }
9338 for (i = start; i <= stop; i++) {
9339 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9340 if (m_pMouseRoute)
9341 m_pMouseRoute->m_lastMousePointIndex =
9342 m_pMouseRoute->GetnPoints();
9343 m_routeState++;
9344 gFrame->RefreshAllCanvas();
9345 ret = true;
9346 }
9347 m_prev_rlat =
9348 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9349 m_prev_rlon =
9350 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9351 m_pMouseRoute->FinalizeForRendering();
9352 }
9353
9354 Refresh(true);
9355 ret = true;
9356 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9357 {
9358 if (m_bedge_pan) {
9359 m_bedge_pan = false;
9360 return false;
9361 }
9362
9363 if (m_ignore_next_leftup) {
9364 m_ignore_next_leftup = false;
9365 return false;
9366 }
9367
9368 if (m_nMeasureState == 1) {
9369 m_pMeasureRoute = new Route();
9370 pRouteList->push_back(m_pMeasureRoute);
9371 r_rband.x = x;
9372 r_rband.y = y;
9373 }
9374
9375 if (m_pMeasureRoute) {
9376 RoutePoint *pMousePoint =
9377 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9378 wxEmptyString, wxEmptyString);
9379 pMousePoint->m_bShowName = false;
9380
9381 m_pMeasureRoute->AddPoint(pMousePoint);
9382
9383 m_prev_rlat = m_cursor_lat;
9384 m_prev_rlon = m_cursor_lon;
9385 m_prev_pMousePoint = pMousePoint;
9386 m_pMeasureRoute->m_lastMousePointIndex =
9387 m_pMeasureRoute->GetnPoints();
9388
9389 m_nMeasureState++;
9390 } else {
9391 CancelMeasureRoute();
9392 }
9393
9394 Refresh(true);
9395 ret = true;
9396 } else {
9397 bool bSelectAllowed = true;
9398 if (NULL == g_pMarkInfoDialog) {
9399 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9400 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9401 bSelectAllowed = false;
9402
9403 // Avoid accidental selection of routepoint if last touchdown started
9404 // a significant chart drag operation
9405 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9406 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9407 significant_drag) ||
9408 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9409 significant_drag)) {
9410 bSelectAllowed = false;
9411 }
9412
9413 /*if this left up happens at the end of a route point dragging and if
9414 the cursor/thumb is on the draghandle icon, not on the point iself a new
9415 selection will select nothing and the drag will never be ended, so the
9416 legs around this point never selectable. At this step we don't need a
9417 new selection, just keep the previoulsly selected and dragged point */
9418 if (m_bRoutePoinDragging) bSelectAllowed = false;
9419
9420 if (bSelectAllowed) {
9421 bool b_was_editing_mark = m_bMarkEditing;
9422 bool b_was_editing_route = m_bRouteEditing;
9423 FindRoutePointsAtCursor(SelectRadius,
9424 true); // Possibly selecting a point in a
9425 // route for later dragging
9426
9427 /*route and a mark points in layer can't be dragged so should't be
9428 * selected and no draghandle icon*/
9429 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9430 m_pRoutePointEditTarget = NULL;
9431
9432 if (!b_was_editing_route) {
9433 if (m_pEditRouteArray) {
9434 b_startedit_route = true;
9435
9436 // Hide the track and route rollover during route point edit, not
9437 // needed, and may be confusing
9438 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9439 m_pTrackRolloverWin->IsActive(false);
9440 }
9441 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9442 m_pRouteRolloverWin->IsActive(false);
9443 }
9444
9445 wxRect pre_rect;
9446 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9447 ir++) {
9448 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9449 // Need to validate route pointer
9450 // Route may be gone due to drgging close to ownship with
9451 // "Delete On Arrival" state set, as in the case of
9452 // navigating to an isolated waypoint on a temporary route
9453 if (g_pRouteMan->IsRouteValid(pr)) {
9454 // pr->SetHiLite(50);
9455 wxRect route_rect;
9456 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9457 pre_rect.Union(route_rect);
9458 }
9459 }
9460 RefreshRect(pre_rect, true);
9461 }
9462 } else {
9463 b_startedit_route = false;
9464 }
9465
9466 // Mark editing in touch mode, left-up event.
9467 if (m_pRoutePointEditTarget) {
9468 if (b_was_editing_mark ||
9469 b_was_editing_route) { // kill previous hilight
9470 if (m_lastRoutePointEditTarget) {
9471 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9472 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9473 RoutePointGui(*m_lastRoutePointEditTarget)
9474 .EnableDragHandle(false);
9475 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9476 SELTYPE_DRAGHANDLE);
9477 }
9478 }
9479
9480 if (m_pRoutePointEditTarget) {
9481 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9482 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9483 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9484 wxPoint2DDouble dragHandlePoint =
9485 RoutePointGui(*m_pRoutePointEditTarget)
9486 .GetDragHandlePoint(this);
9487 pSelect->AddSelectablePoint(
9488 dragHandlePoint.m_y, dragHandlePoint.m_x,
9489 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9490 }
9491 } else { // Deselect everything
9492 if (m_lastRoutePointEditTarget) {
9493 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9494 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9495 RoutePointGui(*m_lastRoutePointEditTarget)
9496 .EnableDragHandle(false);
9497 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9498 SELTYPE_DRAGHANDLE);
9499
9500 // Clear any routes being edited, probably orphans
9501 wxArrayPtrVoid *lastEditRouteArray =
9503 m_lastRoutePointEditTarget);
9504 if (lastEditRouteArray) {
9505 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9506 ir++) {
9507 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9508 if (g_pRouteMan->IsRouteValid(pr)) {
9509 pr->m_bIsBeingEdited = false;
9510 }
9511 }
9512 delete lastEditRouteArray;
9513 }
9514 }
9515 }
9516
9517 // Do the refresh
9518
9519 if (g_bopengl) {
9520 InvalidateGL();
9521 Refresh(false);
9522 } else {
9523 if (m_lastRoutePointEditTarget) {
9524 wxRect wp_rect;
9525 RoutePointGui(*m_lastRoutePointEditTarget)
9526 .CalculateDCRect(m_dc_route, this, &wp_rect);
9527 RefreshRect(wp_rect, true);
9528 }
9529
9530 if (m_pRoutePointEditTarget) {
9531 wxRect wp_rect;
9532 RoutePointGui(*m_pRoutePointEditTarget)
9533 .CalculateDCRect(m_dc_route, this, &wp_rect);
9534 RefreshRect(wp_rect, true);
9535 }
9536 }
9537 }
9538 } // bSelectAllowed
9539
9540 // Check to see if there is a route or AIS target under the cursor
9541 // If so, start the rollover timer which creates the popup
9542 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9543 bool b_start_rollover = false;
9544 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9545 SelectItem *pFind = pSelectAIS->FindSelection(
9546 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9547 if (pFind) b_start_rollover = true;
9548 }
9549
9550 if (!b_start_rollover && !b_startedit_route) {
9551 SelectableItemList SelList = pSelect->FindSelectionList(
9552 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9553 for (SelectItem *pFindSel : SelList) {
9554 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9555 if (pr && pr->IsVisible()) {
9556 b_start_rollover = true;
9557 break;
9558 }
9559 } // while
9560 }
9561
9562 if (!b_start_rollover && !b_startedit_route) {
9563 SelectableItemList SelList = pSelect->FindSelectionList(
9564 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9565 for (SelectItem *pFindSel : SelList) {
9566 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9567 if (tr && tr->IsVisible()) {
9568 b_start_rollover = true;
9569 break;
9570 }
9571 } // while
9572 }
9573
9574 if (b_start_rollover)
9575 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9576 wxTIMER_ONE_SHOT);
9577 Route *tail = 0;
9578 Route *current = 0;
9579 bool appending = false;
9580 bool inserting = false;
9581 int connect = 0;
9582 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9583 // drag
9584 if (m_pRoutePointEditTarget) {
9585 // Check to see if there is a nearby point which may replace the
9586 // dragged one
9587 RoutePoint *pMousePoint = NULL;
9588
9589 int index_last;
9590 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9591 double nearby_radius_meters =
9592 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9593 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9594 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9595 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9596 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9597 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9598 bool duplicate =
9599 false; // ensure we won't create duplicate point in routes
9600 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9601 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9602 ir++) {
9603 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9604 if (pr && pr->pRoutePointList) {
9605 auto *list = pr->pRoutePointList;
9606 auto pos =
9607 std::find(list->begin(), list->end(), pNearbyPoint);
9608 if (pos != list->end()) {
9609 duplicate = true;
9610 break;
9611 }
9612 }
9613 }
9614 }
9615
9616 // Special case:
9617 // Allow "re-use" of a route's waypoints iff it is a simple
9618 // isolated route. This allows, for instance, creation of a closed
9619 // polygon route
9620 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9621
9622 if (!duplicate) {
9623 int dlg_return;
9624 dlg_return =
9625 OCPNMessageBox(this,
9626 _("Replace this RoutePoint by the nearby "
9627 "Waypoint?"),
9628 _("OpenCPN RoutePoint change"),
9629 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9630 if (dlg_return == wxID_YES) {
9631 /*double confirmation if the dragged point has been manually
9632 * created which can be important and could be deleted
9633 * unintentionally*/
9634
9635 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9636 pNearbyPoint);
9637 current =
9638 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9639
9640 if (tail && current && (tail != current)) {
9641 int dlg_return1;
9642 connect = tail->GetIndexOf(pNearbyPoint);
9643 int index_current_route =
9644 current->GetIndexOf(m_pRoutePointEditTarget);
9645 index_last = current->GetIndexOf(current->GetLastPoint());
9646 dlg_return1 = wxID_NO;
9647 if (index_last ==
9648 index_current_route) { // we are dragging the last
9649 // point of the route
9650 if (connect != tail->GetnPoints()) { // anything to do?
9651
9652 wxString dmsg(
9653 _("Last part of route to be appended to dragged "
9654 "route?"));
9655 if (connect == 1)
9656 dmsg =
9657 _("Full route to be appended to dragged route?");
9658
9659 dlg_return1 = OCPNMessageBox(
9660 this, dmsg, _("OpenCPN Route Create"),
9661 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9662 if (dlg_return1 == wxID_YES) {
9663 appending = true;
9664 }
9665 }
9666 } else if (index_current_route ==
9667 1) { // dragging the first point of the route
9668 if (connect != 1) { // anything to do?
9669
9670 wxString dmsg(
9671 _("First part of route to be inserted into dragged "
9672 "route?"));
9673 if (connect == tail->GetnPoints())
9674 dmsg = _(
9675 "Full route to be inserted into dragged route?");
9676
9677 dlg_return1 = OCPNMessageBox(
9678 this, dmsg, _("OpenCPN Route Create"),
9679 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9680 if (dlg_return1 == wxID_YES) {
9681 inserting = true;
9682 }
9683 }
9684 }
9685 }
9686
9687 if (m_pRoutePointEditTarget->IsShared()) {
9688 // dlg_return = wxID_NO;
9689 dlg_return = OCPNMessageBox(
9690 this,
9691 _("Do you really want to delete and replace this "
9692 "WayPoint") +
9693 "\n" + _("which has been created manually?"),
9694 ("OpenCPN RoutePoint warning"),
9695 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9696 }
9697 }
9698 if (dlg_return == wxID_YES) {
9699 pMousePoint = pNearbyPoint;
9700 if (pMousePoint->m_bIsolatedMark) {
9701 pMousePoint->SetShared(true);
9702 }
9703 pMousePoint->m_bIsolatedMark =
9704 false; // definitely no longer isolated
9705 pMousePoint->m_bIsInRoute = true;
9706 }
9707 }
9708 }
9709 }
9710 if (!pMousePoint)
9711 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9712
9713 if (m_pEditRouteArray) {
9714 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9715 ir++) {
9716 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9717 if (g_pRouteMan->IsRouteValid(pr)) {
9718 if (pMousePoint) { // remove the dragged point and insert the
9719 // nearby
9720 auto *list = pr->pRoutePointList;
9721 auto pos = std::find(list->begin(), list->end(),
9722 m_pRoutePointEditTarget);
9723
9724 pSelect->DeleteAllSelectableRoutePoints(pr);
9725 pSelect->DeleteAllSelectableRouteSegments(pr);
9726
9727 pr->pRoutePointList->insert(pos, pMousePoint);
9728 pos = std::find(list->begin(), list->end(),
9729 m_pRoutePointEditTarget);
9730 pr->pRoutePointList->erase(pos);
9731
9732 pSelect->AddAllSelectableRouteSegments(pr);
9733 pSelect->AddAllSelectableRoutePoints(pr);
9734 }
9735 pr->FinalizeForRendering();
9736 pr->UpdateSegmentDistances();
9737 if (m_bRoutePoinDragging) {
9738 // pConfig->UpdateRoute(pr);
9739 NavObj_dB::GetInstance().UpdateRoute(pr);
9740 }
9741 }
9742 }
9743 }
9744
9745 // Update the RouteProperties Dialog, if currently shown
9746 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9747 if (m_pEditRouteArray) {
9748 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9749 ir++) {
9750 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9751 if (g_pRouteMan->IsRouteValid(pr)) {
9752 if (pRoutePropDialog->GetRoute() == pr) {
9753 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9754 }
9755 /* cannot edit track points anyway
9756 else if ( ( NULL !=
9757 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9758 pTrackPropDialog->m_pTrack == pr ) {
9759 pTrackPropDialog->SetTrackAndUpdate(
9760 pr );
9761 }
9762 */
9763 }
9764 }
9765 }
9766 }
9767 if (pMousePoint) { // clear all about the dragged point
9768 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9769 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9770 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9771 // Hide mark properties dialog if open on the replaced point
9772 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9773 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9774 g_pMarkInfoDialog->Hide();
9775
9776 delete m_pRoutePointEditTarget;
9777 m_lastRoutePointEditTarget = NULL;
9778 m_pRoutePointEditTarget = NULL;
9779 undo->AfterUndoableAction(pMousePoint);
9780 undo->InvalidateUndo();
9781 }
9782 }
9783 }
9784
9785 else if (m_bMarkEditing) { // End of way point drag
9786 if (m_pRoutePointEditTarget)
9787 if (m_bRoutePoinDragging) {
9788 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9789 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9790 }
9791 }
9792
9793 if (m_pRoutePointEditTarget)
9794 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9795
9796 if (!m_pRoutePointEditTarget) {
9797 delete m_pEditRouteArray;
9798 m_pEditRouteArray = NULL;
9799 m_bRouteEditing = false;
9800 }
9801 m_bRoutePoinDragging = false;
9802
9803 if (appending) { // Appending to the route of which the last point is
9804 // dragged onto another route
9805
9806 // copy tail from connect until length to end of current after dragging
9807
9808 int length = tail->GetnPoints();
9809 for (int i = connect + 1; i <= length; i++) {
9810 current->AddPointAndSegment(tail->GetPoint(i), false);
9811 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9812 m_routeState++;
9813 gFrame->RefreshAllCanvas();
9814 ret = true;
9815 }
9816 current->FinalizeForRendering();
9817 current->m_bIsBeingEdited = false;
9818 FinishRoute();
9819 g_pRouteMan->DeleteRoute(tail);
9820 }
9821 if (inserting) {
9822 pSelect->DeleteAllSelectableRoutePoints(current);
9823 pSelect->DeleteAllSelectableRouteSegments(current);
9824 for (int i = 1; i < connect; i++) { // numbering in the tail route
9825 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9826 }
9827 pSelect->AddAllSelectableRouteSegments(current);
9828 pSelect->AddAllSelectableRoutePoints(current);
9829 current->FinalizeForRendering();
9830 current->m_bIsBeingEdited = false;
9831 g_pRouteMan->DeleteRoute(tail);
9832 }
9833
9834 // Update the RouteProperties Dialog, if currently shown
9835 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9836 if (m_pEditRouteArray) {
9837 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9838 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9839 if (g_pRouteMan->IsRouteValid(pr)) {
9840 if (pRoutePropDialog->GetRoute() == pr) {
9841 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9842 }
9843 }
9844 }
9845 }
9846 }
9847
9848 } // g_btouch
9849
9850 else { // !g_btouch
9851 if (m_bRouteEditing) { // End of RoutePoint drag
9852 Route *tail = 0;
9853 Route *current = 0;
9854 bool appending = false;
9855 bool inserting = false;
9856 int connect = 0;
9857 int index_last;
9858 if (m_pRoutePointEditTarget) {
9859 m_pRoutePointEditTarget->m_bBlink = false;
9860 // Check to see if there is a nearby point which may replace the
9861 // dragged one
9862 RoutePoint *pMousePoint = NULL;
9863 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9864 double nearby_radius_meters =
9865 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9866 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9867 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9868 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9869 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9870 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9871 bool duplicate = false; // don't create duplicate point in routes
9872 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9873 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9874 ir++) {
9875 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9876 if (pr && pr->pRoutePointList) {
9877 auto *list = pr->pRoutePointList;
9878 auto pos =
9879 std::find(list->begin(), list->end(), pNearbyPoint);
9880 if (pos != list->end()) {
9881 duplicate = true;
9882 break;
9883 }
9884 }
9885 }
9886 }
9887
9888 // Special case:
9889 // Allow "re-use" of a route's waypoints iff it is a simple
9890 // isolated route. This allows, for instance, creation of a closed
9891 // polygon route
9892 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9893
9894 if (!duplicate) {
9895 int dlg_return;
9896 dlg_return =
9897 OCPNMessageBox(this,
9898 _("Replace this RoutePoint by the nearby "
9899 "Waypoint?"),
9900 _("OpenCPN RoutePoint change"),
9901 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9902 if (dlg_return == wxID_YES) {
9903 /*double confirmation if the dragged point has been manually
9904 * created which can be important and could be deleted
9905 * unintentionally*/
9906 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9907 pNearbyPoint);
9908 current =
9909 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9910
9911 if (tail && current && (tail != current)) {
9912 int dlg_return1;
9913 connect = tail->GetIndexOf(pNearbyPoint);
9914 int index_current_route =
9915 current->GetIndexOf(m_pRoutePointEditTarget);
9916 index_last = current->GetIndexOf(current->GetLastPoint());
9917 dlg_return1 = wxID_NO;
9918 if (index_last ==
9919 index_current_route) { // we are dragging the last
9920 // point of the route
9921 if (connect != tail->GetnPoints()) { // anything to do?
9922
9923 wxString dmsg(
9924 _("Last part of route to be appended to dragged "
9925 "route?"));
9926 if (connect == 1)
9927 dmsg =
9928 _("Full route to be appended to dragged route?");
9929
9930 dlg_return1 = OCPNMessageBox(
9931 this, dmsg, _("OpenCPN Route Create"),
9932 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9933 if (dlg_return1 == wxID_YES) {
9934 appending = true;
9935 }
9936 }
9937 } else if (index_current_route ==
9938 1) { // dragging the first point of the route
9939 if (connect != 1) { // anything to do?
9940
9941 wxString dmsg(
9942 _("First part of route to be inserted into dragged "
9943 "route?"));
9944 if (connect == tail->GetnPoints())
9945 dmsg = _(
9946 "Full route to be inserted into dragged route?");
9947
9948 dlg_return1 = OCPNMessageBox(
9949 this, dmsg, _("OpenCPN Route Create"),
9950 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9951 if (dlg_return1 == wxID_YES) {
9952 inserting = true;
9953 }
9954 }
9955 }
9956 }
9957
9958 if (m_pRoutePointEditTarget->IsShared()) {
9959 dlg_return = wxID_NO;
9960 dlg_return = OCPNMessageBox(
9961 this,
9962 _("Do you really want to delete and replace this "
9963 "WayPoint") +
9964 "\n" + _("which has been created manually?"),
9965 ("OpenCPN RoutePoint warning"),
9966 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9967 }
9968 }
9969 if (dlg_return == wxID_YES) {
9970 pMousePoint = pNearbyPoint;
9971 if (pMousePoint->m_bIsolatedMark) {
9972 pMousePoint->SetShared(true);
9973 }
9974 pMousePoint->m_bIsolatedMark =
9975 false; // definitely no longer isolated
9976 pMousePoint->m_bIsInRoute = true;
9977 }
9978 }
9979 }
9980 }
9981 if (!pMousePoint)
9982 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9983
9984 if (m_pEditRouteArray) {
9985 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9986 ir++) {
9987 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9988 if (g_pRouteMan->IsRouteValid(pr)) {
9989 if (pMousePoint) { // replace dragged point by nearby one
9990 auto *list = pr->pRoutePointList;
9991 auto pos = std::find(list->begin(), list->end(),
9992 m_pRoutePointEditTarget);
9993
9994 pSelect->DeleteAllSelectableRoutePoints(pr);
9995 pSelect->DeleteAllSelectableRouteSegments(pr);
9996
9997 pr->pRoutePointList->insert(pos, pMousePoint);
9998 pos = std::find(list->begin(), list->end(),
9999 m_pRoutePointEditTarget);
10000 if (pos != list->end()) list->erase(pos);
10001 // pr->pRoutePointList->erase(pos + 1);
10002
10003 pSelect->AddAllSelectableRouteSegments(pr);
10004 pSelect->AddAllSelectableRoutePoints(pr);
10005 }
10006 pr->FinalizeForRendering();
10007 pr->UpdateSegmentDistances();
10008 pr->m_bIsBeingEdited = false;
10009
10010 if (m_bRoutePoinDragging) {
10011 // Special case optimization.
10012 // Dragging a single point of a route
10013 // without any point additions or re-ordering
10014 if (!pMousePoint)
10015 NavObj_dB::GetInstance().UpdateRoutePoint(
10016 m_pRoutePointEditTarget);
10017 else
10018 NavObj_dB::GetInstance().UpdateRoute(pr);
10019 }
10020 pr->SetHiLite(0);
10021 }
10022 }
10023 Refresh(false);
10024 }
10025
10026 if (appending) {
10027 // copy tail from connect until length to end of current after
10028 // dragging
10029
10030 int length = tail->GetnPoints();
10031 for (int i = connect + 1; i <= length; i++) {
10032 current->AddPointAndSegment(tail->GetPoint(i), false);
10033 if (current)
10034 current->m_lastMousePointIndex = current->GetnPoints();
10035 m_routeState++;
10036 gFrame->RefreshAllCanvas();
10037 ret = true;
10038 }
10039 current->FinalizeForRendering();
10040 current->m_bIsBeingEdited = false;
10041 FinishRoute();
10042 g_pRouteMan->DeleteRoute(tail);
10043 }
10044 if (inserting) {
10045 pSelect->DeleteAllSelectableRoutePoints(current);
10046 pSelect->DeleteAllSelectableRouteSegments(current);
10047 for (int i = 1; i < connect; i++) { // numbering in the tail route
10048 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10049 }
10050 pSelect->AddAllSelectableRouteSegments(current);
10051 pSelect->AddAllSelectableRoutePoints(current);
10052 current->FinalizeForRendering();
10053 current->m_bIsBeingEdited = false;
10054 g_pRouteMan->DeleteRoute(tail);
10055 }
10056
10057 // Update the RouteProperties Dialog, if currently shown
10058 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10059 if (m_pEditRouteArray) {
10060 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10061 ir++) {
10062 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10063 if (g_pRouteMan->IsRouteValid(pr)) {
10064 if (pRoutePropDialog->GetRoute() == pr) {
10065 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10066 }
10067 }
10068 }
10069 }
10070 }
10071
10072 if (pMousePoint) {
10073 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10074 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10075 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10076 // Hide mark properties dialog if open on the replaced point
10077 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10078 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10079 g_pMarkInfoDialog->Hide();
10080
10081 delete m_pRoutePointEditTarget;
10082 m_lastRoutePointEditTarget = NULL;
10083 undo->AfterUndoableAction(pMousePoint);
10084 undo->InvalidateUndo();
10085 } else {
10086 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10087 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10088
10089 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10090 }
10091
10092 delete m_pEditRouteArray;
10093 m_pEditRouteArray = NULL;
10094 }
10095
10096 InvalidateGL();
10097 m_bRouteEditing = false;
10098 m_pRoutePointEditTarget = NULL;
10099
10100 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10101 ret = true;
10102 }
10103
10104 else if (m_bMarkEditing) { // end of Waypoint drag
10105 if (m_pRoutePointEditTarget) {
10106 if (m_bRoutePoinDragging) {
10107 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10108 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10109 }
10110 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10111 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10112 if (!g_bopengl) {
10113 wxRect wp_rect;
10114 RoutePointGui(*m_pRoutePointEditTarget)
10115 .CalculateDCRect(m_dc_route, this, &wp_rect);
10116 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10117 RefreshRect(wp_rect, true);
10118 }
10119 }
10120 m_pRoutePointEditTarget = NULL;
10121 m_bMarkEditing = false;
10122 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10123 ret = true;
10124 }
10125
10126 else if (leftIsDown) { // left click for chart center
10127 leftIsDown = false;
10128 ret = false;
10129
10130 if (!g_btouch) {
10131 if (!m_bChartDragging && !m_bMeasure_Active) {
10132 } else {
10133 m_bChartDragging = false;
10134 }
10135 }
10136 }
10137 m_bRoutePoinDragging = false;
10138 } // !btouch
10139
10140 if (ret) return true;
10141 } // left up
10142
10143 if (event.RightDown()) {
10144 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10145 last_drag.x = mx;
10146 last_drag.y = my;
10147
10148 if (g_btouch) {
10149 // if( m_pRoutePointEditTarget )
10150 // return false;
10151 }
10152
10153 ret = true;
10154 m_FinishRouteOnKillFocus = false;
10155 CallPopupMenu(mx, my);
10156 m_FinishRouteOnKillFocus = true;
10157 } // Right down
10158
10159 return ret;
10160}
10161
10162bool panleftIsDown;
10163bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10164 // Skip all mouse processing if shift is held.
10165 // This allows plugins to implement shift+drag behaviors.
10166 if (event.ShiftDown()) {
10167 return false;
10168 }
10169 int x, y;
10170 event.GetPosition(&x, &y);
10171
10172 x *= m_displayScale;
10173 y *= m_displayScale;
10174
10175 // Check for wheel rotation
10176 // ideally, should be just longer than the time between
10177 // processing accumulated mouse events from the event queue
10178 // as would happen during screen redraws.
10179 int wheel_dir = event.GetWheelRotation();
10180
10181 if (wheel_dir) {
10182 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10183 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10184
10185 double factor = g_mouse_zoom_sensitivity;
10186 if (wheel_dir < 0) factor = 1 / factor;
10187
10188 if (g_bsmoothpanzoom) {
10189 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10190 if (wheel_dir == m_last_wheel_dir) {
10191 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10192 // m_zoom_target /= factor;
10193 } else
10194 StopMovement();
10195 } else {
10196 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10197 m_wheelstopwatch.Start(0);
10198 // m_zoom_target = VPoint.chart_scale / factor;
10199 }
10200 }
10201
10202 m_last_wheel_dir = wheel_dir;
10203
10204 ZoomCanvas(factor, true, false);
10205 }
10206
10207 if (event.LeftDown()) {
10208 // Skip the first left click if it will cause a canvas focus shift
10209 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10210 return false;
10211 }
10212
10213 last_drag.x = x, last_drag.y = y;
10214 panleftIsDown = true;
10215 }
10216
10217 if (event.LeftUp()) {
10218 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10219 // seen here.
10220 panleftIsDown = false;
10221
10222 if (!g_btouch) {
10223 if (!m_bChartDragging && !m_bMeasure_Active) {
10224 switch (cursor_region) {
10225 case MID_RIGHT: {
10226 PanCanvas(100, 0);
10227 break;
10228 }
10229
10230 case MID_LEFT: {
10231 PanCanvas(-100, 0);
10232 break;
10233 }
10234
10235 case MID_TOP: {
10236 PanCanvas(0, 100);
10237 break;
10238 }
10239
10240 case MID_BOT: {
10241 PanCanvas(0, -100);
10242 break;
10243 }
10244
10245 case CENTER: {
10246 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10247 break;
10248 }
10249 }
10250 } else {
10251 m_bChartDragging = false;
10252 }
10253 }
10254 }
10255 }
10256
10257 if (event.Dragging() && event.LeftIsDown()) {
10258 /*
10259 * fixed dragging.
10260 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10261 * before the drag event. Hence, as there is no mouse down event, last_drag
10262 * is not reset before the drag. And that results in one single drag
10263 * session, meaning you cannot drag the map a few miles north, lift your
10264 * finger, and the go even further north. Instead, the map resets itself
10265 * always to the very first drag start (since there is not reset of
10266 * last_drag).
10267 *
10268 * Besides, should not left down and dragging be enough of a situation to
10269 * start a drag procedure?
10270 *
10271 * Anyways, guarded it to be active in touch situations only.
10272 */
10273 if (g_btouch && !m_inPinch) {
10274 struct timespec now;
10275 clock_gettime(CLOCK_MONOTONIC, &now);
10276 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10277
10278 bool trigger_hold = false;
10279 if (false == m_bChartDragging) {
10280 if (m_DragTrigger < 0) {
10281 // printf("\ntrigger1\n");
10282 m_DragTrigger = 0;
10283 m_DragTriggerStartTime = tnow;
10284 trigger_hold = true;
10285 } else {
10286 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10287 m_DragTrigger = -1; // Reset trigger
10288 // printf("trigger fired\n");
10289 }
10290 }
10291 }
10292 if (trigger_hold) return true;
10293
10294 if (false == m_bChartDragging) {
10295 // printf("starting drag\n");
10296 // Reset drag calculation members
10297 last_drag.x = x - 1, last_drag.y = y - 1;
10298 m_bChartDragging = true;
10299 m_chart_drag_total_time = 0;
10300 m_chart_drag_total_x = 0;
10301 m_chart_drag_total_y = 0;
10302 m_inertia_last_drag_x = x;
10303 m_inertia_last_drag_y = y;
10304 m_drag_vec_x.clear();
10305 m_drag_vec_y.clear();
10306 m_drag_vec_t.clear();
10307 m_last_drag_time = tnow;
10308 }
10309
10310 // Calculate and store drag dynamics.
10311 uint64_t delta_t = tnow - m_last_drag_time;
10312 double delta_tf = delta_t / 1e9;
10313
10314 m_chart_drag_total_time += delta_tf;
10315 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10316 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10317
10318 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10319 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10320 m_drag_vec_t.push_back(delta_tf);
10321
10322 m_inertia_last_drag_x = x;
10323 m_inertia_last_drag_y = y;
10324 m_last_drag_time = tnow;
10325
10326 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10327 m_bChartDragging = true;
10328 StartTimedMovement();
10329 m_pan_drag.x += last_drag.x - x;
10330 m_pan_drag.y += last_drag.y - y;
10331 last_drag.x = x, last_drag.y = y;
10332 }
10333 } else if (!g_btouch) {
10334 if ((last_drag.x != x) || (last_drag.y != y)) {
10335 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10336 // dragging on route create.
10337 // github #2994
10338 m_bChartDragging = true;
10339 StartTimedMovement();
10340 m_pan_drag.x += last_drag.x - x;
10341 m_pan_drag.y += last_drag.y - y;
10342 last_drag.x = x, last_drag.y = y;
10343 }
10344 }
10345 }
10346
10347 // Handle some special cases
10348 if (g_btouch) {
10349 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10350 // deactivate next LeftUp to ovoid creating an unexpected point
10351 m_ignore_next_leftup = true;
10352 m_DoubleClickTimer->Start();
10353 singleClickEventIsValid = false;
10354 }
10355 }
10356 }
10357
10358 return true;
10359}
10360
10361void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10362 if (MouseEventOverlayWindows(event)) return;
10363
10364 if (MouseEventSetup(event)) return; // handled, no further action required
10365
10366 bool nm = MouseEventProcessObjects(event);
10367 if (!nm) MouseEventProcessCanvas(event);
10368}
10369
10370void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10371 // Switch to the appropriate cursor on mouse movement
10372
10373 wxCursor *ptarget_cursor = pCursorArrow;
10374 if (!pPlugIn_Cursor) {
10375 ptarget_cursor = pCursorArrow;
10376 if ((!m_routeState) &&
10377 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10378 if (cursor_region == MID_RIGHT) {
10379 ptarget_cursor = pCursorRight;
10380 } else if (cursor_region == MID_LEFT) {
10381 ptarget_cursor = pCursorLeft;
10382 } else if (cursor_region == MID_TOP) {
10383 ptarget_cursor = pCursorDown;
10384 } else if (cursor_region == MID_BOT) {
10385 ptarget_cursor = pCursorUp;
10386 } else {
10387 ptarget_cursor = pCursorArrow;
10388 }
10389 } else if (m_bMeasure_Active ||
10390 m_routeState) // If Measure tool use Pencil Cursor
10391 ptarget_cursor = pCursorPencil;
10392 } else {
10393 ptarget_cursor = pPlugIn_Cursor;
10394 }
10395
10396 SetCursor(*ptarget_cursor);
10397}
10398
10399void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10400 SetCursor(*pCursorArrow);
10401}
10402
10403void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10404 ChartPlugInWrapper *target_plugin_chart = NULL;
10405 s57chart *Chs57 = NULL;
10406 wxFileName file;
10407 wxArrayString files;
10408
10409 ChartBase *target_chart = GetChartAtCursor();
10410 if (target_chart) {
10411 file.Assign(target_chart->GetFullPath());
10412 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10413 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10414 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10415 else
10416 Chs57 = dynamic_cast<s57chart *>(target_chart);
10417 } else { // target_chart = null, might be mbtiles
10418 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10419 unsigned int im = stackIndexArray.size();
10420 int scale = 2147483647; // max 32b integer
10421 if (VPoint.b_quilt && im > 0) {
10422 for (unsigned int is = 0; is < im; is++) {
10423 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10424 CHART_TYPE_MBTILES) {
10425 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10426 double lat, lon;
10427 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10428 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10429 .GetBBox()
10430 .Contains(lat, lon)) {
10431 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10432 scale) {
10433 scale =
10434 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10435 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10436 }
10437 }
10438 }
10439 }
10440 }
10441 }
10442
10443 std::vector<Ais8_001_22 *> area_notices;
10444
10445 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10446 float vp_scale = GetVPScale();
10447
10448 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10449 auto target_data = target.second;
10450 if (!target_data->area_notices.empty()) {
10451 for (auto &ani : target_data->area_notices) {
10452 Ais8_001_22 &area_notice = ani.second;
10453
10454 BoundingBox bbox;
10455
10456 for (Ais8_001_22_SubAreaList::iterator sa =
10457 area_notice.sub_areas.begin();
10458 sa != area_notice.sub_areas.end(); ++sa) {
10459 switch (sa->shape) {
10460 case AIS8_001_22_SHAPE_CIRCLE: {
10461 wxPoint target_point;
10462 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10463 bbox.Expand(target_point);
10464 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10465 break;
10466 }
10467 case AIS8_001_22_SHAPE_RECT: {
10468 wxPoint target_point;
10469 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10470 bbox.Expand(target_point);
10471 if (sa->e_dim_m > sa->n_dim_m)
10472 bbox.EnLarge(sa->e_dim_m * vp_scale);
10473 else
10474 bbox.EnLarge(sa->n_dim_m * vp_scale);
10475 break;
10476 }
10477 case AIS8_001_22_SHAPE_POLYGON:
10478 case AIS8_001_22_SHAPE_POLYLINE: {
10479 for (int i = 0; i < 4; ++i) {
10480 double lat = sa->latitude;
10481 double lon = sa->longitude;
10482 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10483 &lat, &lon);
10484 wxPoint target_point;
10485 GetCanvasPointPix(lat, lon, &target_point);
10486 bbox.Expand(target_point);
10487 }
10488 break;
10489 }
10490 case AIS8_001_22_SHAPE_SECTOR: {
10491 double lat1 = sa->latitude;
10492 double lon1 = sa->longitude;
10493 double lat, lon;
10494 wxPoint target_point;
10495 GetCanvasPointPix(lat1, lon1, &target_point);
10496 bbox.Expand(target_point);
10497 for (int i = 0; i < 18; ++i) {
10498 ll_gc_ll(
10499 lat1, lon1,
10500 sa->left_bound_deg +
10501 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10502 sa->radius_m / 1852.0, &lat, &lon);
10503 GetCanvasPointPix(lat, lon, &target_point);
10504 bbox.Expand(target_point);
10505 }
10506 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10507 &lat, &lon);
10508 GetCanvasPointPix(lat, lon, &target_point);
10509 bbox.Expand(target_point);
10510 break;
10511 }
10512 }
10513 }
10514
10515 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10516 area_notices.push_back(&area_notice);
10517 }
10518 }
10519 }
10520 }
10521 }
10522
10523 if (target_chart || !area_notices.empty() || file.HasName()) {
10524 // Go get the array of all objects at the cursor lat/lon
10525 int sel_rad_pix = 5;
10526 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10527
10528 // Make sure we always get the lights from an object, even if we are
10529 // currently not displaying lights on the chart.
10530
10531 SetCursor(wxCURSOR_WAIT);
10532 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10533 if (!lightsVis) SetShowENCLights(true);
10534 ;
10535
10536 ListOfObjRazRules *rule_list = NULL;
10537 ListOfPI_S57Obj *pi_rule_list = NULL;
10538 if (Chs57)
10539 rule_list =
10540 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10541 else if (target_plugin_chart)
10542 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10543 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10544
10545 ListOfObjRazRules *overlay_rule_list = NULL;
10546 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10547 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10548
10549 if (CHs57_Overlay) {
10550 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10551 zlat, zlon, SelectRadius, &GetVP());
10552 }
10553
10554 if (!lightsVis) SetShowENCLights(false);
10555
10556 wxString objText;
10557 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10558 wxString face = dFont->GetFaceName();
10559
10560 if (NULL == g_pObjectQueryDialog) {
10562 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10563 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10564 }
10565
10566 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10567 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10568
10569#ifdef __WXOSX__
10570 // Auto Adjustment for dark mode
10571 fg = g_pObjectQueryDialog->GetForegroundColour();
10572#endif
10573
10574 objText.Printf(
10575 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10576 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10577
10578#ifdef __WXOSX__
10579 int points = dFont->GetPointSize();
10580#else
10581 int points = dFont->GetPointSize() + 1;
10582#endif
10583
10584 int sizes[7];
10585 for (int i = -2; i < 5; i++) {
10586 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10587 }
10588 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10589
10590 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10591
10592 if (overlay_rule_list && CHs57_Overlay) {
10593 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10594 objText << "<hr noshade>";
10595 }
10596
10597 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10598 an != area_notices.end(); ++an) {
10599 objText << "<b>AIS Area Notice:</b> ";
10600 objText << ais8_001_22_notice_names[(*an)->notice_type];
10601 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10602 (*an)->sub_areas.begin();
10603 sa != (*an)->sub_areas.end(); ++sa)
10604 if (!sa->text.empty()) objText << sa->text;
10605 objText << "<br>expires: " << (*an)->expiry_time.Format();
10606 objText << "<hr noshade>";
10607 }
10608
10609 if (Chs57)
10610 objText << Chs57->CreateObjDescriptions(rule_list);
10611 else if (target_plugin_chart)
10612 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10613 pi_rule_list);
10614
10615 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10616
10617 // Add the additional info files
10618 wxString AddFiles, filenameOK;
10619 int filecount = 0;
10620 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10621 // plugin
10622
10623 AddFiles = wxString::Format(
10624 "<hr noshade><br><b>Additional info files attached to: </b> "
10625 "<font "
10626 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10627 "cellpadding=3>",
10628 file.GetFullName());
10629 file.Normalize();
10630 file.Assign(file.GetPath(), "");
10631 wxDir dir(file.GetFullPath());
10632 wxString filename;
10633 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10634 while (cont) {
10635 file.Assign(dir.GetNameWithSep().append(filename));
10636 wxString FormatString =
10637 "<td valign=top><font size=-2><a "
10638 "href=\"%s\">%s</a></font></td>";
10639 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10640 filenameOK = file.GetFullPath(); // remember last valid name
10641 // we are making a 3 columns table. New row only every third file
10642 if (3 * ((int)filecount / 3) == filecount)
10643 FormatString.Prepend("<tr>"); // new row
10644 else
10645 FormatString.Prepend(
10646 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10647 // spacer column
10648
10649 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10650 file.GetFullName());
10651 filecount++;
10652 }
10653 cont = dir.GetNext(&filename);
10654 }
10655 objText << AddFiles << "</table>";
10656 }
10657 objText << "</font>";
10658 objText << "</body></html>";
10659
10660 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10661 g_pObjectQueryDialog->SetHTMLPage(objText);
10662 g_pObjectQueryDialog->Show();
10663 }
10664 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10665 // generate an event to avoid double code
10666 wxHtmlLinkInfo hli(filenameOK);
10667 wxHtmlLinkEvent hle(1, hli);
10668 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10669 }
10670
10671 if (rule_list) rule_list->Clear();
10672 delete rule_list;
10673
10674 if (overlay_rule_list) overlay_rule_list->Clear();
10675 delete overlay_rule_list;
10676
10677 if (pi_rule_list) pi_rule_list->Clear();
10678 delete pi_rule_list;
10679
10680 SetCursor(wxCURSOR_ARROW);
10681 }
10682}
10683
10684void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10685 bool bNew = false;
10686 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10687 // Dialog
10688 g_pMarkInfoDialog = new MarkInfoDlg(this);
10689 bNew = true;
10690 }
10691
10692 if (1 /*g_bresponsive*/) {
10693 wxSize canvas_size = GetSize();
10694
10695 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10696 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10697
10698 g_pMarkInfoDialog->Layout();
10699
10700 wxPoint canvas_pos = GetPosition();
10701 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10702
10703 bool newFit = false;
10704 if (canvas_size.x < fitted_size.x) {
10705 fitted_size.x = canvas_size.x - 40;
10706 if (canvas_size.y < fitted_size.y)
10707 fitted_size.y -= 40; // scrollbar added
10708 }
10709 if (canvas_size.y < fitted_size.y) {
10710 fitted_size.y = canvas_size.y - 40;
10711 if (canvas_size.x < fitted_size.x)
10712 fitted_size.x -= 40; // scrollbar added
10713 }
10714
10715 if (newFit) {
10716 g_pMarkInfoDialog->SetSize(fitted_size);
10717 g_pMarkInfoDialog->Centre();
10718 }
10719 }
10720
10721 markPoint->m_bRPIsBeingEdited = false;
10722
10723 wxString title_base = _("Mark Properties");
10724 if (markPoint->m_bIsInRoute) {
10725 title_base = _("Waypoint Properties");
10726 }
10727 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10728 g_pMarkInfoDialog->UpdateProperties();
10729 if (markPoint->m_bIsInLayer) {
10730 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10731 GetLayerName(markPoint->m_LayerID)));
10732 g_pMarkInfoDialog->SetDialogTitle(caption);
10733 } else
10734 g_pMarkInfoDialog->SetDialogTitle(title_base);
10735
10736 g_pMarkInfoDialog->Show();
10737 g_pMarkInfoDialog->Raise();
10738 g_pMarkInfoDialog->InitialFocus();
10739 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10740}
10741
10742void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10743 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10744 pRoutePropDialog->SetRouteAndUpdate(selected);
10745 // pNew->UpdateProperties();
10746 pRoutePropDialog->Show();
10747 pRoutePropDialog->Raise();
10748 return;
10749 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10750 this); // There is one global instance of the RouteProp Dialog
10751
10752 if (g_bresponsive) {
10753 wxSize canvas_size = GetSize();
10754 wxPoint canvas_pos = GetPosition();
10755 wxSize fitted_size = pRoutePropDialog->GetSize();
10756 ;
10757
10758 if (canvas_size.x < fitted_size.x) {
10759 fitted_size.x = canvas_size.x;
10760 if (canvas_size.y < fitted_size.y)
10761 fitted_size.y -= 20; // scrollbar added
10762 }
10763 if (canvas_size.y < fitted_size.y) {
10764 fitted_size.y = canvas_size.y;
10765 if (canvas_size.x < fitted_size.x)
10766 fitted_size.x -= 20; // scrollbar added
10767 }
10768
10769 pRoutePropDialog->SetSize(fitted_size);
10770 pRoutePropDialog->Centre();
10771
10772 // int xp = (canvas_size.x - fitted_size.x)/2;
10773 // int yp = (canvas_size.y - fitted_size.y)/2;
10774
10775 wxPoint xxp = ClientToScreen(canvas_pos);
10776 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10777 }
10778
10779 pRoutePropDialog->SetRouteAndUpdate(selected);
10780
10781 pRoutePropDialog->Show();
10782
10783 Refresh(false);
10784}
10785
10786void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10787 pTrackPropDialog = TrackPropDlg::getInstance(
10788 this); // There is one global instance of the RouteProp Dialog
10789
10790 pTrackPropDialog->SetTrackAndUpdate(selected);
10792
10793 pTrackPropDialog->Show();
10794
10795 Refresh(false);
10796}
10797
10798void pupHandler_PasteWaypoint() {
10799 Kml kml;
10800
10801 int pasteBuffer = kml.ParsePasteBuffer();
10802 RoutePoint *pasted = kml.GetParsedRoutePoint();
10803 if (!pasted) return;
10804
10805 double nearby_radius_meters =
10806 g_Platform->GetSelectRadiusPix() /
10807 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10808
10809 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10810 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10811
10812 int answer = wxID_NO;
10813 if (nearPoint && !nearPoint->m_bIsInLayer) {
10814 wxString msg;
10815 msg << _(
10816 "There is an existing waypoint at the same location as the one you are "
10817 "pasting. Would you like to merge the pasted data with it?\n\n");
10818 msg << _("Answering 'No' will create a new waypoint at the same location.");
10819 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10820 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10821 }
10822
10823 if (answer == wxID_YES) {
10824 nearPoint->SetName(pasted->GetName());
10825 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10826 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10827 pRouteManagerDialog->UpdateWptListCtrl();
10828 }
10829
10830 if (answer == wxID_NO) {
10831 RoutePoint *newPoint = new RoutePoint(pasted);
10832 newPoint->m_bIsolatedMark = true;
10833 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10834 newPoint);
10835 // pConfig->AddNewWayPoint(newPoint, -1);
10836 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10837
10838 pWayPointMan->AddRoutePoint(newPoint);
10839 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10840 pRouteManagerDialog->UpdateWptListCtrl();
10841 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10842 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10843 }
10844
10845 gFrame->InvalidateAllGL();
10846 gFrame->RefreshAllCanvas(false);
10847}
10848
10849void pupHandler_PasteRoute() {
10850 Kml kml;
10851
10852 int pasteBuffer = kml.ParsePasteBuffer();
10853 Route *pasted = kml.GetParsedRoute();
10854 if (!pasted) return;
10855
10856 double nearby_radius_meters =
10857 g_Platform->GetSelectRadiusPix() /
10858 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10859
10860 RoutePoint *curPoint;
10861 RoutePoint *nearPoint;
10862 RoutePoint *prevPoint = NULL;
10863
10864 bool mergepoints = false;
10865 bool createNewRoute = true;
10866 int existingWaypointCounter = 0;
10867
10868 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10869 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10870 nearPoint = pWayPointMan->GetNearbyWaypoint(
10871 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10872 if (nearPoint) {
10873 mergepoints = true;
10874 existingWaypointCounter++;
10875 // Small hack here to avoid both extending RoutePoint and repeating all
10876 // the GetNearbyWaypoint calculations. Use existin data field in
10877 // RoutePoint as temporary storage.
10878 curPoint->m_bPtIsSelected = true;
10879 }
10880 }
10881
10882 int answer = wxID_NO;
10883 if (mergepoints) {
10884 wxString msg;
10885 msg << _(
10886 "There are existing waypoints at the same location as some of the ones "
10887 "you are pasting. Would you like to just merge the pasted data into "
10888 "them?\n\n");
10889 msg << _("Answering 'No' will create all new waypoints for this route.");
10890 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10891 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10892
10893 if (answer == wxID_CANCEL) {
10894 return;
10895 }
10896 }
10897
10898 // If all waypoints exist since before, and a route with the same name, we
10899 // don't create a new route.
10900 if (mergepoints && answer == wxID_YES &&
10901 existingWaypointCounter == pasted->GetnPoints()) {
10902 for (Route *proute : *pRouteList) {
10903 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10904 createNewRoute = false;
10905 break;
10906 }
10907 }
10908 }
10909
10910 Route *newRoute = 0;
10911 RoutePoint *newPoint = 0;
10912
10913 if (createNewRoute) {
10914 newRoute = new Route();
10915 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10916 }
10917
10918 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10919 curPoint = pasted->GetPoint(i);
10920 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10921 curPoint->m_bPtIsSelected = false;
10922 newPoint = pWayPointMan->GetNearbyWaypoint(
10923 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10924 newPoint->SetName(curPoint->GetName());
10925 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10926
10927 if (createNewRoute) newRoute->AddPoint(newPoint);
10928 } else {
10929 curPoint->m_bPtIsSelected = false;
10930
10931 newPoint = new RoutePoint(curPoint);
10932 newPoint->m_bIsolatedMark = false;
10933 newPoint->SetIconName("circle");
10934 newPoint->m_bIsVisible = true;
10935 newPoint->m_bShowName = false;
10936 newPoint->SetShared(false);
10937
10938 newRoute->AddPoint(newPoint);
10939 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10940 newPoint);
10941 // pConfig->AddNewWayPoint(newPoint, -1);
10942 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10943 pWayPointMan->AddRoutePoint(newPoint);
10944 }
10945 if (i > 1 && createNewRoute)
10946 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10947 curPoint->m_lat, curPoint->m_lon,
10948 prevPoint, newPoint, newRoute);
10949 prevPoint = newPoint;
10950 }
10951
10952 if (createNewRoute) {
10953 pRouteList->push_back(newRoute);
10954 // pConfig->AddNewRoute(newRoute); // use auto next num
10955 NavObj_dB::GetInstance().InsertRoute(newRoute);
10956
10957 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10958 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10959 }
10960
10961 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10962 pRouteManagerDialog->UpdateRouteListCtrl();
10963 pRouteManagerDialog->UpdateWptListCtrl();
10964 }
10965 gFrame->InvalidateAllGL();
10966 gFrame->RefreshAllCanvas(false);
10967 }
10968 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10969 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10970}
10971
10972void pupHandler_PasteTrack() {
10973 Kml kml;
10974
10975 int pasteBuffer = kml.ParsePasteBuffer();
10976 Track *pasted = kml.GetParsedTrack();
10977 if (!pasted) return;
10978
10979 TrackPoint *curPoint;
10980
10981 Track *newTrack = new Track();
10982 TrackPoint *newPoint;
10983 TrackPoint *prevPoint = NULL;
10984
10985 newTrack->SetName(pasted->GetName());
10986
10987 for (int i = 0; i < pasted->GetnPoints(); i++) {
10988 curPoint = pasted->GetPoint(i);
10989
10990 newPoint = new TrackPoint(curPoint);
10991
10992 wxDateTime now = wxDateTime::Now();
10993 newPoint->SetCreateTime(curPoint->GetCreateTime());
10994
10995 newTrack->AddPoint(newPoint);
10996
10997 if (prevPoint)
10998 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10999 newPoint->m_lat, newPoint->m_lon,
11000 prevPoint, newPoint, newTrack);
11001
11002 prevPoint = newPoint;
11003 }
11004
11005 g_TrackList.push_back(newTrack);
11006 // pConfig->AddNewTrack(newTrack);
11007 NavObj_dB::GetInstance().InsertTrack(newTrack);
11008
11009 gFrame->InvalidateAllGL();
11010 gFrame->RefreshAllCanvas(false);
11011}
11012
11013bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11014 wxJSONValue v;
11015 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11016 v["CursorPosition_x"] = x;
11017 v["CursorPosition_y"] = y;
11018 // Send a limited set of selection types depending on what is
11019 // found under the mouse point.
11020 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11021 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11022 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11023
11024 wxJSONWriter w;
11025 wxString out;
11026 w.Write(v, out);
11027 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11028
11029 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11030
11031#if 0
11032#define SELTYPE_UNKNOWN 0x0001
11033#define SELTYPE_ROUTEPOINT 0x0002
11034#define SELTYPE_ROUTESEGMENT 0x0004
11035#define SELTYPE_TIDEPOINT 0x0008
11036#define SELTYPE_CURRENTPOINT 0x0010
11037#define SELTYPE_ROUTECREATE 0x0020
11038#define SELTYPE_AISTARGET 0x0040
11039#define SELTYPE_MARKPOINT 0x0080
11040#define SELTYPE_TRACKSEGMENT 0x0100
11041#define SELTYPE_DRAGHANDLE 0x0200
11042#endif
11043
11044 if (g_bhide_context_menus) return true;
11045 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11046 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11047 m_pIDXCandidate, m_nmea_log);
11048
11049 Connect(
11050 wxEVT_COMMAND_MENU_SELECTED,
11051 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11052
11053#ifdef __WXGTK__
11054 // Funny requirement here for gtk, to clear the menu trigger event
11055 // TODO
11056 // Causes a slight "flasH" of the menu,
11057 if (m_inLongPress) {
11058 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11059 m_inLongPress = false;
11060 }
11061#endif
11062
11063 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11064
11065 Disconnect(
11066 wxEVT_COMMAND_MENU_SELECTED,
11067 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11068
11069 delete m_canvasMenu;
11070 m_canvasMenu = NULL;
11071
11072#ifdef __WXQT__
11073 // gFrame->SurfaceToolbar();
11074 // g_MainToolbar->Raise();
11075#endif
11076
11077 return true;
11078}
11079
11080void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11081 // Pass menu events from the canvas to the menu handler
11082 // This is necessarily in ChartCanvas since that is the menu's parent.
11083 if (m_canvasMenu) {
11084 m_canvasMenu->PopupMenuHandler(event);
11085 }
11086 return;
11087}
11088
11089void ChartCanvas::StartRoute() {
11090 // Do not allow more than one canvas to create a route at one time.
11091 if (g_brouteCreating) return;
11092
11093 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11094
11095 g_brouteCreating = true;
11096 m_routeState = 1;
11097 m_bDrawingRoute = false;
11098 SetCursor(*pCursorPencil);
11099 // SetCanvasToolbarItemState(ID_ROUTE, true);
11100 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11101
11102 HideGlobalToolbar();
11103
11104#ifdef __ANDROID__
11105 androidSetRouteAnnunciator(true);
11106#endif
11107}
11108
11109wxString ChartCanvas::FinishRoute() {
11110 m_routeState = 0;
11111 m_prev_pMousePoint = NULL;
11112 m_bDrawingRoute = false;
11113 wxString rv = "";
11114 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11115
11116 // SetCanvasToolbarItemState(ID_ROUTE, false);
11117 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11118#ifdef __ANDROID__
11119 androidSetRouteAnnunciator(false);
11120#endif
11121
11122 SetCursor(*pCursorArrow);
11123
11124 if (m_pMouseRoute) {
11125 if (m_bAppendingRoute) {
11126 // pConfig->UpdateRoute(m_pMouseRoute);
11127 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11128 } else {
11129 if (m_pMouseRoute->GetnPoints() > 1) {
11130 // pConfig->AddNewRoute(m_pMouseRoute);
11131 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11132 } else {
11133 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11134 m_pMouseRoute = NULL;
11135 }
11136 }
11137 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11138
11139 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11140 (pRoutePropDialog->IsShown())) {
11141 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11142 }
11143
11144 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11145 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11146 pRouteManagerDialog->UpdateRouteListCtrl();
11147 }
11148 }
11149 m_bAppendingRoute = false;
11150 m_pMouseRoute = NULL;
11151
11152 m_pSelectedRoute = NULL;
11153
11154 undo->InvalidateUndo();
11155 gFrame->RefreshAllCanvas(true);
11156
11157 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11158
11159 ShowGlobalToolbar();
11160
11161 g_brouteCreating = false;
11162
11163 return rv;
11164}
11165
11166void ChartCanvas::HideGlobalToolbar() {
11167 if (m_canvasIndex == 0) {
11168 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11169 }
11170}
11171
11172void ChartCanvas::ShowGlobalToolbar() {
11173 if (m_canvasIndex == 0) {
11174 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11175 }
11176}
11177
11178void ChartCanvas::ShowAISTargetList() {
11179 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11180 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11181 }
11182
11183 g_pAISTargetList->UpdateAISTargetList();
11184}
11185
11186void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11187 if (!m_bShowOutlines) return;
11188
11189 if (!ChartData) return;
11190
11191 int nEntry = ChartData->GetChartTableEntries();
11192
11193 for (int i = 0; i < nEntry; i++) {
11194 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11195
11196 // Check to see if the candidate chart is in the currently active group
11197 bool b_group_draw = false;
11198 if (m_groupIndex > 0) {
11199 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11200 int index = pt->GetGroupArray()[ig];
11201 if (m_groupIndex == index) {
11202 b_group_draw = true;
11203 break;
11204 }
11205 }
11206 } else
11207 b_group_draw = true;
11208
11209 if (b_group_draw) RenderChartOutline(dc, i, vp);
11210 }
11211
11212 // On CM93 Composite Charts, draw the outlines of the next smaller
11213 // scale cell
11214 cm93compchart *pcm93 = NULL;
11215 if (VPoint.b_quilt) {
11216 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11217 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11218 pcm93 = (cm93compchart *)pch;
11219 break;
11220 }
11221 } else if (m_singleChart &&
11222 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11223 pcm93 = (cm93compchart *)m_singleChart;
11224
11225 if (pcm93) {
11226 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11227 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11228
11229 if (zoom_factor > 8.0) {
11230 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11231 dc.SetPen(mPen);
11232 } else {
11233 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11234 dc.SetPen(mPen);
11235 }
11236
11237 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11238 }
11239}
11240
11241void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11242#ifdef ocpnUSE_GL
11243 if (g_bopengl && m_glcc) {
11244 /* opengl version specially optimized */
11245 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11246 return;
11247 }
11248#endif
11249
11250 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11251 if (!ChartData->IsChartAvailable(dbIndex)) return;
11252 }
11253
11254 float plylat, plylon;
11255 float plylat1, plylon1;
11256
11257 int pixx, pixy, pixx1, pixy1;
11258
11259 LLBBox box;
11260 ChartData->GetDBBoundingBox(dbIndex, box);
11261
11262 // Don't draw an outline in the case where the chart covers the entire world
11263 // */
11264 if (box.GetLonRange() == 360) return;
11265
11266 double lon_bias = 0;
11267 // chart is outside of viewport lat/lon bounding box
11268 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11269
11270 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11271
11272 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11273 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11274
11275 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11276 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11277
11278 else
11279 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11280
11281 // Are there any aux ply entries?
11282 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11283 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11284 {
11285 wxPoint r, r1;
11286
11287 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11288 plylon += lon_bias;
11289
11290 GetCanvasPointPix(plylat, plylon, &r);
11291 pixx = r.x;
11292 pixy = r.y;
11293
11294 for (int i = 0; i < nPly - 1; i++) {
11295 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11296 plylon1 += lon_bias;
11297
11298 GetCanvasPointPix(plylat1, plylon1, &r1);
11299 pixx1 = r1.x;
11300 pixy1 = r1.y;
11301
11302 int pixxs1 = pixx1;
11303 int pixys1 = pixy1;
11304
11305 bool b_skip = false;
11306
11307 if (vp.chart_scale > 5e7) {
11308 // calculate projected distance between these two points in meters
11309 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11310 pow((double)(pixy1 - pixy), 2)) /
11311 vp.view_scale_ppm;
11312
11313 if (dist > 0.0) {
11314 // calculate GC distance between these two points in meters
11315 double distgc =
11316 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11317
11318 // If the distances are nonsense, it means that the scale is very
11319 // small and the segment wrapped the world So skip it....
11320 // TODO improve this to draw two segments
11321 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11322 b_skip = true;
11323 } else
11324 b_skip = true;
11325 }
11326
11327 ClipResult res = cohen_sutherland_line_clip_i(
11328 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11329 if (res != Invisible && !b_skip)
11330 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11331
11332 plylat = plylat1;
11333 plylon = plylon1;
11334 pixx = pixxs1;
11335 pixy = pixys1;
11336 }
11337
11338 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11339 plylon1 += lon_bias;
11340
11341 GetCanvasPointPix(plylat1, plylon1, &r1);
11342 pixx1 = r1.x;
11343 pixy1 = r1.y;
11344
11345 ClipResult res = cohen_sutherland_line_clip_i(
11346 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11347 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11348 }
11349
11350 else // Use Aux PlyPoints
11351 {
11352 wxPoint r, r1;
11353
11354 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11355 for (int j = 0; j < nAuxPlyEntries; j++) {
11356 int nAuxPly =
11357 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11358 GetCanvasPointPix(plylat, plylon, &r);
11359 pixx = r.x;
11360 pixy = r.y;
11361
11362 for (int i = 0; i < nAuxPly - 1; i++) {
11363 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11364
11365 GetCanvasPointPix(plylat1, plylon1, &r1);
11366 pixx1 = r1.x;
11367 pixy1 = r1.y;
11368
11369 int pixxs1 = pixx1;
11370 int pixys1 = pixy1;
11371
11372 bool b_skip = false;
11373
11374 if (vp.chart_scale > 5e7) {
11375 // calculate projected distance between these two points in meters
11376 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11377 ((pixy1 - pixy) * (pixy1 - pixy))) /
11378 vp.view_scale_ppm;
11379 if (dist > 0.0) {
11380 // calculate GC distance between these two points in meters
11381 double distgc =
11382 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11383
11384 // If the distances are nonsense, it means that the scale is very
11385 // small and the segment wrapped the world So skip it....
11386 // TODO improve this to draw two segments
11387 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11388 b_skip = true;
11389 } else
11390 b_skip = true;
11391 }
11392
11393 ClipResult res = cohen_sutherland_line_clip_i(
11394 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11395 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11396
11397 plylat = plylat1;
11398 plylon = plylon1;
11399 pixx = pixxs1;
11400 pixy = pixys1;
11401 }
11402
11403 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11404 GetCanvasPointPix(plylat1, plylon1, &r1);
11405 pixx1 = r1.x;
11406 pixy1 = r1.y;
11407
11408 ClipResult res = cohen_sutherland_line_clip_i(
11409 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11410 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11411 }
11412 }
11413}
11414
11415static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11416 const wxArrayString &legend) {
11417 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11418
11419 int pointsize = dFont->GetPointSize();
11420 pointsize /= OCPN_GetWinDIPScaleFactor();
11421
11422 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11423 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11424 false, dFont->GetFaceName());
11425
11426 dc.SetFont(*psRLI_font);
11427
11428 int h = 0;
11429 int w = 0;
11430 int hl, wl;
11431
11432 int xp, yp;
11433 int hilite_offset = 3;
11434
11435 for (wxString line : legend) {
11436#ifdef __WXMAC__
11437 wxScreenDC sdc;
11438 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11439#else
11440 dc.GetTextExtent(line, &wl, &hl);
11441 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11442 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11443#endif
11444 h += hl;
11445 w = wxMax(w, wl);
11446 }
11447 w += (hl / 2); // Add a little right pad
11448
11449 xp = ref_point.x - w;
11450 yp = ref_point.y;
11451 yp += hilite_offset;
11452
11453 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11454
11455 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11456 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11457
11458 for (wxString line : legend) {
11459 dc.DrawText(line, xp, yp);
11460 yp += hl;
11461 }
11462}
11463
11464void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11465 if (!g_bAllowShipToActive) return;
11466
11467 Route *rt = g_pRouteMan->GetpActiveRoute();
11468 if (!rt) return;
11469
11470 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11471 wxPoint2DDouble pa, pb;
11473 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11474
11475 // set pen
11476 int width =
11477 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11478 if (rt->m_width != wxPENSTYLE_INVALID)
11479 width = rt->m_width; // set route pen style if any
11480 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11481 g_shipToActiveStyle, 5)]; // get setting pen style
11482 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11483 wxColour color =
11484 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11485 : // set setting route pen color
11486 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11487 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11488
11489 dc.SetPen(*mypen);
11490 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11491
11492 if (!Use_Opengl)
11493 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11494 (int)pb.m_y, GetVP(), true);
11495
11496#ifdef ocpnUSE_GL
11497 else {
11498#ifdef USE_ANDROID_GLES2
11499 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11500#else
11501 if (style != wxPENSTYLE_SOLID) {
11502 if (glChartCanvas::dash_map.find(style) !=
11503 glChartCanvas::dash_map.end()) {
11504 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11505 dc.SetPen(*mypen);
11506 }
11507 }
11508 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11509#endif
11510
11511 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11512 (int)pb.m_x, (int)pb.m_y, GetVP());
11513 }
11514#endif
11515 }
11516}
11517
11518void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11519 Route *route = 0;
11520 if (m_routeState >= 2) route = m_pMouseRoute;
11521 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11522 route = m_pMeasureRoute;
11523
11524 if (!route) return;
11525
11526 // Validate route pointer
11527 if (!g_pRouteMan->IsRouteValid(route)) return;
11528
11529 double render_lat = m_cursor_lat;
11530 double render_lon = m_cursor_lon;
11531
11532 int np = route->GetnPoints();
11533 if (np) {
11534 if (g_btouch && (np > 1)) np--;
11535 RoutePoint rp = route->GetPoint(np);
11536 render_lat = rp.m_lat;
11537 render_lon = rp.m_lon;
11538 }
11539
11540 double rhumbBearing, rhumbDist;
11541 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11542 &rhumbBearing, &rhumbDist);
11543 double brg = rhumbBearing;
11544 double dist = rhumbDist;
11545
11546 // Skip GreatCircle rubberbanding on touch devices.
11547 if (!g_btouch) {
11548 double gcBearing, gcBearing2, gcDist;
11549 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11550 m_cursor_lat, &gcDist, &gcBearing,
11551 &gcBearing2);
11552 double gcDistm = gcDist / 1852.0;
11553
11554 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11555 rhumbBearing = 90.;
11556
11557 wxPoint destPoint, lastPoint;
11558
11559 route->m_NextLegGreatCircle = false;
11560 int milesDiff = rhumbDist - gcDistm;
11561 if (milesDiff > 1) {
11562 brg = gcBearing;
11563 dist = gcDistm;
11564 route->m_NextLegGreatCircle = true;
11565 }
11566
11567 // FIXME (MacOS, the first segment is rendered wrong)
11568 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11569 &lastPoint);
11570
11571 if (route->m_NextLegGreatCircle) {
11572 for (int i = 1; i <= milesDiff; i++) {
11573 double p = (double)i * (1.0 / (double)milesDiff);
11574 double pLat, pLon;
11575 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11576 &pLon, &pLat, &gcBearing2);
11577 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11578 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11579 false);
11580 lastPoint = destPoint;
11581 }
11582 } else {
11583 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11584 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11585 false);
11586 if (m_bMeasure_DistCircle) {
11587 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11588 powf((float)(r_rband.y - lastPoint.y), 2));
11589
11590 dc.SetPen(*g_pRouteMan->GetRoutePen());
11591 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11592 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11593 }
11594 }
11595 }
11596 }
11597
11598 wxString routeInfo;
11599 wxArrayString infoArray;
11600 double varBrg = 0;
11601 if (g_bShowTrue)
11602 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11603 0x00B0);
11604
11605 if (g_bShowMag) {
11606 double latAverage = (m_cursor_lat + render_lat) / 2;
11607 double lonAverage = (m_cursor_lon + render_lon) / 2;
11608 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11609
11610 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11611 (int)varBrg, 0x00B0);
11612 }
11613 routeInfo << " " << FormatDistanceAdaptive(dist);
11614 infoArray.Add(routeInfo);
11615 routeInfo.Clear();
11616
11617 // To make it easier to use a route as a bearing on a charted object add for
11618 // the first leg also the reverse bearing.
11619 if (np == 1) {
11620 routeInfo << "Reverse: ";
11621 if (g_bShowTrue)
11622 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11623 (int)(brg + 180.) % 360, 0x00B0);
11624 if (g_bShowMag)
11625 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11626 (int)(varBrg + 180.) % 360, 0x00B0);
11627 infoArray.Add(routeInfo);
11628 routeInfo.Clear();
11629 }
11630
11631 wxString s0;
11632 if (!route->m_bIsInLayer)
11633 s0.Append(_("Route") + ": ");
11634 else
11635 s0.Append(_("Layer Route: "));
11636
11637 double disp_length = route->m_route_length;
11638 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11639 s0 += FormatDistanceAdaptive(disp_length);
11640
11641 infoArray.Add(s0);
11642 routeInfo.Clear();
11643
11644 RouteLegInfo(dc, r_rband, infoArray);
11645
11646 m_brepaint_piano = true;
11647}
11648
11649void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11650 if (!m_bShowVisibleSectors) return;
11651
11652 if (g_bDeferredInitDone) {
11653 // need to re-evaluate sectors?
11654 double rhumbBearing, rhumbDist;
11655 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11656 &rhumbBearing, &rhumbDist);
11657
11658 if (rhumbDist > 0.05) // miles
11659 {
11660 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11661 m_sectorlegsVisible);
11662 m_sector_glat = gLat;
11663 m_sector_glon = gLon;
11664 }
11665 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11666 }
11667}
11668
11669void ChartCanvas::WarpPointerDeferred(int x, int y) {
11670 warp_x = x;
11671 warp_y = y;
11672 warp_flag = true;
11673}
11674
11675int s_msg;
11676
11677void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11678 if (!ps52plib) return;
11679
11680 if (VPoint.b_quilt) { // quilted
11681 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11682
11683 if (m_pQuilt->IsQuiltVector()) {
11684 if (ps52plib->GetStateHash() != m_s52StateHash) {
11685 UpdateS52State();
11686 m_s52StateHash = ps52plib->GetStateHash();
11687 }
11688 }
11689 } else {
11690 if (ps52plib->GetStateHash() != m_s52StateHash) {
11691 UpdateS52State();
11692 m_s52StateHash = ps52plib->GetStateHash();
11693 }
11694 }
11695
11696 // Plugin charts
11697 bool bSendPlibState = true;
11698 if (VPoint.b_quilt) { // quilted
11699 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11700 }
11701
11702 if (bSendPlibState) {
11703 wxJSONValue v;
11704 v["OpenCPN Version Major"] = VERSION_MAJOR;
11705 v["OpenCPN Version Minor"] = VERSION_MINOR;
11706 v["OpenCPN Version Patch"] = VERSION_PATCH;
11707 v["OpenCPN Version Date"] = VERSION_DATE;
11708 v["OpenCPN Version Full"] = VERSION_FULL;
11709
11710 // S52PLIB state
11711 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11712 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11713 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11714 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11715 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11716 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11717 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11718
11719 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11720
11721 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11722 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11723
11724 // Global S52 options
11725
11726 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11727 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11728 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11729 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11730 ps52plib->m_bShowS57ImportantTextOnly;
11731 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11732 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11733 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11734 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11735 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11736
11737 // Some global GUI parameters, for completeness
11738 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11739 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11740 v["OpenCPN Scale Factor Exp"] =
11741 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11742 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11743
11744 wxJSONWriter w;
11745 wxString out;
11746 w.Write(v, out);
11747
11748 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11749 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11750 g_lastS52PLIBPluginMessage = out;
11751 }
11752 }
11753}
11754int spaint;
11755int s_in_update;
11756void ChartCanvas::OnPaint(wxPaintEvent &event) {
11757 wxPaintDC dc(this);
11758
11759 // GetToolbar()->Show( m_bToolbarEnable );
11760
11761 // Paint updates may have been externally disabled (temporarily, to avoid
11762 // Yield() recursion performance loss) It is important that the wxPaintDC is
11763 // built, even if we elect to not process this paint message. Otherwise, the
11764 // paint message may not be removed from the message queue, esp on Windows.
11765 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11766
11767 if (!m_b_paint_enable) {
11768 return;
11769 }
11770
11771 // If necessary, reconfigure the S52 PLIB
11773
11774#ifdef ocpnUSE_GL
11775 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11776
11777 if (m_glcc && g_bopengl) {
11778 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11779 s_in_update++;
11780 m_glcc->Update();
11781 s_in_update--;
11782 }
11783
11784 return;
11785 }
11786#endif
11787
11788 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11789
11790 wxRegion ru = GetUpdateRegion();
11791
11792 int rx, ry, rwidth, rheight;
11793 ru.GetBox(rx, ry, rwidth, rheight);
11794
11795#ifdef ocpnUSE_DIBSECTION
11796 ocpnMemDC temp_dc;
11797#else
11798 wxMemoryDC temp_dc;
11799#endif
11800
11801 long height = GetVP().pix_height;
11802
11803#ifdef __WXMAC__
11804 // On OS X we have to explicitly extend the region for the piano area
11805 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11806 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11807 height += m_Piano->GetHeight();
11808#endif // __WXMAC__
11809 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11810
11811 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11812 if (pthumbwin) {
11813 int thumbx, thumby, thumbsx, thumbsy;
11814 pthumbwin->GetPosition(&thumbx, &thumby);
11815 pthumbwin->GetSize(&thumbsx, &thumbsy);
11816 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11817
11818 if (pthumbwin->IsShown()) {
11819 rgn_chart.Subtract(rgn_thumbwin);
11820 ru.Subtract(rgn_thumbwin);
11821 }
11822 }
11823
11824 // subtract the chart bar if it isn't transparent, and determine if we need to
11825 // paint it
11826 wxRegion rgn_blit = ru;
11827 if (g_bShowChartBar) {
11828 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11829 GetClientSize().x, m_Piano->GetHeight());
11830
11831 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11832 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11833 if (style->chartStatusWindowTransparent)
11834 m_brepaint_piano = true;
11835 else
11836 ru.Subtract(chart_bar_rect);
11837 }
11838 }
11839
11840 if (m_Compass && m_Compass->IsShown()) {
11841 wxRect compassRect = m_Compass->GetRect();
11842 if (ru.Contains(compassRect) != wxOutRegion) {
11843 ru.Subtract(compassRect);
11844 }
11845 }
11846
11847 if (m_notification_button) {
11848 wxRect noteRect = m_notification_button->GetRect();
11849 if (ru.Contains(noteRect) != wxOutRegion) {
11850 ru.Subtract(noteRect);
11851 }
11852 }
11853
11854 // Is this viewpoint the same as the previously painted one?
11855 bool b_newview = true;
11856
11857 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11858 (m_cache_vp.rotation == VPoint.rotation) &&
11859 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11860 m_cache_vp.IsValid()) {
11861 b_newview = false;
11862 }
11863
11864 // If the ViewPort is skewed or rotated, we may be able to use the cached
11865 // rotated bitmap.
11866 bool b_rcache_ok = false;
11867 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11868 b_rcache_ok = !b_newview;
11869
11870 // Make a special VP
11871 if (VPoint.b_MercatorProjectionOverride)
11872 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11873 ViewPort svp = VPoint;
11874
11875 svp.pix_width = svp.rv_rect.width;
11876 svp.pix_height = svp.rv_rect.height;
11877
11878 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11879 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11880 // VPoint.rv_rect.height);
11881
11882 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11883
11884 // If we are going to use the cached rotated image, there is no need to fetch
11885 // any chart data and this will do it...
11886 if (b_rcache_ok) chart_get_region.Clear();
11887
11888 // Blit pan acceleration
11889 if (VPoint.b_quilt) // quilted
11890 {
11891 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11892
11893 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11894
11895 bool busy = false;
11896 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11897 m_cache_vp.rotation != VPoint.rotation)) {
11898 AbstractPlatform::ShowBusySpinner();
11899 busy = true;
11900 }
11901
11902 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11903 (m_working_bm.GetHeight() != svp.pix_height))
11904 m_working_bm.Create(svp.pix_width, svp.pix_height,
11905 -1); // make sure the target is big enoug
11906
11907 if (fabs(VPoint.rotation) < 0.01) {
11908 bool b_save = true;
11909
11910 if (g_SencThreadManager) {
11911 if (g_SencThreadManager->GetJobCount()) {
11912 b_save = false;
11913 m_cache_vp.Invalidate();
11914 }
11915 }
11916
11917 // If the saved wxBitmap from last OnPaint is useable
11918 // calculate the blit parameters
11919
11920 // We can only do screen blit painting if subsequent ViewPorts differ by
11921 // whole pixels So, in small scale bFollow mode, force the full screen
11922 // render. This seems a hack....There may be better logic here.....
11923
11924 // if(m_bFollow)
11925 // b_save = false;
11926
11927 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11928 if (b_newview) {
11929 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11930 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11931
11932 int dy = c_new.y - c_old.y;
11933 int dx = c_new.x - c_old.x;
11934
11935 // printf("In OnPaint Trying Blit dx: %d
11936 // dy:%d\n\n", dx, dy);
11937
11938 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11939 if (dx || dy) {
11940 // Blit the reuseable portion of the cached wxBitmap to a working
11941 // bitmap
11942 temp_dc.SelectObject(m_working_bm);
11943
11944 wxMemoryDC cache_dc;
11945 cache_dc.SelectObject(m_cached_chart_bm);
11946
11947 if (dy > 0) {
11948 if (dx > 0) {
11949 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11950 VPoint.pix_height - dy, &cache_dc, dx, dy);
11951 } else {
11952 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11953 VPoint.pix_height - dy, &cache_dc, 0, dy);
11954 }
11955
11956 } else {
11957 if (dx > 0) {
11958 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11959 VPoint.pix_height + dy, &cache_dc, dx, 0);
11960 } else {
11961 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11962 VPoint.pix_height + dy, &cache_dc, 0, 0);
11963 }
11964 }
11965
11966 OCPNRegion update_region;
11967 if (dy) {
11968 if (dy > 0)
11969 update_region.Union(
11970 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11971 else
11972 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11973 }
11974
11975 if (dx) {
11976 if (dx > 0)
11977 update_region.Union(
11978 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11979 else
11980 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11981 }
11982
11983 // Render the new region
11984 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11985 update_region);
11986 cache_dc.SelectObject(wxNullBitmap);
11987 } else {
11988 // No sensible (dx, dy) change in the view, so use the cached
11989 // member bitmap
11990 temp_dc.SelectObject(m_cached_chart_bm);
11991 b_save = false;
11992 }
11993 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11994
11995 } else // not blitable
11996 {
11997 temp_dc.SelectObject(m_working_bm);
11998 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11999 chart_get_region);
12000 }
12001 } else {
12002 // No change in the view, so use the cached member bitmap2
12003 temp_dc.SelectObject(m_cached_chart_bm);
12004 b_save = false;
12005 }
12006 } else // cached bitmap is not yet valid
12007 {
12008 temp_dc.SelectObject(m_working_bm);
12009 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12010 chart_get_region);
12011 }
12012
12013 // Save the fully rendered quilt image as a wxBitmap member of this class
12014 if (b_save) {
12015 // if((m_cached_chart_bm.GetWidth() !=
12016 // svp.pix_width) ||
12017 // (m_cached_chart_bm.GetHeight() !=
12018 // svp.pix_height))
12019 // m_cached_chart_bm.Create(svp.pix_width,
12020 // svp.pix_height, -1); // target wxBitmap
12021 // is big enough
12022 wxMemoryDC scratch_dc_0;
12023 scratch_dc_0.SelectObject(m_cached_chart_bm);
12024 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12025
12026 scratch_dc_0.SelectObject(wxNullBitmap);
12027
12028 m_bm_cache_vp =
12029 VPoint; // save the ViewPort associated with the cached wxBitmap
12030 }
12031 }
12032
12033 else // quilted, rotated
12034 {
12035 temp_dc.SelectObject(m_working_bm);
12036 OCPNRegion chart_get_all_region(
12037 wxRect(0, 0, svp.pix_width, svp.pix_height));
12038 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12039 chart_get_all_region);
12040 }
12041
12042 AbstractPlatform::HideBusySpinner();
12043
12044 }
12045
12046 else // not quilted
12047 {
12048 if (!m_singleChart) {
12049 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12050 dc.Clear();
12051 return;
12052 }
12053
12054 if (!chart_get_region.IsEmpty()) {
12055 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12056 }
12057 }
12058
12059 if (temp_dc.IsOk()) {
12060 // Arrange to render the World Chart vector data behind the rendered
12061 // current chart so that uncovered canvas areas show at least the world
12062 // chart.
12063 OCPNRegion chartValidRegion;
12064 if (!VPoint.b_quilt) {
12065 // Make a region covering the current chart on the canvas
12066
12067 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12068 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12069 else {
12070 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12071 // require that the viewport passed here have pix_width and pix_height
12072 // set to the actual display, not the virtual (rv_rect) sizes
12073 // (the vector calculations require the virtual sizes in svp)
12074
12075 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12076 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12077 }
12078 } else
12079 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12080
12081 temp_dc.DestroyClippingRegion();
12082
12083 // Copy current chart region
12084 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12085
12086 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12087
12088 if (!backgroundRegion.IsEmpty()) {
12089 // Draw the Background Chart only in the areas NOT covered by the
12090 // current chart view
12091
12092 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12093 clipping regions with more than 1 rectangle so... */
12094 wxColour water = pWorldBackgroundChart->water;
12095 if (water.IsOk()) {
12096 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12097 temp_dc.SetBrush(wxBrush(water));
12098 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12099 while (upd.HaveRects()) {
12100 wxRect rect = upd.GetRect();
12101 temp_dc.DrawRectangle(rect);
12102 upd.NextRect();
12103 }
12104 }
12105 // Associate with temp_dc
12106 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12107 temp_dc.SetDeviceClippingRegion(*clip_region);
12108 delete clip_region;
12109
12110 ocpnDC bgdc(temp_dc);
12111 double r = VPoint.rotation;
12112 SetVPRotation(VPoint.skew);
12113
12114 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12115 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12116
12117 SetVPRotation(r);
12118 }
12119 } // temp_dc.IsOk();
12120
12121 wxMemoryDC *pChartDC = &temp_dc;
12122 wxMemoryDC rotd_dc;
12123
12124 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12125 // Can we use the current rotated image cache?
12126 if (!b_rcache_ok) {
12127#ifdef __WXMSW__
12128 wxMemoryDC tbase_dc;
12129 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12130 tbase_dc.SelectObject(bm_base);
12131 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12132 tbase_dc.SelectObject(wxNullBitmap);
12133#else
12134 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12135#endif
12136
12137 wxImage base_image;
12138 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12139
12140 // Use a local static image rotator to improve wxWidgets code profile
12141 // Especially, on GTK the wxRound and wxRealPoint functions are very
12142 // expensive.....
12143
12144 double angle = GetVP().skew - GetVP().rotation;
12145 wxImage ri;
12146 bool b_rot_ok = false;
12147 if (base_image.IsOk()) {
12148 ViewPort rot_vp = GetVP();
12149
12150 m_b_rot_hidef = false;
12151
12152 ri = Image_Rotate(
12153 base_image, angle,
12154 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12155 m_b_rot_hidef, &m_roffset);
12156
12157 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12158 (rot_vp.rotation == VPoint.rotation) &&
12159 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12160 rot_vp.IsValid() && (ri.IsOk())) {
12161 b_rot_ok = true;
12162 }
12163 }
12164
12165 if (b_rot_ok) {
12166 delete m_prot_bm;
12167 m_prot_bm = new wxBitmap(ri);
12168 }
12169
12170 m_roffset.x += VPoint.rv_rect.x;
12171 m_roffset.y += VPoint.rv_rect.y;
12172 }
12173
12174 if (m_prot_bm && m_prot_bm->IsOk()) {
12175 rotd_dc.SelectObject(*m_prot_bm);
12176 pChartDC = &rotd_dc;
12177 } else {
12178 pChartDC = &temp_dc;
12179 m_roffset = wxPoint(0, 0);
12180 }
12181 } else { // unrotated
12182 pChartDC = &temp_dc;
12183 m_roffset = wxPoint(0, 0);
12184 }
12185
12186 wxPoint offset = m_roffset;
12187
12188 // Save the PixelCache viewpoint for next time
12189 m_cache_vp = VPoint;
12190
12191 // Set up a scratch DC for overlay objects
12192 wxMemoryDC mscratch_dc;
12193 mscratch_dc.SelectObject(*pscratch_bm);
12194
12195 mscratch_dc.ResetBoundingBox();
12196 mscratch_dc.DestroyClippingRegion();
12197 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12198
12199 // Blit the externally invalidated areas of the chart onto the scratch dc
12200 wxRegionIterator upd(rgn_blit); // get the update rect list
12201 while (upd) {
12202 wxRect rect = upd.GetRect();
12203
12204 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12205 rect.x - offset.x, rect.y - offset.y);
12206 upd++;
12207 }
12208
12209 // If multi-canvas, indicate which canvas has keyboard focus
12210 // by drawing a simple blue bar at the top.
12211 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12212 if (this == wxWindow::FindFocus()) {
12213 g_focusCanvas = this;
12214
12215 wxColour colour = GetGlobalColor("BLUE4");
12216 mscratch_dc.SetPen(wxPen(colour));
12217 mscratch_dc.SetBrush(wxBrush(colour));
12218
12219 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12220 mscratch_dc.DrawRectangle(activeRect);
12221 }
12222 }
12223
12224 // Any MBtiles?
12225 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12226 unsigned int im = stackIndexArray.size();
12227 if (VPoint.b_quilt && im > 0) {
12228 std::vector<int> tiles_to_show;
12229 for (unsigned int is = 0; is < im; is++) {
12230 const ChartTableEntry &cte =
12231 ChartData->GetChartTableEntry(stackIndexArray[is]);
12232 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12233 continue;
12234 }
12235 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12236 tiles_to_show.push_back(stackIndexArray[is]);
12237 }
12238 }
12239
12240 if (tiles_to_show.size())
12241 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12242 }
12243
12244 // May get an unexpected OnPaint call while switching display modes
12245 // Guard for that.
12246 if (!g_bopengl) {
12247 ocpnDC scratch_dc(mscratch_dc);
12248 RenderAlertMessage(mscratch_dc, GetVP());
12249 }
12250
12251#if 0
12252 // quiting?
12253 if (g_bquiting) {
12254#ifdef ocpnUSE_DIBSECTION
12255 ocpnMemDC q_dc;
12256#else
12257 wxMemoryDC q_dc;
12258#endif
12259 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12260 q_dc.SelectObject(qbm);
12261
12262 // Get a copy of the screen
12263 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12264
12265 // Draw a rectangle over the screen with a stipple brush
12266 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12267 q_dc.SetBrush(qbr);
12268 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12269
12270 // Blit back into source
12271 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12272 wxCOPY);
12273
12274 q_dc.SelectObject(wxNullBitmap);
12275 }
12276#endif
12277
12278#if 0
12279 // It is possible that this two-step method may be reuired for some platforms.
12280 // So, retain in the code base to aid recovery if necessary
12281
12282 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12283 if( VPoint.b_quilt ) {
12284 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12285 ChartBase *chart = m_pQuilt->GetRefChart();
12286 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12287
12288 // Clear the text Global declutter list
12289 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12290 if(ChPI)
12291 ChPI->ClearPLIBTextList();
12292 else{
12293 if(ps52plib)
12294 ps52plib->ClearTextList();
12295 }
12296
12297 wxMemoryDC t_dc;
12298 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12299
12300 wxColor maskBackground = wxColour(1,0,0);
12301 t_dc.SelectObject( qbm );
12302 t_dc.SetBackground(wxBrush(maskBackground));
12303 t_dc.Clear();
12304
12305 // Copy the scratch DC into the new bitmap
12306 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12307
12308 // Render the text to the new bitmap
12309 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12310 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12311
12312 // Copy the new bitmap back to the scratch dc
12313 wxRegionIterator upd_final( ru );
12314 while( upd_final ) {
12315 wxRect rect = upd_final.GetRect();
12316 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12317 upd_final++;
12318 }
12319
12320 t_dc.SelectObject( wxNullBitmap );
12321 }
12322 }
12323 }
12324#endif
12325 // Direct rendering model...
12326 if (VPoint.b_quilt) {
12327 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12328 ChartBase *chart = m_pQuilt->GetRefChart();
12329 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12330 // Clear the text Global declutter list
12331 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12332 if (ChPI)
12333 ChPI->ClearPLIBTextList();
12334 else {
12335 if (ps52plib) ps52plib->ClearTextList();
12336 }
12337
12338 // Render the text directly to the scratch bitmap
12339 OCPNRegion chart_all_text_region(
12340 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12341
12342 if (g_bShowChartBar && m_Piano) {
12343 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12344 GetVP().pix_width, m_Piano->GetHeight());
12345
12346 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12347 if (!style->chartStatusWindowTransparent)
12348 chart_all_text_region.Subtract(chart_bar_rect);
12349 }
12350
12351 if (m_Compass && m_Compass->IsShown()) {
12352 wxRect compassRect = m_Compass->GetRect();
12353 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12354 chart_all_text_region.Subtract(compassRect);
12355 }
12356 }
12357
12358 mscratch_dc.DestroyClippingRegion();
12359
12360 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12361 chart_all_text_region);
12362 }
12363 }
12364 }
12365
12366 // Now that charts are fully rendered, apply the overlay objects as decals.
12367 ocpnDC scratch_dc(mscratch_dc);
12368 DrawOverlayObjects(scratch_dc, ru);
12369
12370 // And finally, blit the scratch dc onto the physical dc
12371 wxRegionIterator upd_final(rgn_blit);
12372 while (upd_final) {
12373 wxRect rect = upd_final.GetRect();
12374 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12375 rect.y);
12376 upd_final++;
12377 }
12378
12379 // Deselect the chart bitmap from the temp_dc, so that it will not be
12380 // destroyed in the temp_dc dtor
12381 temp_dc.SelectObject(wxNullBitmap);
12382 // And for the scratch bitmap
12383 mscratch_dc.SelectObject(wxNullBitmap);
12384
12385 dc.DestroyClippingRegion();
12386
12387 PaintCleanup();
12388}
12389
12390void ChartCanvas::PaintCleanup() {
12391 // Handle the current graphic window, if present
12392 if (m_inPinch) return;
12393
12394 if (pCwin) {
12395 pCwin->Show();
12396 if (m_bTCupdate) {
12397 pCwin->Refresh();
12398 pCwin->Update();
12399 }
12400 }
12401
12402 // And set flags for next time
12403 m_bTCupdate = false;
12404
12405 // Handle deferred WarpPointer
12406 if (warp_flag) {
12407 WarpPointer(warp_x, warp_y);
12408 warp_flag = false;
12409 }
12410
12411 // Start movement timers, this runs nearly immediately.
12412 // the reason we cannot simply call it directly is the
12413 // refresh events it emits may be blocked from this paint event
12414 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12415 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12416}
12417
12418#if 0
12419wxColour GetErrorGraphicColor(double val)
12420{
12421 /*
12422 double valm = wxMin(val_max, val);
12423
12424 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12425 unsigned char red = (unsigned char)(255 * (valm/val_max));
12426
12427 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12428
12429 hv.saturation = 1.0;
12430 hv.value = 1.0;
12431
12432 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12433 return wxColour(rv.red, rv.green, rv.blue);
12434 */
12435
12436 // HTML colors taken from NOAA WW3 Web representation
12437 wxColour c;
12438 if((val > 0) && (val < 1)) c.Set("#002ad9");
12439 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12440 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12441 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12442 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12443 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12444 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12445 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12446 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12447 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12448 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12449 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12450 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12451 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12452 else if((val >= 30) && (val < 36)) c.Set("#870000");
12453 else if((val >= 36) && (val < 42)) c.Set("#690000");
12454 else if((val >= 42) && (val < 48)) c.Set("#550000");
12455 else if( val >= 48) c.Set("#410000");
12456
12457 return c;
12458}
12459
12460void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12461{
12462 wxImage gr_image(vp->pix_width, vp->pix_height);
12463 gr_image.InitAlpha();
12464
12465 double maxval = -10000;
12466 double minval = 10000;
12467
12468 double rlat, rlon;
12469 double glat, glon;
12470
12471 GetCanvasPixPoint(0, 0, rlat, rlon);
12472
12473 for(int i=1; i < vp->pix_height-1; i++)
12474 {
12475 for(int j=0; j < vp->pix_width; j++)
12476 {
12477 // Reference mercator value
12478// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12479
12480 // Georef value
12481 GetCanvasPixPoint(j, i, glat, glon);
12482
12483 maxval = wxMax(maxval, (glat - rlat));
12484 minval = wxMin(minval, (glat - rlat));
12485
12486 }
12487 rlat = glat;
12488 }
12489
12490 GetCanvasPixPoint(0, 0, rlat, rlon);
12491 for(int i=1; i < vp->pix_height-1; i++)
12492 {
12493 for(int j=0; j < vp->pix_width; j++)
12494 {
12495 // Reference mercator value
12496// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12497
12498 // Georef value
12499 GetCanvasPixPoint(j, i, glat, glon);
12500
12501 double f = ((glat - rlat)-minval)/(maxval - minval);
12502
12503 double dy = (f * 40);
12504
12505 wxColour c = GetErrorGraphicColor(dy);
12506 unsigned char r = c.Red();
12507 unsigned char g = c.Green();
12508 unsigned char b = c.Blue();
12509
12510 gr_image.SetRGB(j, i, r,g,b);
12511 if((glat - rlat )!= 0)
12512 gr_image.SetAlpha(j, i, 128);
12513 else
12514 gr_image.SetAlpha(j, i, 255);
12515
12516 }
12517 rlat = glat;
12518 }
12519
12520 // Create a Bitmap
12521 wxBitmap *pbm = new wxBitmap(gr_image);
12522 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12523 pbm->SetMask(gr_mask);
12524
12525 pmdc->DrawBitmap(*pbm, 0,0);
12526
12527 delete pbm;
12528
12529}
12530
12531#endif
12532
12533void ChartCanvas::CancelMouseRoute() {
12534 m_routeState = 0;
12535 m_pMouseRoute = NULL;
12536 m_bDrawingRoute = false;
12537}
12538
12539int ChartCanvas::GetNextContextMenuId() {
12540 return CanvasMenuHandler::GetNextContextMenuId();
12541}
12542
12543bool ChartCanvas::SetCursor(const wxCursor &c) {
12544#ifdef ocpnUSE_GL
12545 if (g_bopengl && m_glcc)
12546 return m_glcc->SetCursor(c);
12547 else
12548#endif
12549 return wxWindow::SetCursor(c);
12550}
12551
12552void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12553 if (g_bquiting) return;
12554 // Keep the mouse position members up to date
12555 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12556
12557 // Retrigger the route leg popup timer
12558 // This handles the case when the chart is moving in auto-follow mode,
12559 // but no user mouse input is made. The timer handler may Hide() the
12560 // popup if the chart moved enough n.b. We use slightly longer oneshot
12561 // value to allow this method's Refresh() to complete before potentially
12562 // getting another Refresh() in the popup timer handler.
12563 if (!m_RolloverPopupTimer.IsRunning() &&
12564 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12565 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12566 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12567 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12568
12569#ifdef ocpnUSE_GL
12570 if (m_glcc && g_bopengl) {
12571 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12572 // overlay objects.
12573 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12574
12575 m_glcc->Refresh(eraseBackground,
12576 NULL); // We always are going to render the entire screen
12577 // anyway, so make
12578 // sure that the window managers understand the invalid area
12579 // is actually the entire client area.
12580
12581 // We need to selectively Refresh some child windows, if they are visible.
12582 // Note that some children are refreshed elsewhere on timer ticks, so don't
12583 // need attention here.
12584
12585 // Thumbnail chart
12586 if (pthumbwin && pthumbwin->IsShown()) {
12587 pthumbwin->Raise();
12588 pthumbwin->Refresh(false);
12589 }
12590
12591 // ChartInfo window
12592 if (m_pCIWin && m_pCIWin->IsShown()) {
12593 m_pCIWin->Raise();
12594 m_pCIWin->Refresh(false);
12595 }
12596
12597 // if(g_MainToolbar)
12598 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12599
12600 } else
12601#endif
12602 wxWindow::Refresh(eraseBackground, rect);
12603}
12604
12605void ChartCanvas::Update() {
12606 if (m_glcc && g_bopengl) {
12607#ifdef ocpnUSE_GL
12608 m_glcc->Update();
12609#endif
12610 } else
12611 wxWindow::Update();
12612}
12613
12614void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12615 if (!pemboss) return;
12616 int x = pemboss->x, y = pemboss->y;
12617 const double factor = 200;
12618
12619 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12620 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12621 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12622
12623 // Grab a snipped image out of the chart
12624 wxMemoryDC snip_dc;
12625 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12626 snip_dc.SelectObject(snip_bmp);
12627
12628 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12629 snip_dc.SelectObject(wxNullBitmap);
12630
12631 wxImage snip_img = snip_bmp.ConvertToImage();
12632
12633 // Apply Emboss map to the snip image
12634 unsigned char *pdata = snip_img.GetData();
12635 if (pdata) {
12636 for (int y = 0; y < pemboss->height; y++) {
12637 int map_index = (y * pemboss->width);
12638 for (int x = 0; x < pemboss->width; x++) {
12639 double val = (pemboss->pmap[map_index] * factor) / 256.;
12640
12641 int nred = (int)((*pdata) + val);
12642 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12643 *pdata++ = (unsigned char)nred;
12644
12645 int ngreen = (int)((*pdata) + val);
12646 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12647 *pdata++ = (unsigned char)ngreen;
12648
12649 int nblue = (int)((*pdata) + val);
12650 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12651 *pdata++ = (unsigned char)nblue;
12652
12653 map_index++;
12654 }
12655 }
12656 }
12657
12658 // Convert embossed snip to a bitmap
12659 wxBitmap emb_bmp(snip_img);
12660
12661 // Map to another memoryDC
12662 wxMemoryDC result_dc;
12663 result_dc.SelectObject(emb_bmp);
12664
12665 // Blit to target
12666 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12667
12668 result_dc.SelectObject(wxNullBitmap);
12669}
12670
12671emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12672 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12673
12674 if (GetQuiltMode()) {
12675 // disable Overzoom indicator for MBTiles
12676 int refIndex = GetQuiltRefChartdbIndex();
12677 if (refIndex >= 0) {
12678 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12679 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12680 if (current_type == CHART_TYPE_MBTILES) {
12681 ChartBase *pChart = m_pQuilt->GetRefChart();
12682 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12683 if (ptc) {
12684 zoom_factor = ptc->GetZoomFactor();
12685 }
12686 }
12687 }
12688
12689 if (zoom_factor <= 3.9) return NULL;
12690 } else {
12691 if (m_singleChart) {
12692 if (zoom_factor <= 3.9) return NULL;
12693 } else
12694 return NULL;
12695 }
12696
12697 if (m_pEM_OverZoom) {
12698 m_pEM_OverZoom->x = 4;
12699 m_pEM_OverZoom->y = 0;
12700 if (g_MainToolbar && IsPrimaryCanvas()) {
12701 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12702 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12703 }
12704 }
12705 return m_pEM_OverZoom;
12706}
12707
12708void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12709 GridDraw(dc);
12710
12711 // bool pluginOverlayRender = true;
12712 //
12713 // if(g_canvasConfig > 0){ // Multi canvas
12714 // if(IsPrimaryCanvas())
12715 // pluginOverlayRender = false;
12716 // }
12717
12718 g_overlayCanvas = this;
12719
12720 if (g_pi_manager) {
12721 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12722 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12724 }
12725
12726 AISDrawAreaNotices(dc, GetVP(), this);
12727
12728 wxDC *pdc = dc.GetDC();
12729 if (pdc) {
12730 pdc->DestroyClippingRegion();
12731 wxDCClipper(*pdc, ru);
12732 }
12733
12734 if (m_bShowNavobjects) {
12735 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12736 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12737 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12738 DrawAnchorWatchPoints(dc);
12739 } else {
12740 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12741 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12742 }
12743
12744 AISDraw(dc, GetVP(), this);
12745 ShipDraw(dc);
12746 AlertDraw(dc);
12747
12748 RenderVisibleSectorLights(dc);
12749
12750 RenderAllChartOutlines(dc, GetVP());
12751 RenderRouteLegs(dc);
12752 RenderShipToActive(dc, false);
12753 ScaleBarDraw(dc);
12754 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12755 if (g_pi_manager) {
12756 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12758 }
12759
12760 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12761 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12762
12763 if (g_pi_manager) {
12764 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12766 }
12767
12768 if (m_bShowTide) {
12769 RebuildTideSelectList(GetVP().GetBBox());
12770 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12771 }
12772
12773 if (m_bShowCurrent) {
12774 RebuildCurrentSelectList(GetVP().GetBBox());
12775 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12776 }
12777
12778 if (!g_PrintingInProgress) {
12779 if (IsPrimaryCanvas()) {
12780 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12781 }
12782
12783 if (IsPrimaryCanvas()) {
12784 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12785 }
12786
12787 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12788
12789 if (m_pTrackRolloverWin) {
12790 m_pTrackRolloverWin->Draw(dc);
12791 m_brepaint_piano = true;
12792 }
12793
12794 if (m_pRouteRolloverWin) {
12795 m_pRouteRolloverWin->Draw(dc);
12796 m_brepaint_piano = true;
12797 }
12798
12799 if (m_pAISRolloverWin) {
12800 m_pAISRolloverWin->Draw(dc);
12801 m_brepaint_piano = true;
12802 }
12803 if (m_brepaint_piano && g_bShowChartBar) {
12804 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12805 }
12806
12807 if (m_Compass) m_Compass->Paint(dc);
12808
12809 if (!g_CanvasHideNotificationIcon) {
12810 if (IsPrimaryCanvas()) {
12811 auto &noteman = NotificationManager::GetInstance();
12812 if (noteman.GetNotificationCount()) {
12813 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12814 if (m_notification_button->UpdateStatus()) Refresh();
12815 m_notification_button->Show(true);
12816 m_notification_button->Paint(dc);
12817 } else {
12818 m_notification_button->Show(false);
12819 }
12820 }
12821 }
12822 }
12823 if (g_pi_manager) {
12824 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12826 }
12827}
12828
12829emboss_data *ChartCanvas::EmbossDepthScale() {
12830 if (!m_bShowDepthUnits) return NULL;
12831
12832 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12833
12834 if (GetQuiltMode()) {
12835 wxString s = m_pQuilt->GetQuiltDepthUnit();
12836 s.MakeUpper();
12837 if (s == "FEET")
12838 depth_unit_type = DEPTH_UNIT_FEET;
12839 else if (s.StartsWith("FATHOMS"))
12840 depth_unit_type = DEPTH_UNIT_FATHOMS;
12841 else if (s.StartsWith("METERS"))
12842 depth_unit_type = DEPTH_UNIT_METERS;
12843 else if (s.StartsWith("METRES"))
12844 depth_unit_type = DEPTH_UNIT_METERS;
12845 else if (s.StartsWith("METRIC"))
12846 depth_unit_type = DEPTH_UNIT_METERS;
12847 else if (s.StartsWith("METER"))
12848 depth_unit_type = DEPTH_UNIT_METERS;
12849
12850 } else {
12851 if (m_singleChart) {
12852 depth_unit_type = m_singleChart->GetDepthUnitType();
12853 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12854 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12855 }
12856 }
12857
12858 emboss_data *ped = NULL;
12859 switch (depth_unit_type) {
12860 case DEPTH_UNIT_FEET:
12861 ped = m_pEM_Feet;
12862 break;
12863 case DEPTH_UNIT_METERS:
12864 ped = m_pEM_Meters;
12865 break;
12866 case DEPTH_UNIT_FATHOMS:
12867 ped = m_pEM_Fathoms;
12868 break;
12869 default:
12870 return NULL;
12871 }
12872
12873 ped->x = (GetVP().pix_width - ped->width);
12874
12875 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12876 wxRect r = m_Compass->GetRect();
12877 ped->y = r.y + r.height;
12878 } else {
12879 ped->y = 40;
12880 }
12881 return ped;
12882}
12883
12884void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12885 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12886 wxFont font;
12887 if (style->embossFont == wxEmptyString) {
12888 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12889 font = *dFont;
12890 font.SetPointSize(60);
12891 font.SetWeight(wxFONTWEIGHT_BOLD);
12892 } else
12893 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12894 wxFONTWEIGHT_BOLD, false, style->embossFont);
12895
12896 int emboss_width = 500;
12897 int emboss_height = 200;
12898
12899 // Free any existing emboss maps
12900 delete m_pEM_Feet;
12901 delete m_pEM_Meters;
12902 delete m_pEM_Fathoms;
12903
12904 // Create the 3 DepthUnit emboss map structures
12905 m_pEM_Feet =
12906 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12907 m_pEM_Meters =
12908 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12909 m_pEM_Fathoms =
12910 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12911}
12912
12913#define OVERZOOM_TEXT _("OverZoom")
12914
12915void ChartCanvas::SetOverzoomFont() {
12916 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12917 int w, h;
12918
12919 wxFont font;
12920 if (style->embossFont == wxEmptyString) {
12921 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12922 font = *dFont;
12923 font.SetPointSize(40);
12924 font.SetWeight(wxFONTWEIGHT_BOLD);
12925 } else
12926 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12927 wxFONTWEIGHT_BOLD, false, style->embossFont);
12928
12929 wxClientDC dc(this);
12930 dc.SetFont(font);
12931 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12932
12933 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12934 font.SetPointSize(font.GetPointSize() - 1);
12935 dc.SetFont(font);
12936 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12937 }
12938 m_overzoomFont = font;
12939 m_overzoomTextWidth = w;
12940 m_overzoomTextHeight = h;
12941}
12942
12943void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12944 delete m_pEM_OverZoom;
12945
12946 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12947 m_pEM_OverZoom =
12948 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12949 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12950}
12951
12952emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12953 int height, const wxString &str,
12954 ColorScheme cs) {
12955 int *pmap;
12956
12957 // Create a temporary bitmap
12958 wxBitmap bmp(width, height, -1);
12959
12960 // Create a memory DC
12961 wxMemoryDC temp_dc;
12962 temp_dc.SelectObject(bmp);
12963
12964 // Paint on it
12965 temp_dc.SetBackground(*wxWHITE_BRUSH);
12966 temp_dc.SetTextBackground(*wxWHITE);
12967 temp_dc.SetTextForeground(*wxBLACK);
12968
12969 temp_dc.Clear();
12970
12971 temp_dc.SetFont(font);
12972
12973 int str_w, str_h;
12974 temp_dc.GetTextExtent(str, &str_w, &str_h);
12975 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12976 temp_dc.DrawText(str, 1, 1);
12977
12978 // Deselect the bitmap
12979 temp_dc.SelectObject(wxNullBitmap);
12980
12981 // Convert bitmap the wxImage for manipulation
12982 wxImage img = bmp.ConvertToImage();
12983
12984 int image_width = str_w * 105 / 100;
12985 int image_height = str_h * 105 / 100;
12986 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12987 wxMin(image_height, img.GetHeight()));
12988 wxImage imgs = img.GetSubImage(r);
12989
12990 double val_factor;
12991 switch (cs) {
12992 case GLOBAL_COLOR_SCHEME_DAY:
12993 default:
12994 val_factor = 1;
12995 break;
12996 case GLOBAL_COLOR_SCHEME_DUSK:
12997 val_factor = .5;
12998 break;
12999 case GLOBAL_COLOR_SCHEME_NIGHT:
13000 val_factor = .25;
13001 break;
13002 }
13003
13004 int val;
13005 int index;
13006 const int w = imgs.GetWidth();
13007 const int h = imgs.GetHeight();
13008 pmap = (int *)calloc(w * h * sizeof(int), 1);
13009 // Create emboss map by differentiating the emboss image
13010 // and storing integer results in pmap
13011 // n.b. since the image is B/W, it is sufficient to check
13012 // one channel (i.e. red) only
13013 for (int y = 1; y < h - 1; y++) {
13014 for (int x = 1; x < w - 1; x++) {
13015 val =
13016 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13017 val = (int)(val * val_factor);
13018 index = (y * w) + x;
13019 pmap[index] = val;
13020 }
13021 }
13022
13023 emboss_data *pret = new emboss_data;
13024 pret->pmap = pmap;
13025 pret->width = w;
13026 pret->height = h;
13027
13028 return pret;
13029}
13030
13031void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13032 Track *active_track = NULL;
13033 for (Track *pTrackDraw : g_TrackList) {
13034 if (g_pActiveTrack == pTrackDraw) {
13035 active_track = pTrackDraw;
13036 continue;
13037 }
13038
13039 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13040 }
13041
13042 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13043}
13044
13045void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13046 Track *active_track = NULL;
13047 for (Track *pTrackDraw : g_TrackList) {
13048 if (g_pActiveTrack == pTrackDraw) {
13049 active_track = pTrackDraw;
13050 break;
13051 }
13052 }
13053 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13054}
13055
13056void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13057 Route *active_route = NULL;
13058 for (Route *pRouteDraw : *pRouteList) {
13059 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13060 active_route = pRouteDraw;
13061 continue;
13062 }
13063
13064 // if(m_canvasIndex == 1)
13065 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13066 }
13067
13068 // Draw any active or selected route (or track) last, so that is is always on
13069 // top
13070 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13071}
13072
13073void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13074 Route *active_route = NULL;
13075
13076 for (Route *pRouteDraw : *pRouteList) {
13077 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13078 active_route = pRouteDraw;
13079 break;
13080 }
13081 }
13082 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13083}
13084
13085void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13086 if (!pWayPointMan) return;
13087
13088 auto node = pWayPointMan->GetWaypointList()->begin();
13089
13090 while (node != pWayPointMan->GetWaypointList()->end()) {
13091 RoutePoint *pWP = *node;
13092 if (pWP) {
13093 if (pWP->m_bIsInRoute) {
13094 ++node;
13095 continue;
13096 }
13097
13098 /* technically incorrect... waypoint has bounding box */
13099 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13100 RoutePointGui(*pWP).Draw(dc, this, NULL);
13101 else {
13102 // Are Range Rings enabled?
13103 if (pWP->GetShowWaypointRangeRings() &&
13104 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13105 double factor = 1.00;
13106 if (pWP->GetWaypointRangeRingsStepUnits() ==
13107 1) // convert kilometers to NMi
13108 factor = 1 / 1.852;
13109
13110 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13111 pWP->GetWaypointRangeRingsStep() / 60.;
13112 radius *= 2; // Fudge factor
13113
13114 LLBBox radar_box;
13115 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13116 pWP->m_lat + radius, pWP->m_lon + radius);
13117 if (!BltBBox.IntersectOut(radar_box)) {
13118 RoutePointGui(*pWP).Draw(dc, this, NULL);
13119 }
13120 }
13121 }
13122 }
13123
13124 ++node;
13125 }
13126}
13127
13128void ChartCanvas::DrawBlinkObjects() {
13129 // All RoutePoints
13130 wxRect update_rect;
13131
13132 if (!pWayPointMan) return;
13133
13134 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13135 if (pWP) {
13136 if (pWP->m_bBlink) {
13137 update_rect.Union(pWP->CurrentRect_in_DC);
13138 }
13139 }
13140 }
13141 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13142}
13143
13144void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13145 // draw anchor watch rings, if activated
13146
13148 wxPoint r1, r2;
13149 wxPoint lAnchorPoint1, lAnchorPoint2;
13150 double lpp1 = 0.0;
13151 double lpp2 = 0.0;
13152 if (pAnchorWatchPoint1) {
13153 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13155 &lAnchorPoint1);
13156 }
13157 if (pAnchorWatchPoint2) {
13158 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13160 &lAnchorPoint2);
13161 }
13162
13163 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13164 wxPen ppPenr(GetGlobalColor("URED"), 2);
13165
13166 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13167 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13168 dc.SetBrush(*ppBrush);
13169
13170 if (lpp1 > 0) {
13171 dc.SetPen(ppPeng);
13172 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13173 }
13174
13175 if (lpp2 > 0) {
13176 dc.SetPen(ppPeng);
13177 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13178 }
13179
13180 if (lpp1 < 0) {
13181 dc.SetPen(ppPenr);
13182 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13183 }
13184
13185 if (lpp2 < 0) {
13186 dc.SetPen(ppPenr);
13187 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13188 }
13189 }
13190}
13191
13192double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13193 double lpp = 0.;
13194 wxPoint r1;
13195 wxPoint lAnchorPoint;
13196 double d1 = 0.0;
13197 double dabs;
13198 double tlat1, tlon1;
13199
13200 if (pAnchorWatchPoint) {
13201 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13202 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13203 dabs = fabs(d1 / 1852.);
13204 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13205 &tlat1, &tlon1);
13206 GetCanvasPointPix(tlat1, tlon1, &r1);
13207 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13208 &lAnchorPoint);
13209 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13210 pow((double)(lAnchorPoint.y - r1.y), 2));
13211
13212 // This is an entry watch
13213 if (d1 < 0) lpp = -lpp;
13214 }
13215 return lpp;
13216}
13217
13218//------------------------------------------------------------------------------------------
13219// Tides Support
13220//------------------------------------------------------------------------------------------
13221void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13222 if (!ptcmgr) return;
13223
13224 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13225
13226 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13227 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13228 double lon = pIDX->IDX_lon;
13229 double lat = pIDX->IDX_lat;
13230
13231 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13232 if ((type == 't') || (type == 'T')) {
13233 if (BBox.Contains(lat, lon)) {
13234 // Manage the point selection list
13235 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13236 }
13237 }
13238 }
13239}
13240
13241void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13242 if (!ptcmgr) return;
13243
13244 wxDateTime this_now = gTimeSource;
13245 bool cur_time = !gTimeSource.IsValid();
13246 if (cur_time) this_now = wxDateTime::Now();
13247 time_t t_this_now = this_now.GetTicks();
13248
13249 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13250 wxPENSTYLE_SOLID);
13251 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13252 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13253 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13254 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13255
13256 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13257 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13258 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13259 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13260 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13261 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13262
13263 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13264 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13265 int font_size = wxMax(10, dFont->GetPointSize());
13266 font_size /= g_Platform->GetDisplayDIPMult(this);
13267 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13268 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13269 false, dFont->GetFaceName());
13270
13271 dc.SetPen(*pblack_pen);
13272 dc.SetBrush(*pgreen_brush);
13273
13274 wxBitmap bm;
13275 switch (m_cs) {
13276 case GLOBAL_COLOR_SCHEME_DAY:
13277 bm = m_bmTideDay;
13278 break;
13279 case GLOBAL_COLOR_SCHEME_DUSK:
13280 bm = m_bmTideDusk;
13281 break;
13282 case GLOBAL_COLOR_SCHEME_NIGHT:
13283 bm = m_bmTideNight;
13284 break;
13285 default:
13286 bm = m_bmTideDay;
13287 break;
13288 }
13289
13290 int bmw = bm.GetWidth();
13291 int bmh = bm.GetHeight();
13292
13293 float scale_factor = 1.0;
13294
13295 // Set the onscreen size of the symbol
13296 // Compensate for various display resolutions
13297 float icon_pixelRefDim = 45;
13298
13299 // Tidal report graphic is scaled by the text size of the label in use
13300 wxScreenDC sdc;
13301 int height;
13302 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13303 height *= g_Platform->GetDisplayDIPMult(this);
13304 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13305
13306 scale_factor *= pix_factor;
13307
13308 float user_scale_factor = g_ChartScaleFactorExp;
13309 if (g_ChartScaleFactorExp > 1.0)
13310 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13311 1.2; // soften the scale factor a bit
13312
13313 scale_factor *= user_scale_factor;
13314 scale_factor *= GetContentScaleFactor();
13315
13316 {
13317 double marge = 0.05;
13318 std::vector<LLBBox> drawn_boxes;
13319 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13320 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13321
13322 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13323 if ((type == 't') || (type == 'T')) // only Tides
13324 {
13325 double lon = pIDX->IDX_lon;
13326 double lat = pIDX->IDX_lat;
13327
13328 if (BBox.ContainsMarge(lat, lon, marge)) {
13329 // Avoid drawing detailed graphic for duplicate tide stations
13330 if (GetVP().chart_scale < 500000) {
13331 bool bdrawn = false;
13332 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13333 if (drawn_boxes[i].Contains(lat, lon)) {
13334 bdrawn = true;
13335 break;
13336 }
13337 }
13338 if (bdrawn) continue; // the station loop
13339
13340 LLBBox this_box;
13341 this_box.Set(lat, lon, lat, lon);
13342 this_box.EnLarge(.005);
13343 drawn_boxes.push_back(this_box);
13344 }
13345
13346 wxPoint r;
13347 GetCanvasPointPix(lat, lon, &r);
13348 // draw standard icons
13349 if (GetVP().chart_scale > 500000) {
13350 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13351 }
13352 // draw "extended" icons
13353 else {
13354 dc.SetFont(*plabelFont);
13355 {
13356 {
13357 float val, nowlev;
13358 float ltleve = 0.;
13359 float htleve = 0.;
13360 time_t tctime;
13361 time_t lttime = 0;
13362 time_t httime = 0;
13363 bool wt;
13364 // define if flood or ebb in the last ten minutes and verify if
13365 // data are useable
13366 if (ptcmgr->GetTideFlowSens(
13367 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13368 pIDX->IDX_rec_num, nowlev, val, wt)) {
13369 // search forward the first HW or LW near "now" ( starting at
13370 // "now" - ten minutes )
13371 ptcmgr->GetHightOrLowTide(
13372 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13373 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13374 wt, pIDX->IDX_rec_num, val, tctime);
13375 if (wt) {
13376 httime = tctime;
13377 htleve = val;
13378 } else {
13379 lttime = tctime;
13380 ltleve = val;
13381 }
13382 wt = !wt;
13383
13384 // then search opposite tide near "now"
13385 if (tctime > t_this_now) // search backward
13386 ptcmgr->GetHightOrLowTide(
13387 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13388 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13389 pIDX->IDX_rec_num, val, tctime);
13390 else
13391 // or search forward
13392 ptcmgr->GetHightOrLowTide(
13393 t_this_now, FORWARD_TEN_MINUTES_STEP,
13394 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13395 val, tctime);
13396 if (wt) {
13397 httime = tctime;
13398 htleve = val;
13399 } else {
13400 lttime = tctime;
13401 ltleve = val;
13402 }
13403
13404 // draw the tide rectangle:
13405
13406 // tide icon rectangle has default pre-scaled width = 12 ,
13407 // height = 45
13408 int width = (int)(12 * scale_factor + 0.5);
13409 int height = (int)(45 * scale_factor + 0.5);
13410 int linew = wxMax(1, (int)(scale_factor));
13411 int xDraw = r.x - (width / 2);
13412 int yDraw = r.y - (height / 2);
13413
13414 // process tide state ( %height and flow sens )
13415 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13416 int hs = (httime > lttime) ? -4 : 4;
13417 hs *= (int)(scale_factor + 0.5);
13418 if (ts > 0.995 || ts < 0.005) hs = 0;
13419 int ht_y = (int)(height * ts);
13420
13421 // draw yellow tide rectangle outlined in black
13422 pblack_pen->SetWidth(linew);
13423 dc.SetPen(*pblack_pen);
13424 dc.SetBrush(*pyelo_brush);
13425 dc.DrawRectangle(xDraw, yDraw, width, height);
13426
13427 // draw blue rectangle as water height, smaller in width than
13428 // yellow rectangle
13429 dc.SetPen(*pblue_pen);
13430 dc.SetBrush(*pblue_brush);
13431 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13432 (width - (4 * linew)), height - ht_y);
13433
13434 // draw sens arrows (ensure they are not "under-drawn" by top
13435 // line of blue rectangle )
13436 int hl;
13437 wxPoint arrow[3];
13438 arrow[0].x = xDraw + 2 * linew;
13439 arrow[1].x = xDraw + width / 2;
13440 arrow[2].x = xDraw + width - 2 * linew;
13441 pyelo_pen->SetWidth(linew);
13442 pblue_pen->SetWidth(linew);
13443 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13444 {
13445 hl = (int)(height * 0.25) + yDraw;
13446 arrow[0].y = hl;
13447 arrow[1].y = hl + hs;
13448 arrow[2].y = hl;
13449 if (ts < 0.15)
13450 dc.SetPen(*pyelo_pen);
13451 else
13452 dc.SetPen(*pblue_pen);
13453 dc.DrawLines(3, arrow);
13454 }
13455 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13456 {
13457 hl = (int)(height * 0.5) + yDraw;
13458 arrow[0].y = hl;
13459 arrow[1].y = hl + hs;
13460 arrow[2].y = hl;
13461 if (ts < 0.40)
13462 dc.SetPen(*pyelo_pen);
13463 else
13464 dc.SetPen(*pblue_pen);
13465 dc.DrawLines(3, arrow);
13466 }
13467 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13468 {
13469 hl = (int)(height * 0.75) + yDraw;
13470 arrow[0].y = hl;
13471 arrow[1].y = hl + hs;
13472 arrow[2].y = hl;
13473 if (ts < 0.65)
13474 dc.SetPen(*pyelo_pen);
13475 else
13476 dc.SetPen(*pblue_pen);
13477 dc.DrawLines(3, arrow);
13478 }
13479 // draw tide level text
13480 wxString s;
13481 s.Printf("%3.1f", nowlev);
13482 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13483 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13484 int wx1;
13485 dc.GetTextExtent(s, &wx1, NULL);
13486 wx1 *= g_Platform->GetDisplayDIPMult(this);
13487 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13488 }
13489 }
13490 }
13491 }
13492 }
13493 }
13494 }
13495 }
13496}
13497
13498//------------------------------------------------------------------------------------------
13499// Currents Support
13500//------------------------------------------------------------------------------------------
13501
13502void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13503 if (!ptcmgr) return;
13504
13505 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13506
13507 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13508 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13509 double lon = pIDX->IDX_lon;
13510 double lat = pIDX->IDX_lat;
13511
13512 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13513 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13514 if ((BBox.Contains(lat, lon))) {
13515 // Manage the point selection list
13516 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13517 }
13518 }
13519 }
13520}
13521
13522void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13523 if (!ptcmgr) return;
13524
13525 float tcvalue, dir;
13526 bool bnew_val;
13527 char sbuf[20];
13528 wxFont *pTCFont;
13529 double lon_last = 0.;
13530 double lat_last = 0.;
13531 // arrow size for Raz Blanchard : 12 knots north
13532 double marge = 0.2;
13533 bool cur_time = !gTimeSource.IsValid();
13534
13535 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13536 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13537
13538 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13539 wxPENSTYLE_SOLID);
13540 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13541 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13542 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13543 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13544 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13545 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13546 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13547 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13548
13549 double skew_angle = GetVPRotation();
13550
13551 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13552 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13553 int font_size = wxMax(10, dFont->GetPointSize());
13554 font_size /= g_Platform->GetDisplayDIPMult(this);
13555 pTCFont = FontMgr::Get().FindOrCreateFont(
13556 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13557 false, dFont->GetFaceName());
13558
13559 float scale_factor = 1.0;
13560
13561 // Set the onscreen size of the symbol
13562 // Current report graphic is scaled by the text size of the label in use
13563 wxScreenDC sdc;
13564 int height;
13565 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13566 height *= g_Platform->GetDisplayDIPMult(this);
13567 float nominal_icon_size_pixels = 15;
13568 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13569
13570 scale_factor *= pix_factor;
13571
13572 float user_scale_factor = g_ChartScaleFactorExp;
13573 if (g_ChartScaleFactorExp > 1.0)
13574 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13575 1.2; // soften the scale factor a bit
13576
13577 scale_factor *= user_scale_factor;
13578
13579 scale_factor *= GetContentScaleFactor();
13580
13581 {
13582 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13583 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13584 double lon = pIDX->IDX_lon;
13585 double lat = pIDX->IDX_lat;
13586
13587 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13588 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13589 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13590 wxPoint r;
13591 GetCanvasPointPix(lat, lon, &r);
13592
13593 wxPoint d[4]; // points of a diamond at the current station location
13594 int dd = (int)(5.0 * scale_factor + 0.5);
13595 d[0].x = r.x;
13596 d[0].y = r.y + dd;
13597 d[1].x = r.x + dd;
13598 d[1].y = r.y;
13599 d[2].x = r.x;
13600 d[2].y = r.y - dd;
13601 d[3].x = r.x - dd;
13602 d[3].y = r.y;
13603
13604 if (1) {
13605 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13606 dc.SetPen(*pblack_pen);
13607 dc.SetBrush(*porange_brush);
13608 dc.DrawPolygon(4, d);
13609
13610 if (type == 'C') {
13611 dc.SetBrush(*pblack_brush);
13612 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13613 }
13614
13615 if (GetVP().chart_scale < 1000000) {
13616 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13617 continue;
13618 } else
13619 continue;
13620
13621 if (1 /*type == 'c'*/) {
13622 {
13623 // Get the display pixel location of the current station
13624 int pixxc, pixyc;
13625 pixxc = r.x;
13626 pixyc = r.y;
13627
13628 // Adjust drawing size using logarithmic scale. tcvalue is
13629 // current in knots
13630 double a1 = fabs(tcvalue) * 10.;
13631 // Current values <= 0.1 knot will have no arrow
13632 a1 = wxMax(1.0, a1);
13633 double a2 = log10(a1);
13634
13635 float cscale = scale_factor * a2 * 0.3;
13636
13637 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13638 dc.SetPen(*porange_pen);
13639 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13640 cscale);
13641 // Draw text, if enabled
13642
13643 if (bDrawCurrentValues) {
13644 dc.SetFont(*pTCFont);
13645 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13646 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13647 }
13648 }
13649 } // scale
13650 }
13651 /* This is useful for debugging the TC database
13652 else
13653 {
13654 dc.SetPen ( *porange_pen );
13655 dc.SetBrush ( *pgray_brush );
13656 dc.DrawPolygon ( 4, d );
13657 }
13658 */
13659 }
13660 lon_last = lon;
13661 lat_last = lat;
13662 }
13663 }
13664 }
13665}
13666
13667void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13668 ShowSingleTideDialog(x, y, pvIDX);
13669}
13670
13671void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13672 if (!pvIDX) return; // Validate input
13673
13674 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13675
13676 // Check if a tide dialog is already open and visible
13677 if (pCwin && pCwin->IsShown()) {
13678 // Same tide station: bring existing dialog to front (preserves user
13679 // context)
13680 if (pCwin->GetCurrentIDX() == pNewIDX) {
13681 pCwin->Raise();
13682 pCwin->SetFocus();
13683
13684 // Provide subtle visual feedback that dialog is already open
13685 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13686 return;
13687 }
13688
13689 // Different tide station: close current dialog before opening new one
13690 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13691 }
13692
13693 if (pCwin) {
13694 // This shouldn't happen but ensures clean state
13695 pCwin->Destroy();
13696 pCwin = NULL;
13697 }
13698
13699 // Create and display new tide dialog
13700 pCwin = new TCWin(this, x, y, pvIDX);
13701
13702 // Ensure the dialog is properly shown and focused
13703 if (pCwin) {
13704 pCwin->Show();
13705 pCwin->Raise();
13706 pCwin->SetFocus();
13707 }
13708}
13709
13710bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13711
13713 if (pCwin) {
13714 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13715 }
13716}
13717
13718#define NUM_CURRENT_ARROW_POINTS 9
13719static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13720 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13721 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13722 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13723
13724void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13725 double scale) {
13726 if (scale > 1e-2) {
13727 float sin_rot = sin(rot_angle * PI / 180.);
13728 float cos_rot = cos(rot_angle * PI / 180.);
13729
13730 // Move to the first point
13731
13732 float xt = CurrentArrowArray[0].x;
13733 float yt = CurrentArrowArray[0].y;
13734
13735 float xp = (xt * cos_rot) - (yt * sin_rot);
13736 float yp = (xt * sin_rot) + (yt * cos_rot);
13737 int x1 = (int)(xp * scale);
13738 int y1 = (int)(yp * scale);
13739
13740 // Walk thru the point list
13741 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13742 xt = CurrentArrowArray[ip].x;
13743 yt = CurrentArrowArray[ip].y;
13744
13745 float xp = (xt * cos_rot) - (yt * sin_rot);
13746 float yp = (xt * sin_rot) + (yt * cos_rot);
13747 int x2 = (int)(xp * scale);
13748 int y2 = (int)(yp * scale);
13749
13750 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13751
13752 x1 = x2;
13753 y1 = y2;
13754 }
13755 }
13756}
13757
13758wxString ChartCanvas::FindValidUploadPort() {
13759 wxString port;
13760 // Try to use the saved persistent upload port first
13761 if (!g_uploadConnection.IsEmpty() &&
13762 g_uploadConnection.StartsWith("Serial")) {
13763 port = g_uploadConnection;
13764 }
13765
13766 else {
13767 // If there is no persistent upload port recorded (yet)
13768 // then use the first available serial connection which has output defined.
13769 for (auto *cp : TheConnectionParams()) {
13770 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13771 port << "Serial:" << cp->Port;
13772 }
13773 }
13774 return port;
13775}
13776
13777void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13778 if (!win) return;
13779
13780 if (NULL == g_pais_query_dialog_active) {
13781 int pos_x = g_ais_query_dialog_x;
13782 int pos_y = g_ais_query_dialog_y;
13783
13784 if (g_pais_query_dialog_active) {
13785 g_pais_query_dialog_active->Destroy();
13786 g_pais_query_dialog_active = new AISTargetQueryDialog();
13787 } else {
13788 g_pais_query_dialog_active = new AISTargetQueryDialog();
13789 }
13790
13791 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13792 wxPoint(pos_x, pos_y));
13793
13794 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13795 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13796 g_pais_query_dialog_active->SetMMSI(mmsi);
13797 g_pais_query_dialog_active->UpdateText();
13798 wxSize sz = g_pais_query_dialog_active->GetSize();
13799
13800 bool b_reset_pos = false;
13801#ifdef __WXMSW__
13802 // Support MultiMonitor setups which an allow negative window positions.
13803 // If the requested window title bar does not intersect any installed
13804 // monitor, then default to simple primary monitor positioning.
13805 RECT frame_title_rect;
13806 frame_title_rect.left = pos_x;
13807 frame_title_rect.top = pos_y;
13808 frame_title_rect.right = pos_x + sz.x;
13809 frame_title_rect.bottom = pos_y + 30;
13810
13811 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13812 b_reset_pos = true;
13813#else
13814
13815 // Make sure drag bar (title bar) of window intersects wxClient Area of
13816 // screen, with a little slop...
13817 wxRect window_title_rect; // conservative estimate
13818 window_title_rect.x = pos_x;
13819 window_title_rect.y = pos_y;
13820 window_title_rect.width = sz.x;
13821 window_title_rect.height = 30;
13822
13823 wxRect ClientRect = wxGetClientDisplayRect();
13824 ClientRect.Deflate(
13825 60, 60); // Prevent the new window from being too close to the edge
13826 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13827
13828#endif
13829
13830 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13831
13832 } else {
13833 g_pais_query_dialog_active->SetMMSI(mmsi);
13834 g_pais_query_dialog_active->UpdateText();
13835 }
13836
13837 g_pais_query_dialog_active->Show();
13838}
13839
13840void ChartCanvas::ToggleCanvasQuiltMode() {
13841 bool cur_mode = GetQuiltMode();
13842
13843 if (!GetQuiltMode())
13844 SetQuiltMode(true);
13845 else if (GetQuiltMode()) {
13846 SetQuiltMode(false);
13847 g_sticky_chart = GetQuiltReferenceChartIndex();
13848 }
13849
13850 if (cur_mode != GetQuiltMode()) {
13851 SetupCanvasQuiltMode();
13852 DoCanvasUpdate();
13853 InvalidateGL();
13854 Refresh();
13855 }
13856 // TODO What to do about this?
13857 // g_bQuiltEnable = GetQuiltMode();
13858
13859 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13860 if (ps52plib) ps52plib->GenerateStateHash();
13861
13862 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13863 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13864}
13865
13866void ChartCanvas::DoCanvasStackDelta(int direction) {
13867 if (!GetQuiltMode()) {
13868 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13869 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13870 if ((current_stack_index + direction) < 0) return;
13871
13872 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13873 int new_dbIndex =
13874 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13875
13876 if (IsChartQuiltableRef(new_dbIndex)) {
13877 ToggleCanvasQuiltMode();
13878 SelectQuiltRefdbChart(new_dbIndex);
13879 m_bpersistent_quilt = false;
13880 }
13881 } else {
13882 SelectChartFromStack(current_stack_index + direction);
13883 }
13884 } else {
13885 std::vector<int> piano_chart_index_array =
13886 GetQuiltExtendedStackdbIndexArray();
13887 int refdb = GetQuiltRefChartdbIndex();
13888
13889 // Find the ref chart in the stack
13890 int current_index = -1;
13891 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13892 if (refdb == piano_chart_index_array[i]) {
13893 current_index = i;
13894 break;
13895 }
13896 }
13897 if (current_index == -1) return;
13898
13899 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13900 int target_family = ctet.GetChartFamily();
13901
13902 int new_index = -1;
13903 int check_index = current_index + direction;
13904 bool found = false;
13905 int check_dbIndex = -1;
13906 int new_dbIndex = -1;
13907
13908 // When quilted. switch within the same chart family
13909 while (!found &&
13910 (unsigned int)check_index < piano_chart_index_array.size() &&
13911 (check_index >= 0)) {
13912 check_dbIndex = piano_chart_index_array[check_index];
13913 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13914 if (target_family == cte.GetChartFamily()) {
13915 found = true;
13916 new_index = check_index;
13917 new_dbIndex = check_dbIndex;
13918 break;
13919 }
13920
13921 check_index += direction;
13922 }
13923
13924 if (!found) return;
13925
13926 if (!IsChartQuiltableRef(new_dbIndex)) {
13927 ToggleCanvasQuiltMode();
13928 SelectdbChart(new_dbIndex);
13929 m_bpersistent_quilt = true;
13930 } else {
13931 SelectQuiltRefChart(new_index);
13932 }
13933 }
13934
13935 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13936 // (checkmarks etc)
13937 SetQuiltChartHiLiteIndex(-1);
13938
13939 ReloadVP();
13940}
13941
13942//--------------------------------------------------------------------------------------------------------
13943//
13944// Toolbar support
13945//
13946//--------------------------------------------------------------------------------------------------------
13947
13948void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13949 // Handle the per-canvas toolbar clicks here
13950
13951 switch (event.GetId()) {
13952 case ID_ZOOMIN: {
13953 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13954 break;
13955 }
13956
13957 case ID_ZOOMOUT: {
13958 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13959 break;
13960 }
13961
13962 case ID_STKUP:
13963 DoCanvasStackDelta(1);
13964 DoCanvasUpdate();
13965 break;
13966
13967 case ID_STKDN:
13968 DoCanvasStackDelta(-1);
13969 DoCanvasUpdate();
13970 break;
13971
13972 case ID_FOLLOW: {
13973 TogglebFollow();
13974 break;
13975 }
13976
13977 case ID_CURRENT: {
13978 ShowCurrents(!GetbShowCurrent());
13979 ReloadVP();
13980 Refresh(false);
13981 break;
13982 }
13983
13984 case ID_TIDE: {
13985 ShowTides(!GetbShowTide());
13986 ReloadVP();
13987 Refresh(false);
13988 break;
13989 }
13990
13991 case ID_ROUTE: {
13992 if (0 == m_routeState) {
13993 StartRoute();
13994 } else {
13995 FinishRoute();
13996 }
13997
13998#ifdef __ANDROID__
13999 androidSetRouteAnnunciator(m_routeState == 1);
14000#endif
14001 break;
14002 }
14003
14004 case ID_AIS: {
14005 SetAISCanvasDisplayStyle(-1);
14006 break;
14007 }
14008
14009 default:
14010 break;
14011 }
14012
14013 // And then let gFrame handle the rest....
14014 event.Skip();
14015}
14016
14017void ChartCanvas::SetShowAIS(bool show) {
14018 m_bShowAIS = show;
14019 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14020 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14021}
14022
14023void ChartCanvas::SetAttenAIS(bool show) {
14024 m_bShowAISScaled = show;
14025 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14026 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14027}
14028
14029void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14030 // make some arrays to hold the dfferences between cycle steps
14031 // show all, scaled, hide all
14032 bool bShowAIS_Array[3] = {true, true, false};
14033 bool bShowScaled_Array[3] = {false, true, true};
14034 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14035 _("Attenuate less critical AIS targets"),
14036 _("Hide AIS Targets")};
14037 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14038 int ArraySize = 3;
14039 int AIS_Toolbar_Switch = 0;
14040 if (StyleIndx == -1) { // -1 means coming from toolbar button
14041 // find current state of switch
14042 for (int i = 1; i < ArraySize; i++) {
14043 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14044 (bShowScaled_Array[i] == m_bShowAISScaled))
14045 AIS_Toolbar_Switch = i;
14046 }
14047 AIS_Toolbar_Switch++; // we did click so continu with next item
14048 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14049 AIS_Toolbar_Switch++;
14050
14051 } else { // coming from menu bar.
14052 AIS_Toolbar_Switch = StyleIndx;
14053 }
14054 // make sure we are not above array
14055 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14056
14057 int AIS_Toolbar_Switch_Next =
14058 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14059 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14060 AIS_Toolbar_Switch_Next++;
14061 if (AIS_Toolbar_Switch_Next >= ArraySize)
14062 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14063
14064 // Set found values to global and member variables
14065 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14066 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14067}
14068
14069void ChartCanvas::TouchAISToolActive() {}
14070
14071void ChartCanvas::UpdateAISTBTool() {}
14072
14073//---------------------------------------------------------------------------------
14074//
14075// Compass/GPS status icon support
14076//
14077//---------------------------------------------------------------------------------
14078
14079void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14080 // Look for change in overlap or positions
14081 bool b_update = false;
14082 int cc1_edge_comp = 2;
14083 wxRect rect = m_Compass->GetRect();
14084 wxSize parent_size = GetSize();
14085
14086 parent_size *= m_displayScale;
14087
14088 // check to see if it would overlap if it was in its home position (upper
14089 // right)
14090 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14091 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14092 wxRect compass_rect(compass_pt, rect.GetSize());
14093
14094 m_Compass->Move(compass_pt);
14095
14096 if (m_Compass && m_Compass->IsShown())
14097 m_Compass->UpdateStatus(b_force_new | b_update);
14098
14099 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14100 scaler = wxMax(scaler, 1.0);
14101 wxPoint note_point = wxPoint(
14102 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14103 if (m_notification_button) {
14104 m_notification_button->Move(note_point);
14105 m_notification_button->UpdateStatus();
14106 }
14107
14108 if (b_force_new | b_update) Refresh();
14109}
14110
14111void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14112 ChartTypeEnum New_Type,
14113 ChartFamilyEnum New_Family) {
14114 if (!GetpCurrentStack()) return;
14115 if (!ChartData) return;
14116
14117 if (index < GetpCurrentStack()->nEntry) {
14118 // Open the new chart
14119 ChartBase *pTentative_Chart;
14120 pTentative_Chart = ChartData->OpenStackChartConditional(
14121 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14122
14123 if (pTentative_Chart) {
14124 if (m_singleChart) m_singleChart->Deactivate();
14125
14126 m_singleChart = pTentative_Chart;
14127 m_singleChart->Activate();
14128
14129 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14130 GetpCurrentStack(), m_singleChart->GetFullPath());
14131 }
14132
14133 // Setup the view
14134 double zLat, zLon;
14135 if (m_bFollow) {
14136 zLat = gLat;
14137 zLon = gLon;
14138 } else {
14139 zLat = m_vLat;
14140 zLon = m_vLon;
14141 }
14142
14143 double best_scale_ppm = GetBestVPScale(m_singleChart);
14144 double rotation = GetVPRotation();
14145 double oldskew = GetVPSkew();
14146 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14147
14148 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14149 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14150 if (fabs(newskew) > 0.0001) rotation = newskew;
14151 }
14152
14153 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14154
14155 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14156 }
14157
14158 // refresh Piano
14159 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14160 if (idx < 0) return;
14161
14162 std::vector<int> piano_active_chart_index_array;
14163 piano_active_chart_index_array.push_back(
14164 GetpCurrentStack()->GetCurrentEntrydbIndex());
14165 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14166}
14167
14168void ChartCanvas::SelectdbChart(int dbindex) {
14169 if (!GetpCurrentStack()) return;
14170 if (!ChartData) return;
14171
14172 if (dbindex >= 0) {
14173 // Open the new chart
14174 ChartBase *pTentative_Chart;
14175 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14176
14177 if (pTentative_Chart) {
14178 if (m_singleChart) m_singleChart->Deactivate();
14179
14180 m_singleChart = pTentative_Chart;
14181 m_singleChart->Activate();
14182
14183 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14184 GetpCurrentStack(), m_singleChart->GetFullPath());
14185 }
14186
14187 // Setup the view
14188 double zLat, zLon;
14189 if (m_bFollow) {
14190 zLat = gLat;
14191 zLon = gLon;
14192 } else {
14193 zLat = m_vLat;
14194 zLon = m_vLon;
14195 }
14196
14197 double best_scale_ppm = GetBestVPScale(m_singleChart);
14198
14199 if (m_singleChart)
14200 SetViewPoint(zLat, zLon, best_scale_ppm,
14201 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14202
14203 // SetChartUpdatePeriod( );
14204
14205 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14206 }
14207
14208 // TODO refresh_Piano();
14209}
14210
14211void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14212 double target_scale = GetVP().view_scale_ppm;
14213
14214 if (!GetQuiltMode()) {
14215 if (GetpCurrentStack()) {
14216 int stack_index = -1;
14217 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14218 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14219 if (check_dbIndex < 0) continue;
14220 const ChartTableEntry &cte =
14221 ChartData->GetChartTableEntry(check_dbIndex);
14222 if (type == cte.GetChartType()) {
14223 stack_index = i;
14224 break;
14225 } else if (family == cte.GetChartFamily()) {
14226 stack_index = i;
14227 break;
14228 }
14229 }
14230
14231 if (stack_index >= 0) {
14232 SelectChartFromStack(stack_index);
14233 }
14234 }
14235 } else {
14236 int sel_dbIndex = -1;
14237 std::vector<int> piano_chart_index_array =
14238 GetQuiltExtendedStackdbIndexArray();
14239 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14240 int check_dbIndex = piano_chart_index_array[i];
14241 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14242 if (type == cte.GetChartType()) {
14243 if (IsChartQuiltableRef(check_dbIndex)) {
14244 sel_dbIndex = check_dbIndex;
14245 break;
14246 }
14247 } else if (family == cte.GetChartFamily()) {
14248 if (IsChartQuiltableRef(check_dbIndex)) {
14249 sel_dbIndex = check_dbIndex;
14250 break;
14251 }
14252 }
14253 }
14254
14255 if (sel_dbIndex >= 0) {
14256 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14257 // Re-qualify the quilt reference chart selection
14258 AdjustQuiltRefChart();
14259 }
14260
14261 // Now reset the scale to the target...
14262 SetVPScale(target_scale);
14263 }
14264
14265 SetQuiltChartHiLiteIndex(-1);
14266
14267 ReloadVP();
14268}
14269
14270bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14271 return std::find(m_tile_yesshow_index_array.begin(),
14272 m_tile_yesshow_index_array.end(),
14273 index) != m_tile_yesshow_index_array.end();
14274}
14275
14276bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14277 return std::find(m_tile_noshow_index_array.begin(),
14278 m_tile_noshow_index_array.end(),
14279 index) != m_tile_noshow_index_array.end();
14280}
14281
14282void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14283 if (std::find(m_tile_noshow_index_array.begin(),
14284 m_tile_noshow_index_array.end(),
14285 index) == m_tile_noshow_index_array.end()) {
14286 m_tile_noshow_index_array.push_back(index);
14287 }
14288}
14289
14290//-------------------------------------------------------------------------------------------------------
14291//
14292// Piano support
14293//
14294//-------------------------------------------------------------------------------------------------------
14295
14296void ChartCanvas::HandlePianoClick(
14297 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14298 if (g_options && g_options->IsShown())
14299 return; // Piano might be invalid due to chartset updates.
14300 if (!m_pCurrentStack) return;
14301 if (!ChartData) return;
14302
14303 // stop movement or on slow computer we may get something like :
14304 // zoom out with the wheel (timer is set)
14305 // quickly click and display a chart, which may zoom in
14306 // but the delayed timer fires first and it zooms out again!
14307 StopMovement();
14308
14309 // When switching by piano key click, we may appoint the new target chart to
14310 // be any chart in the composite array.
14311 // As an improvement to UX, find the chart that is "closest" to the current
14312 // vp,
14313 // and select that chart. This will cause a jump to the centroid of that
14314 // chart
14315
14316 double distance = 25000; // RTW
14317 int closest_index = -1;
14318 for (int chart_index : selected_dbIndex_array) {
14319 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14320 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14321 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14322
14323 // measure distance as Manhattan style
14324 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14325 if (test_distance < distance) {
14326 distance = test_distance;
14327 closest_index = chart_index;
14328 }
14329 }
14330
14331 int selected_dbIndex = selected_dbIndex_array[0];
14332 if (closest_index >= 0) selected_dbIndex = closest_index;
14333
14334 if (!GetQuiltMode()) {
14335 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14336 if (IsChartQuiltableRef(selected_dbIndex)) {
14337 ToggleCanvasQuiltMode();
14338 SelectQuiltRefdbChart(selected_dbIndex);
14339 m_bpersistent_quilt = false;
14340 } else {
14341 SelectChartFromStack(selected_index);
14342 }
14343 } else {
14344 SelectChartFromStack(selected_index);
14345 g_sticky_chart = selected_dbIndex;
14346 }
14347
14348 if (m_singleChart)
14349 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14350 } else {
14351 // Handle MBTiles overlays first
14352 // Left click simply toggles the noshow array index entry
14353 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14354 bool bfound = false;
14355 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14356 if (m_tile_noshow_index_array[i] ==
14357 selected_dbIndex) { // chart is in the noshow list
14358 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14359 i); // erase it
14360 bfound = true;
14361 break;
14362 }
14363 }
14364 if (!bfound) {
14365 m_tile_noshow_index_array.push_back(selected_dbIndex);
14366 }
14367
14368 // If not already present, add this tileset to the "yes_show" array.
14369 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14370 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14371 }
14372
14373 else {
14374 if (IsChartQuiltableRef(selected_dbIndex)) {
14375 // if( ChartData ) ChartData->PurgeCache();
14376
14377 // If the chart is a vector chart, and of very large scale,
14378 // then we had better set the new scale directly to avoid excessive
14379 // underzoom on, eg, Inland ENCs
14380 bool set_scale = false;
14381 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14382 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14383 set_scale = true;
14384 }
14385 }
14386
14387 if (!set_scale) {
14388 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14389 } else {
14390 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14391
14392 // Adjust scale so that the selected chart is underzoomed/overzoomed
14393 // by a controlled amount
14394 ChartBase *pc =
14395 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14396 if (pc) {
14397 double proposed_scale_onscreen =
14399
14400 if (g_bPreserveScaleOnX) {
14401 proposed_scale_onscreen =
14402 wxMin(proposed_scale_onscreen,
14403 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14404 GetCanvasWidth()));
14405 } else {
14406 proposed_scale_onscreen =
14407 wxMin(proposed_scale_onscreen,
14408 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14409 GetCanvasWidth()));
14410
14411 proposed_scale_onscreen =
14412 wxMax(proposed_scale_onscreen,
14413 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14415 }
14416
14417 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14418 }
14419 }
14420 } else {
14421 ToggleCanvasQuiltMode();
14422 SelectdbChart(selected_dbIndex);
14423 m_bpersistent_quilt = true;
14424 }
14425 }
14426 }
14427
14428 SetQuiltChartHiLiteIndex(-1);
14429 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14430 // (checkmarks etc)
14431 HideChartInfoWindow();
14432 DoCanvasUpdate();
14433 ReloadVP(); // Pick up the new selections
14434}
14435
14436void ChartCanvas::HandlePianoRClick(
14437 int x, int y, int selected_index,
14438 const std::vector<int> &selected_dbIndex_array) {
14439 if (g_options && g_options->IsShown())
14440 return; // Piano might be invalid due to chartset updates.
14441 if (!GetpCurrentStack()) return;
14442
14443 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14444 UpdateCanvasControlBar();
14445
14446 SetQuiltChartHiLiteIndex(-1);
14447}
14448
14449void ChartCanvas::HandlePianoRollover(
14450 int selected_index, const std::vector<int> &selected_dbIndex_array,
14451 int n_charts, int scale) {
14452 if (g_options && g_options->IsShown())
14453 return; // Piano might be invalid due to chartset updates.
14454 if (!GetpCurrentStack()) return;
14455 if (!ChartData) return;
14456
14457 if (ChartData->IsBusy()) return;
14458
14459 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14460
14461 if (!GetQuiltMode()) {
14462 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14463 } else {
14464 // Select the correct vector
14465 std::vector<int> piano_chart_index_array;
14466 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14467 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14468 if ((GetpCurrentStack()->nEntry > 1) ||
14469 (piano_chart_index_array.size() >= 1)) {
14470 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14471
14472 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14473 ReloadVP(false); // no VP adjustment allowed
14474 } else if (GetpCurrentStack()->nEntry == 1) {
14475 const ChartTableEntry &cte =
14476 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14477 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14478 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14479 ReloadVP(false);
14480 } else if ((-1 == selected_index) &&
14481 (0 == selected_dbIndex_array.size())) {
14482 ShowChartInfoWindow(key_location.x, -1);
14483 }
14484 }
14485 } else {
14486 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14487
14488 if ((GetpCurrentStack()->nEntry > 1) ||
14489 (piano_chart_index_array.size() >= 1)) {
14490 if (n_charts > 1)
14491 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14492 selected_dbIndex_array);
14493 else if (n_charts == 1)
14494 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14495
14496 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14497 ReloadVP(false); // no VP adjustment allowed
14498 }
14499 }
14500 }
14501}
14502
14503void ChartCanvas::ClearPianoRollover() {
14504 ClearQuiltChartHiLiteIndexArray();
14505 ShowChartInfoWindow(0, -1);
14506 std::vector<int> vec;
14507 ShowCompositeInfoWindow(0, 0, 0, vec);
14508 ReloadVP(false);
14509}
14510
14511void ChartCanvas::UpdateCanvasControlBar() {
14512 if (m_pianoFrozen) return;
14513
14514 if (!GetpCurrentStack()) return;
14515 if (!ChartData) return;
14516 if (!g_bShowChartBar) return;
14517
14518 int sel_type = -1;
14519 int sel_family = -1;
14520
14521 std::vector<int> piano_chart_index_array;
14522 std::vector<int> empty_piano_chart_index_array;
14523
14524 wxString old_hash = m_Piano->GetStoredHash();
14525
14526 if (GetQuiltMode()) {
14527 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14528 GetQuiltFullScreendbIndexArray());
14529
14530 std::vector<int> piano_active_chart_index_array =
14531 GetQuiltCandidatedbIndexArray();
14532 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14533
14534 std::vector<int> piano_eclipsed_chart_index_array =
14535 GetQuiltEclipsedStackdbIndexArray();
14536 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14537
14538 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14539 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14540
14541 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14542 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14543 } else {
14544 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14545 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14546 // TODO refresh_Piano();
14547
14548 if (m_singleChart) {
14549 sel_type = m_singleChart->GetChartType();
14550 sel_family = m_singleChart->GetChartFamily();
14551 }
14552 }
14553
14554 // Set up the TMerc and Skew arrays
14555 std::vector<int> piano_skew_chart_index_array;
14556 std::vector<int> piano_tmerc_chart_index_array;
14557 std::vector<int> piano_poly_chart_index_array;
14558
14559 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14560 const ChartTableEntry &ctei =
14561 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14562 double skew_norm = ctei.GetChartSkew();
14563 if (skew_norm > 180.) skew_norm -= 360.;
14564
14565 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14566 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14567
14568 // Polyconic skewed charts should show as skewed
14569 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14570 if (fabs(skew_norm) > 1.)
14571 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14572 else
14573 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14574 } else if (fabs(skew_norm) > 1.)
14575 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14576 }
14577 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14578 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14579 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14580
14581 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14582 if (new_hash != old_hash) {
14583 m_Piano->FormatKeys();
14584 HideChartInfoWindow();
14585 m_Piano->ResetRollover();
14586 SetQuiltChartHiLiteIndex(-1);
14587 m_brepaint_piano = true;
14588 }
14589
14590 // Create a bitmask int that describes what Family/Type of charts are shown in
14591 // the bar, and notify the platform.
14592 int mask = 0;
14593 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14594 const ChartTableEntry &ctei =
14595 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14596 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14597 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14598 if (e == CHART_FAMILY_RASTER) mask |= 1;
14599 if (e == CHART_FAMILY_VECTOR) {
14600 if (t == CHART_TYPE_CM93COMP)
14601 mask |= 4;
14602 else
14603 mask |= 2;
14604 }
14605 }
14606
14607 wxString s_indicated;
14608 if (sel_type == CHART_TYPE_CM93COMP)
14609 s_indicated = "cm93";
14610 else {
14611 if (sel_family == CHART_FAMILY_RASTER)
14612 s_indicated = "raster";
14613 else if (sel_family == CHART_FAMILY_VECTOR)
14614 s_indicated = "vector";
14615 }
14616
14617 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14618}
14619
14620void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14621
14622void ChartCanvas::PianoPopupMenu(
14623 int x, int y, int selected_index,
14624 const std::vector<int> &selected_dbIndex_array) {
14625 if (!GetpCurrentStack()) return;
14626
14627 // No context menu if quilting is disabled
14628 if (!GetQuiltMode()) return;
14629
14630 m_piano_ctx_menu = new wxMenu();
14631
14632 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14633 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14634 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14635 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14636 } else {
14637 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14638 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14639 // wxEVT_COMMAND_MENU_SELECTED,
14640 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14641
14642 menu_selected_dbIndex = selected_dbIndex_array[0];
14643 menu_selected_index = selected_index;
14644
14645 // Search the no-show array
14646 bool b_is_in_noshow = false;
14647 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14648 if (m_quilt_noshow_index_array[i] ==
14649 menu_selected_dbIndex) // chart is in the noshow list
14650 {
14651 b_is_in_noshow = true;
14652 break;
14653 }
14654 }
14655
14656 if (b_is_in_noshow) {
14657 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14658 _("Show This Chart"));
14659 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14660 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14661 } else if (GetpCurrentStack()->nEntry > 1) {
14662 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14663 _("Hide This Chart"));
14664 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14665 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14666 }
14667 }
14668
14669 wxPoint pos = wxPoint(x, y - 30);
14670
14671 // Invoke the drop-down menu
14672 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14673 PopupMenu(m_piano_ctx_menu, pos);
14674
14675 delete m_piano_ctx_menu;
14676 m_piano_ctx_menu = NULL;
14677
14678 HideChartInfoWindow();
14679 m_Piano->ResetRollover();
14680
14681 SetQuiltChartHiLiteIndex(-1);
14682 ClearQuiltChartHiLiteIndexArray();
14683
14684 ReloadVP();
14685}
14686
14687void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14688 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14689 if (m_quilt_noshow_index_array[i] ==
14690 menu_selected_dbIndex) // chart is in the noshow list
14691 {
14692 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14693 break;
14694 }
14695 }
14696}
14697
14698void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14699 if (!GetpCurrentStack()) return;
14700 if (!ChartData) return;
14701
14702 RemoveChartFromQuilt(menu_selected_dbIndex);
14703
14704 // It could happen that the chart being disabled is the reference
14705 // chart....
14706 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14707 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14708
14709 int i = menu_selected_index + 1; // select next smaller scale chart
14710 bool b_success = false;
14711 while (i < GetpCurrentStack()->nEntry - 1) {
14712 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14713 if (type == ChartData->GetDBChartType(dbIndex)) {
14714 SelectQuiltRefChart(i);
14715 b_success = true;
14716 break;
14717 }
14718 i++;
14719 }
14720
14721 // If that did not work, try to select the next larger scale compatible
14722 // chart
14723 if (!b_success) {
14724 i = menu_selected_index - 1;
14725 while (i > 0) {
14726 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14727 if (type == ChartData->GetDBChartType(dbIndex)) {
14728 SelectQuiltRefChart(i);
14729 b_success = true;
14730 break;
14731 }
14732 i--;
14733 }
14734 }
14735 }
14736}
14737
14738void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14739 // Remove the item from the list (if it appears) to avoid multiple addition
14740 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14741 if (m_quilt_noshow_index_array[i] ==
14742 dbIndex) // chart is already in the noshow list
14743 {
14744 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14745 break;
14746 }
14747 }
14748
14749 m_quilt_noshow_index_array.push_back(dbIndex);
14750}
14751
14752bool ChartCanvas::UpdateS52State() {
14753 bool retval = false;
14754
14755 if (ps52plib) {
14756 ps52plib->SetShowS57Text(m_encShowText);
14757 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14758 ps52plib->m_bShowSoundg = m_encShowDepth;
14759 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14760 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14761
14762 // Lights
14763 if (!m_encShowLights) // On, going off
14764 ps52plib->AddObjNoshow("LIGHTS");
14765 else // Off, going on
14766 ps52plib->RemoveObjNoshow("LIGHTS");
14767 ps52plib->SetLightsOff(!m_encShowLights);
14768 ps52plib->m_bExtendLightSectors = true;
14769
14770 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14771 ps52plib->SetAnchorOn(m_encShowAnchor);
14772 ps52plib->SetQualityOfData(m_encShowDataQual);
14773 }
14774
14775 return retval;
14776}
14777
14778void ChartCanvas::SetShowENCDataQual(bool show) {
14779 m_encShowDataQual = show;
14780 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14781 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14782
14783 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14784}
14785
14786void ChartCanvas::SetShowENCText(bool show) {
14787 m_encShowText = show;
14788 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14789 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14790
14791 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14792}
14793
14794void ChartCanvas::SetENCDisplayCategory(int category) {
14795 m_encDisplayCategory = category;
14796 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14797}
14798
14799void ChartCanvas::SetShowENCDepth(bool show) {
14800 m_encShowDepth = show;
14801 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14802 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14803
14804 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14805}
14806
14807void ChartCanvas::SetShowENCLightDesc(bool show) {
14808 m_encShowLightDesc = show;
14809 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14810 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14811
14812 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14813}
14814
14815void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14816 m_encShowBuoyLabels = show;
14817 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14818}
14819
14820void ChartCanvas::SetShowENCLights(bool show) {
14821 m_encShowLights = show;
14822 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14823 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14824
14825 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14826}
14827
14828void ChartCanvas::SetShowENCAnchor(bool show) {
14829 m_encShowAnchor = show;
14830 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14831 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14832
14833 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14834}
14835
14836wxRect ChartCanvas::GetMUIBarRect() {
14837 wxRect rv;
14838 if (m_muiBar) {
14839 rv = m_muiBar->GetRect();
14840 }
14841
14842 return rv;
14843}
14844
14845void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14846 if (!GetAlertString().IsEmpty()) {
14847 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14848 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14849
14850 dc.SetFont(*pfont);
14851 dc.SetPen(*wxTRANSPARENT_PEN);
14852
14853 dc.SetBrush(wxColour(243, 229, 47));
14854 int w, h;
14855 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14856 h += 2;
14857 // int yp = vp.pix_height - 20 - h;
14858
14859 wxRect sbr = GetScaleBarRect();
14860 int xp = sbr.x + sbr.width + 10;
14861 int yp = (sbr.y + sbr.height) - h;
14862
14863 int wdraw = w + 10;
14864 dc.DrawRectangle(xp, yp, wdraw, h);
14865 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14866 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14867 }
14868}
14869
14870//--------------------------------------------------------------------------------------------------------
14871// Screen Brightness Control Support Routines
14872//
14873//--------------------------------------------------------------------------------------------------------
14874
14875#ifdef __UNIX__
14876#define BRIGHT_XCALIB
14877#define __OPCPN_USEICC__
14878#endif
14879
14880#ifdef __OPCPN_USEICC__
14881int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14882 double co_green, double co_blue);
14883
14884wxString temp_file_name;
14885#endif
14886
14887#if 0
14888class ocpnCurtain: public wxDialog
14889{
14890 DECLARE_CLASS( ocpnCurtain )
14891 DECLARE_EVENT_TABLE()
14892
14893public:
14894 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14895 ~ocpnCurtain( );
14896 bool ProcessEvent(wxEvent& event);
14897
14898};
14899
14900IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14901
14902BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14903END_EVENT_TABLE()
14904
14905ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14906{
14907 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14908}
14909
14910ocpnCurtain::~ocpnCurtain()
14911{
14912}
14913
14914bool ocpnCurtain::ProcessEvent(wxEvent& event)
14915{
14916 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14917 return GetParent()->GetEventHandler()->ProcessEvent(event);
14918}
14919#endif
14920
14921#ifdef _WIN32
14922#include <windows.h>
14923
14924HMODULE hGDI32DLL;
14925typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14926typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14927SetDeviceGammaRamp_ptr_type
14928 g_pSetDeviceGammaRamp; // the API entry points in the dll
14929GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14930
14931WORD *g_pSavedGammaMap;
14932
14933#endif
14934
14935int InitScreenBrightness() {
14936#ifdef _WIN32
14937#ifdef ocpnUSE_GL
14938 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14939 HDC hDC;
14940 BOOL bbr;
14941
14942 if (NULL == hGDI32DLL) {
14943 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14944
14945 if (NULL != hGDI32DLL) {
14946 // Get the entry points of the required functions
14947 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14948 hGDI32DLL, "SetDeviceGammaRamp");
14949 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14950 hGDI32DLL, "GetDeviceGammaRamp");
14951
14952 // If the functions are not found, unload the DLL and return false
14953 if ((NULL == g_pSetDeviceGammaRamp) ||
14954 (NULL == g_pGetDeviceGammaRamp)) {
14955 FreeLibrary(hGDI32DLL);
14956 hGDI32DLL = NULL;
14957 return 0;
14958 }
14959 }
14960 }
14961
14962 // Interface is ready, so....
14963 // Get some storage
14964 if (!g_pSavedGammaMap) {
14965 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14966
14967 hDC = GetDC(NULL); // Get the full screen DC
14968 bbr = g_pGetDeviceGammaRamp(
14969 hDC, g_pSavedGammaMap); // Get the existing ramp table
14970 ReleaseDC(NULL, hDC); // Release the DC
14971 }
14972
14973 // On Windows hosts, try to adjust the registry to allow full range
14974 // setting of Gamma table This is an undocumented Windows hack.....
14975 wxRegKey *pRegKey = new wxRegKey(
14976 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14977 "NT\\CurrentVersion\\ICM");
14978 if (!pRegKey->Exists()) pRegKey->Create();
14979 pRegKey->SetValue("GdiIcmGammaRange", 256);
14980
14981 g_brightness_init = true;
14982 return 1;
14983 }
14984#endif
14985
14986 {
14987 if (NULL == g_pcurtain) {
14988 if (gFrame->CanSetTransparent()) {
14989 // Build the curtain window
14990 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14991 wxPoint(0, 0), ::wxGetDisplaySize(),
14992 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14993 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14994
14995 // g_pcurtain = new ocpnCurtain(gFrame,
14996 // wxPoint(0,0),::wxGetDisplaySize(),
14997 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14998 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14999
15000 g_pcurtain->Hide();
15001
15002 HWND hWnd = GetHwndOf(g_pcurtain);
15003 SetWindowLong(hWnd, GWL_EXSTYLE,
15004 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15005 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15006 g_pcurtain->SetTransparent(0);
15007
15008 g_pcurtain->Maximize();
15009 g_pcurtain->Show();
15010
15011 // All of this is obtuse, but necessary for Windows...
15012 g_pcurtain->Enable();
15013 g_pcurtain->Disable();
15014
15015 gFrame->Disable();
15016 gFrame->Enable();
15017 // SetFocus();
15018 }
15019 }
15020 g_brightness_init = true;
15021
15022 return 1;
15023 }
15024#else
15025 // Look for "xcalib" application
15026 wxString cmd("xcalib -version");
15027
15028 wxArrayString output;
15029 long r = wxExecute(cmd, output);
15030 if (0 != r)
15031 wxLogMessage(
15032 " External application \"xcalib\" not found. Screen brightness "
15033 "not changed.");
15034
15035 g_brightness_init = true;
15036 return 0;
15037#endif
15038}
15039
15040int RestoreScreenBrightness() {
15041#ifdef _WIN32
15042
15043 if (g_pSavedGammaMap) {
15044 HDC hDC = GetDC(NULL); // Get the full screen DC
15045 g_pSetDeviceGammaRamp(hDC,
15046 g_pSavedGammaMap); // Restore the saved ramp table
15047 ReleaseDC(NULL, hDC); // Release the DC
15048
15049 free(g_pSavedGammaMap);
15050 g_pSavedGammaMap = NULL;
15051 }
15052
15053 if (g_pcurtain) {
15054 g_pcurtain->Close();
15055 g_pcurtain->Destroy();
15056 g_pcurtain = NULL;
15057 }
15058
15059 g_brightness_init = false;
15060 return 1;
15061
15062#endif
15063
15064#ifdef BRIGHT_XCALIB
15065 if (g_brightness_init) {
15066 wxString cmd;
15067 cmd = "xcalib -clear";
15068 wxExecute(cmd, wxEXEC_ASYNC);
15069 g_brightness_init = false;
15070 }
15071
15072 return 1;
15073#endif
15074
15075 return 0;
15076}
15077
15078// Set brightness. [0..100]
15079int SetScreenBrightness(int brightness) {
15080#ifdef _WIN32
15081
15082 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15083 // some (most modern?) versions of gdi32.dll Load the required library dll,
15084 // if not already in place
15085#ifdef ocpnUSE_GL
15086 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15087 if (g_pcurtain) {
15088 g_pcurtain->Close();
15089 g_pcurtain->Destroy();
15090 g_pcurtain = NULL;
15091 }
15092
15093 InitScreenBrightness();
15094
15095 if (NULL == hGDI32DLL) {
15096 // Unicode stuff.....
15097 wchar_t wdll_name[80];
15098 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15099 LPCWSTR cstr = wdll_name;
15100
15101 hGDI32DLL = LoadLibrary(cstr);
15102
15103 if (NULL != hGDI32DLL) {
15104 // Get the entry points of the required functions
15105 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15106 hGDI32DLL, "SetDeviceGammaRamp");
15107 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15108 hGDI32DLL, "GetDeviceGammaRamp");
15109
15110 // If the functions are not found, unload the DLL and return false
15111 if ((NULL == g_pSetDeviceGammaRamp) ||
15112 (NULL == g_pGetDeviceGammaRamp)) {
15113 FreeLibrary(hGDI32DLL);
15114 hGDI32DLL = NULL;
15115 return 0;
15116 }
15117 }
15118 }
15119
15120 HDC hDC = GetDC(NULL); // Get the full screen DC
15121
15122 /*
15123 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15124 if (cmcap != CM_GAMMA_RAMP)
15125 {
15126 wxLogMessage(" Video hardware does not support brightness control by
15127 gamma ramp adjustment."); return false;
15128 }
15129 */
15130
15131 int increment = brightness * 256 / 100;
15132
15133 // Build the Gamma Ramp table
15134 WORD GammaTable[3][256];
15135
15136 int table_val = 0;
15137 for (int i = 0; i < 256; i++) {
15138 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15139 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15140 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15141
15142 table_val += increment;
15143
15144 if (table_val > 65535) table_val = 65535;
15145 }
15146
15147 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15148 ReleaseDC(NULL, hDC); // Release the DC
15149
15150 return 1;
15151 }
15152#endif
15153
15154 {
15155 if (g_pSavedGammaMap) {
15156 HDC hDC = GetDC(NULL); // Get the full screen DC
15157 g_pSetDeviceGammaRamp(hDC,
15158 g_pSavedGammaMap); // Restore the saved ramp table
15159 ReleaseDC(NULL, hDC); // Release the DC
15160 }
15161
15162 if (brightness < 100) {
15163 if (NULL == g_pcurtain) InitScreenBrightness();
15164
15165 if (g_pcurtain) {
15166 int sbrite = wxMax(1, brightness);
15167 sbrite = wxMin(100, sbrite);
15168
15169 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15170 }
15171 } else {
15172 if (g_pcurtain) {
15173 g_pcurtain->Close();
15174 g_pcurtain->Destroy();
15175 g_pcurtain = NULL;
15176 }
15177 }
15178
15179 return 1;
15180 }
15181
15182#endif
15183
15184#ifdef BRIGHT_XCALIB
15185
15186 if (!g_brightness_init) {
15187 last_brightness = 100;
15188 g_brightness_init = true;
15189 temp_file_name = wxFileName::CreateTempFileName("");
15190 InitScreenBrightness();
15191 }
15192
15193#ifdef __OPCPN_USEICC__
15194 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15195 // desired, and then activate this temporary profile using xcalib <filename>
15196 if (!CreateSimpleICCProfileFile(
15197 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15198 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15199 wxString cmd("xcalib ");
15200 cmd += temp_file_name;
15201
15202 wxExecute(cmd, wxEXEC_ASYNC);
15203 }
15204
15205#else
15206 // Or, use "xcalib -co" to set overall contrast value
15207 // This is not as nice, since the -co parameter wants to be a fraction of
15208 // the current contrast, and values greater than 100 are not allowed. As a
15209 // result, increases of contrast must do a "-clear" step first, which
15210 // produces objectionable flashing.
15211 if (brightness > last_brightness) {
15212 wxString cmd;
15213 cmd = "xcalib -clear";
15214 wxExecute(cmd, wxEXEC_ASYNC);
15215
15216 ::wxMilliSleep(10);
15217
15218 int brite_adj = wxMax(1, brightness);
15219 cmd.Printf("xcalib -co %2d -a", brite_adj);
15220 wxExecute(cmd, wxEXEC_ASYNC);
15221 } else {
15222 int brite_adj = wxMax(1, brightness);
15223 int factor = (brite_adj * 100) / last_brightness;
15224 factor = wxMax(1, factor);
15225 wxString cmd;
15226 cmd.Printf("xcalib -co %2d -a", factor);
15227 wxExecute(cmd, wxEXEC_ASYNC);
15228 }
15229
15230#endif
15231
15232 last_brightness = brightness;
15233
15234#endif
15235
15236 return 0;
15237}
15238
15239#ifdef __OPCPN_USEICC__
15240
15241#define MLUT_TAG 0x6d4c5554L
15242#define VCGT_TAG 0x76636774L
15243
15244int GetIntEndian(unsigned char *s) {
15245 int ret;
15246 unsigned char *p;
15247 int i;
15248
15249 p = (unsigned char *)&ret;
15250
15251 if (1)
15252 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15253 else
15254 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15255
15256 return ret;
15257}
15258
15259unsigned short GetShortEndian(unsigned char *s) {
15260 unsigned short ret;
15261 unsigned char *p;
15262 int i;
15263
15264 p = (unsigned char *)&ret;
15265
15266 if (1)
15267 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15268 else
15269 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15270
15271 return ret;
15272}
15273
15274// Create a very simple Gamma correction file readable by xcalib
15275int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15276 double co_green, double co_blue) {
15277 FILE *fp;
15278
15279 if (file_name) {
15280 fp = fopen(file_name, "wb");
15281 if (!fp) return -1; /* file can not be created */
15282 } else
15283 return -1; /* filename char pointer not valid */
15284
15285 // Write header
15286 char header[128];
15287 for (int i = 0; i < 128; i++) header[i] = 0;
15288
15289 fwrite(header, 128, 1, fp);
15290
15291 // Num tags
15292 int numTags0 = 1;
15293 int numTags = GetIntEndian((unsigned char *)&numTags0);
15294 fwrite(&numTags, 1, 4, fp);
15295
15296 int tagName0 = VCGT_TAG;
15297 int tagName = GetIntEndian((unsigned char *)&tagName0);
15298 fwrite(&tagName, 1, 4, fp);
15299
15300 int tagOffset0 = 128 + 4 * sizeof(int);
15301 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15302 fwrite(&tagOffset, 1, 4, fp);
15303
15304 int tagSize0 = 1;
15305 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15306 fwrite(&tagSize, 1, 4, fp);
15307
15308 fwrite(&tagName, 1, 4, fp); // another copy of tag
15309
15310 fwrite(&tagName, 1, 4, fp); // dummy
15311
15312 // Table type
15313
15314 /* VideoCardGammaTable (The simplest type) */
15315 int gammatype0 = 0;
15316 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15317 fwrite(&gammatype, 1, 4, fp);
15318
15319 int numChannels0 = 3;
15320 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15321 fwrite(&numChannels, 1, 2, fp);
15322
15323 int numEntries0 = 256;
15324 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15325 fwrite(&numEntries, 1, 2, fp);
15326
15327 int entrySize0 = 1;
15328 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15329 fwrite(&entrySize, 1, 2, fp);
15330
15331 unsigned char ramp[256];
15332
15333 // Red ramp
15334 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15335 fwrite(ramp, 256, 1, fp);
15336
15337 // Green ramp
15338 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15339 fwrite(ramp, 256, 1, fp);
15340
15341 // Blue ramp
15342 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15343 fwrite(ramp, 256, 1, fp);
15344
15345 fclose(fp);
15346
15347 return 0;
15348}
15349#endif // __OPCPN_USEICC__
Select * pSelectAIS
Global instance.
AisDecoder * g_pAIS
Global instance.
Class AisDecoder and helpers.
Global state for AIS decoder.
Class AISTargetAlertDialog and helpers.
AIS target definitions.
Class AISTargetQueryDialog.
std::unique_ptr< HostApi > GetHostApi()
HostApi factory,.
Definition api_121.cpp:744
BasePlatform * g_BasePlatform
points to g_platform, handles brain-dead MS linker.
Chart canvas configuration state
Canvas context (right click) menu handler.
Class CanvasOptions and helpers – Canvas options Window/Dialog.
Chart info panel.
ChartDB * ChartData
Global instance.
Definition chartdb.cpp:70
Charts database management
ChartGroupArray * g_pGroupArray
Global instance.
Definition chartdbs.cpp:56
BSB chart management.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1312
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1311
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1312
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1311
Dialog for displaying a list of AIS targets.
Dialog for querying detailed information about an AIS target.
bool Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &caption=_("Object Query"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=AIS_TARGET_QUERY_STYLE)
Creation.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
Handles context menu events for the chart canvas.
Definition canvas_menu.h:49
A custom panel for displaying chart information.
Definition ch_info_win.h:36
Base class for BSB (Maptech/NOS) format nautical charts.
Definition chartimg.h:128
Base class for all chart types.
Definition chartbase.h:125
ChartCanvas - Main chart display and interaction component.
Definition chcanv.h:157
void CloseTideDialog()
Close any open tide dialog.
Definition chcanv.cpp:13712
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:4539
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11756
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4535
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3633
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13671
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:4485
double m_cursor_lat
The latitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:782
double GetCanvasScaleFactor()
Return the number of logical pixels per meter for the screen.
Definition chcanv.h:485
double GetPixPerMM()
Get the number of logical pixels per millimeter on the screen.
Definition chcanv.h:516
void SetDisplaySizeMM(double size)
Set the width of the screen in millimeters.
Definition chcanv.cpp:2340
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7970
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7779
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5067
float GetVPScale()
Return the ViewPort scale factor, in physical pixels per meter.
Definition chcanv.h:473
EventVar json_msg
Notified with message targeting all plugins.
Definition chcanv.h:873
void ZoomCanvasSimple(double factor)
Perform an immediate zoom operation without smooth transitions.
Definition chcanv.cpp:4616
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5347
double m_cursor_lon
The longitude in degrees corresponding to the most recently processed cursor position.
Definition chcanv.h:766
void GetCanvasPixPoint(double x, double y, double &lat, double &lon)
Convert canvas pixel coordinates (physical pixels) to latitude/longitude.
Definition chcanv.cpp:4560
bool IsTideDialogOpen() const
Definition chcanv.cpp:13710
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:4622
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13667
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4480
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5366
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10163
Represents a user-defined collection of logically related charts.
Definition chartdbs.h:468
Represents an MBTiles format chart.
Definition mbtiles.h:69
Wrapper class for plugin-based charts.
Definition chartimg.h:389
void Notify() override
Notify all listeners, no data supplied.
wxFont * FindOrCreateFont(int point_size, wxFontFamily family, wxFontStyle style, wxFontWeight weight, bool underline=false, const wxString &facename=wxEmptyString, wxFontEncoding encoding=wxFONTENCODING_DEFAULT)
Creates or finds a matching font in the font cache.
Definition font_mgr.cpp:442
wxColour GetFontColor(const wxString &TextElement) const
Gets the text color for a UI element.
Definition font_mgr.cpp:110
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Modal dialog displaying hotkeys.
Definition hotkeys_dlg.h:33
Represents an index entry for tidal and current data.
Definition idx_entry.h:48
char IDX_type
Entry type identifier "TCtcIUu".
Definition idx_entry.h:60
double IDX_lat
Latitude of the station (in degrees, +North)
Definition idx_entry.h:64
double IDX_lon
Longitude of the station (in degrees, +East)
Definition idx_entry.h:63
bool b_skipTooDeep
Flag to skip processing if depth exceeds limit.
Definition idx_entry.h:109
Station_Data * pref_sta_data
Pointer to the reference station data.
Definition idx_entry.h:96
int IDX_rec_num
Record number for multiple entries with same name.
Definition idx_entry.h:59
Definition kml.h:50
Modern User Interface Control Bar for OpenCPN.
Definition mui_bar.h:61
Dialog for displaying and editing waypoint properties.
Definition mark_info.h:203
Main application frame.
Definition ocpn_frame.h:139
wxRect GetLogicalRect(void) const
Return the coordinates of the widget, in logical pixels.
wxRect GetRect(void) const
Return the coordinates of the widget, in physical pixels relative to the canvas window.
wxSize getDisplaySize()
Get the display size in logical pixels.
An iterator class for OCPNRegion.
A wrapper class for wxRegion with additional functionality.
Definition ocpn_region.h:37
Definition piano.h:60
PluginLoader is a backend module without any direct GUI functionality.
A popup frame containing a detail slider for chart display.
Definition quilt.h:82
bool Compose(const ViewPort &vp)
Definition quilt.cpp:1779
Represents a waypoint or mark within the navigation system.
Definition route_point.h:71
wxString m_MarkDescription
Description text for the waypoint.
LLBBox m_wpBBox
Bounding box for the waypoint.
bool m_bRPIsBeingEdited
Flag indicating if this waypoint is currently being edited.
wxRect CurrentRect_in_DC
Current rectangle occupied by the waypoint in the display.
wxString m_GUID
Globally Unique Identifier for the waypoint.
bool m_bIsolatedMark
Flag indicating if the waypoint is a standalone mark.
bool m_bIsActive
Flag indicating if this waypoint is active for navigation.
bool m_bIsInRoute
Flag indicating if this waypoint is part of a route.
double m_seg_len
Length of the leg from previous waypoint to this waypoint in nautical miles.
bool m_bShowName
Flag indicating if the waypoint name should be shown.
bool m_bBlink
Flag indicating if the waypoint should blink when displayed.
bool m_bPtIsSelected
Flag indicating if this waypoint is currently selected.
bool m_bIsVisible
Flag indicating if the waypoint should be drawn on the chart.
bool m_bIsInLayer
Flag indicating if the waypoint belongs to a layer.
int m_LayerID
Layer identifier if the waypoint belongs to a layer.
Represents a navigational route in the navigation system.
Definition route.h:99
bool m_bRtIsSelected
Flag indicating whether this route is currently selected in the UI.
Definition route.h:203
RoutePointList * pRoutePointList
Ordered list of waypoints (RoutePoints) that make up this route.
Definition route.h:336
double m_route_length
Total length of the route in nautical miles, calculated using rhumb line (Mercator) distances.
Definition route.h:237
bool m_bRtIsActive
Flag indicating whether this route is currently active for navigation.
Definition route.h:208
int m_width
Width of the route line in pixels when rendered on the chart.
Definition route.h:288
wxString m_RouteNameString
User-assigned name for the route.
Definition route.h:247
wxString m_GUID
Globally unique identifier for this route.
Definition route.h:273
bool m_bIsInLayer
Flag indicating whether this route belongs to a layer.
Definition route.h:278
int m_lastMousePointIndex
Index of the most recently interacted with route point.
Definition route.h:298
bool m_bIsBeingEdited
Flag indicating that the route is currently being edited by the user.
Definition route.h:224
bool m_NextLegGreatCircle
Flag indicating whether the next leg should be calculated using great circle navigation or rhumb line...
Definition route.h:315
wxArrayPtrVoid * GetRouteArrayContaining(RoutePoint *pWP)
Find all routes that contain the given waypoint.
Definition routeman.cpp:150
bool ActivateNextPoint(Route *pr, bool skipped)
Activates the next waypoint in a route when the current waypoint is reached.
Definition routeman.cpp:357
bool DeleteRoute(Route *pRoute)
Definition routeman.cpp:762
Dialog for displaying query results of S57 objects.
Definition tc_win.h:44
IDX_entry * GetCurrentIDX() const
Definition tc_win.h:72
Represents a single point in a track.
Definition track.h:59
wxDateTime GetCreateTime(void)
Retrieves the creation timestamp of a track point as a wxDateTime object.
Definition track.cpp:138
void SetCreateTime(wxDateTime dt)
Sets the creation timestamp for a track point.
Definition track.cpp:144
Represents a track, which is a series of connected track points.
Definition track.h:117
Definition undo.h:58
ViewPort - Core geographic projection and coordinate transformation engine.
Definition viewport.h:56
void SetBoxes()
Computes the bounding box coordinates for the current viewport.
Definition viewport.cpp:812
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:124
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:136
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:127
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Definition viewport.h:219
bool AddRoutePoint(RoutePoint *prp)
Add a point to list which owns it.
Definition routeman.cpp:998
bool RemoveRoutePoint(RoutePoint *prp)
Remove a routepoint from list if present, deallocate it all cases.
Encapsulates persistent canvas configuration.
double iLat
Latitude of the center of the chart, in degrees.
bool bShowOutlines
Display chart outlines.
bool bShowDepthUnits
Display depth unit indicators.
double iLon
Longitude of the center of the chart, in degrees.
double iRotation
Initial rotation angle in radians.
bool bCourseUp
Orient display to course up.
bool bQuilt
Enable chart quilting.
bool bFollow
Enable vessel following mode.
double iScale
Initial chart scale factor.
bool bShowENCText
Display ENC text elements.
bool bShowAIS
Display AIS targets.
bool bShowGrid
Display coordinate grid.
bool bShowCurrents
Display current information.
bool bShowTides
Display tide information.
bool bLookahead
Enable lookahead mode.
bool bHeadUp
Orient display to heading up.
bool bAttenAIS
Enable AIS target attenuation.
Represents a composite CM93 chart covering multiple scales.
Definition cm93.h:416
Stores emboss effect data for textures.
Definition emboss_data.h:34
OpenGL chart rendering canvas.
Represents a compass display in the OpenCPN navigation system.
Definition compass.h:39
wxRect GetRect(void) const
Return the coordinates of the compass widget, in physical pixels relative to the canvas window.
Definition compass.h:63
wxRect GetLogicalRect(void) const
Return the coordinates of the compass widget, in logical pixels.
Definition compass.cpp:215
Device context class that can use either wxDC or OpenGL for drawing.
Definition ocpndc.h:60
void DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2, bool b_hiqual=true)
Draw a line between two points using either wxDC or OpenGL.
Definition ocpndc.cpp:474
Represents an S57 format electronic navigational chart in OpenCPN.
Definition s57chart.h:90
The JSON value class implementation.
Definition jsonval.h:84
The JSON document writer.
Definition jsonwriter.h:50
void Write(const wxJSONValue &value, wxString &str)
Write the JSONvalue object to a JSON text.
Class cm93chart and helpers – CM93 chart state.
Global variables reflecting command line options and arguments.
APConsole * console
Global instance.
Definition concanv.cpp:54
Primary navigation console display for route and vessel tracking.
bool g_bsmoothpanzoom
Controls how the chart panning and zooming smoothing is done during user interactions.
bool g_bRollover
enable/disable mouse rollover GUI effects
double g_COGAvg
Debug only usage.
int g_COGAvgSec
COG average period for Course Up Mode (sec)
int g_iDistanceFormat
User-selected distance (horizontal) unit format for display and input.
double g_display_size_mm
Physical display width (mm)
Connection parameters.
PopUpDSlide * pPopupDetailSlider
Global instance.
Chart display details slider.
std::vector< OCPN_MonitorInfo > g_monitor_info
Information about the monitors connected to the system.
Definition displays.cpp:45
Font list manager.
OpenGL chart rendering canvas.
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:77
Waypoint properties maintenance dialog.
MUI (Modern User Interface) Control bar.
Multiplexer class and helpers.
MySQL based storage for routes, tracks, etc.
Utility functions.
double fromUsrDistance(double usr_distance, int unit, int default_val)
Convert distance from user units to nautical miles.
double toUsrDistance(double nm_distance, int unit)
Convert a distance from nautical miles (NMi) to user display units.
wxString FormatDistanceAdaptive(double distance)
Format a distance (given in nautical miles) using the current distance preference,...
Navigation Utility Functions without GUI dependencies.
User notifications manager.
Notification Manager GUI.
OCPN_AUIManager * g_pauimgr
Global instance.
OCPN_AUIManager.
OpenCPN top window.
Optimized wxBitmap Object.
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.
Layer to use wxDC or opengl.
options * g_options
Global instance.
Definition options.cpp:180
Options dialog.
bool bGPSValid
Indicate whether the Global Navigation Satellite System (GNSS) has a valid position.
Definition own_ship.cpp:34
double gHdt
True heading in degrees (0-359.99).
Definition own_ship.cpp:30
double gLat
Vessel's current latitude in decimal degrees.
Definition own_ship.cpp:26
double gCog
Course over ground in degrees (0-359.99).
Definition own_ship.cpp:28
double gSog
Speed over ground in knots.
Definition own_ship.cpp:29
double gLon
Vessel's current longitude in decimal degrees.
Definition own_ship.cpp:27
Position, course, speed, etc.
Chart Bar Window.
Tools to send data to plugins.
PlugInManager * g_pi_manager
Global instance.
PlugInManager and helper classes – Mostly gui parts (dialogs) and plugin API stuff.
Chart quilt support.
Route abstraction.
Route drawing stuff.
Purpose: Track and Trackpoint drawing stuff.
RoutePropDlgImpl * pRoutePropDialog
Global instance.
Route properties dialog.
RoutePoint * pAnchorWatchPoint2
Global instance.
Definition routeman.cpp:64
Routeman * g_pRouteMan
Global instance.
Definition routeman.cpp:60
RouteList * pRouteList
Global instance.
Definition routeman.cpp:66
RoutePoint * pAnchorWatchPoint1
Global instance.
Definition routeman.cpp:63
float g_ChartScaleFactorExp
Global instance.
Definition routeman.cpp:68
Route Manager.
Manage routes dialog.
S57QueryDialog * g_pObjectQueryDialog
Global instance.
S57 object query result window.
S57 Chart Object.
Select * pSelect
Global instance.
Definition select.cpp:36
Select * pSelectTC
Global instance.
Definition select.cpp:37
Selected route, segment, waypoint, etc.
A single, selected generic item.
SENCThreadManager * g_SencThreadManager
Global instance.
ShapeBaseChartSet gShapeBasemap
global instance
Shapefile basemap.
Represents an entry in the chart table, containing information about a single chart.
Definition chartdbs.h:183
Configuration options for date and time formatting.
DateTimeFormatOptions & SetTimezone(const wxString &tz)
Sets the timezone mode for date/time display.
Chart Symbols.
Tide and currents window.
TCMgr * ptcmgr
Global instance.
Definition tcmgr.cpp:42
Tide and Current Manager @TODO Add original author copyright.
ThumbWin * pthumbwin
Global instance.
Definition thumbwin.cpp:40
Chart thumbnail object.
Timer identification constants.
ocpnFloatingToolbarDialog * g_MainToolbar
Global instance.
Definition toolbar.cpp:65
OpenCPN Toolbar.
ActiveTrack * g_pActiveTrack
global instance
Definition track.cpp:99
std::vector< Track * > g_TrackList
Global instance.
Definition track.cpp:96
Recorded track abstraction.
Track and Trackpoint drawing stuff.
TrackPropDlg * pTrackPropDialog
Global instance.
Track Properties Dialog.
Framework for Undo features.