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}
759
760void ChartCanvas::SetupGridFont() {
761 wxFont *dFont = FontMgr::Get().GetFont(_("GridText"), 0);
762 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
763 int gridFontSize = wxMax(10, dFont->GetPointSize() * dpi_factor);
764 m_pgridFont = FontMgr::Get().FindOrCreateFont(
765 gridFontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL,
766 FALSE, wxString("Arial"));
767}
768
769void ChartCanvas::RebuildCursors() {
770 delete pCursorLeft;
771 delete pCursorRight;
772 delete pCursorUp;
773 delete pCursorDown;
774 delete pCursorArrow;
775 delete pCursorPencil;
776 delete pCursorCross;
777
778 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
779 double cursorScale = exp(g_GUIScaleFactor * (0.693 / 5.0));
780
781 double pencilScale = 1.0 / g_Platform->GetDisplayDIPMult(gFrame);
782
783 wxImage ICursorLeft = style->GetIcon("left").ConvertToImage();
784 wxImage ICursorRight = style->GetIcon("right").ConvertToImage();
785 wxImage ICursorUp = style->GetIcon("up").ConvertToImage();
786 wxImage ICursorDown = style->GetIcon("down").ConvertToImage();
787 wxImage ICursorPencil =
788 style->GetIconScaled("pencil", pencilScale).ConvertToImage();
789 wxImage ICursorCross = style->GetIcon("cross").ConvertToImage();
790
791#if !defined(__WXMSW__) && !defined(__WXQT__)
792 ICursorLeft.ConvertAlphaToMask(128);
793 ICursorRight.ConvertAlphaToMask(128);
794 ICursorUp.ConvertAlphaToMask(128);
795 ICursorDown.ConvertAlphaToMask(128);
796 ICursorPencil.ConvertAlphaToMask(10);
797 ICursorCross.ConvertAlphaToMask(10);
798#endif
799
800 if (ICursorLeft.Ok()) {
801 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0);
802 ICursorLeft.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
803 pCursorLeft = new wxCursor(ICursorLeft);
804 } else
805 pCursorLeft = new wxCursor(wxCURSOR_ARROW);
806
807 if (ICursorRight.Ok()) {
808 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 31);
809 ICursorRight.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 15);
810 pCursorRight = new wxCursor(ICursorRight);
811 } else
812 pCursorRight = new wxCursor(wxCURSOR_ARROW);
813
814 if (ICursorUp.Ok()) {
815 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
816 ICursorUp.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 0);
817 pCursorUp = new wxCursor(ICursorUp);
818 } else
819 pCursorUp = new wxCursor(wxCURSOR_ARROW);
820
821 if (ICursorDown.Ok()) {
822 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 15);
823 ICursorDown.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 31);
824 pCursorDown = new wxCursor(ICursorDown);
825 } else
826 pCursorDown = new wxCursor(wxCURSOR_ARROW);
827
828 if (ICursorPencil.Ok()) {
829 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 0 * pencilScale);
830 ICursorPencil.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 16 * pencilScale);
831 pCursorPencil = new wxCursor(ICursorPencil);
832 } else
833 pCursorPencil = new wxCursor(wxCURSOR_ARROW);
834
835 if (ICursorCross.Ok()) {
836 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, 13);
837 ICursorCross.SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, 12);
838 pCursorCross = new wxCursor(ICursorCross);
839 } else
840 pCursorCross = new wxCursor(wxCURSOR_ARROW);
841
842 pCursorArrow = new wxCursor(wxCURSOR_ARROW);
843 pPlugIn_Cursor = NULL;
844}
845
846void ChartCanvas::CanvasApplyLocale() {
847 CreateDepthUnitEmbossMaps(m_cs);
848 CreateOZEmbossMapData(m_cs);
849}
850
851void ChartCanvas::SetupGlCanvas() {
852#ifndef __ANDROID__
853#ifdef ocpnUSE_GL
854 if (!g_bdisable_opengl) {
855 if (g_bopengl) {
856 wxLogMessage("Creating glChartCanvas");
857 m_glcc = new glChartCanvas(this);
858
859 // We use one context for all GL windows, so that textures etc will be
860 // automatically shared
861 if (IsPrimaryCanvas()) {
862 // qDebug() << "Creating Primary Context";
863
864 // wxGLContextAttrs ctxAttr;
865 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
866 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
867 // NULL, &ctxAttr);
868 wxGLContext *pctx = new wxGLContext(m_glcc);
869 m_glcc->SetContext(pctx);
870 g_pGLcontext = pctx; // Save a copy of the common context
871 } else {
872#ifdef __WXOSX__
873 m_glcc->SetContext(new wxGLContext(m_glcc, g_pGLcontext));
874#else
875 m_glcc->SetContext(g_pGLcontext); // If not primary canvas, use the
876 // saved common context
877#endif
878 }
879 }
880 }
881#endif
882#endif
883
884#ifdef __ANDROID__ // ocpnUSE_GL
885 if (!g_bdisable_opengl) {
886 if (g_bopengl) {
887 // qDebug() << "SetupGlCanvas";
888 wxLogMessage("Creating glChartCanvas");
889
890 // We use one context for all GL windows, so that textures etc will be
891 // automatically shared
892 if (IsPrimaryCanvas()) {
893 qDebug() << "Creating Primary glChartCanvas";
894
895 // wxGLContextAttrs ctxAttr;
896 // ctxAttr.PlatformDefaults().CoreProfile().OGLVersion(3,
897 // 2).EndList(); wxGLContext *pctx = new wxGLContext(m_glcc,
898 // NULL, &ctxAttr);
899 m_glcc = new glChartCanvas(this);
900
901 wxGLContext *pctx = new wxGLContext(m_glcc);
902 m_glcc->SetContext(pctx);
903 g_pGLcontext = pctx; // Save a copy of the common context
904 m_glcc->m_pParentCanvas = this;
905 // m_glcc->Reparent(this);
906 } else {
907 qDebug() << "Creating Secondary glChartCanvas";
908 // QGLContext *pctx =
909 // gFrame->GetPrimaryCanvas()->GetglCanvas()->GetQGLContext(); qDebug()
910 // << "pctx: " << pctx;
911
912 m_glcc = new glChartCanvas(
913 gFrame, gFrame->GetPrimaryCanvas()->GetglCanvas()); // Shared
914 // m_glcc = new glChartCanvas(this, pctx); //Shared
915 // m_glcc = new glChartCanvas(this, wxPoint(900, 0));
916 wxGLContext *pwxctx = new wxGLContext(m_glcc);
917 m_glcc->SetContext(pwxctx);
918 m_glcc->m_pParentCanvas = this;
919 // m_glcc->Reparent(this);
920 }
921 }
922 }
923#endif
924}
925
926void ChartCanvas::OnKillFocus(wxFocusEvent &WXUNUSED(event)) {
927 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
928
929 // On Android, we get a KillFocus on just about every keystroke.
930 // Why?
931#ifdef __ANDROID__
932 return;
933#endif
934
935 // Special logic:
936 // On OSX in GL mode, each mouse click causes a kill and immediate regain of
937 // canvas focus. Why??? Who knows... So, we provide for this case by
938 // starting a timer if required to actually Finish() a route on a legitimate
939 // focus change, but not if the focus is quickly regained ( <20 msec.) on
940 // this canvas.
941#ifdef __WXOSX__
942 if (m_routeState && m_FinishRouteOnKillFocus)
943 m_routeFinishTimer.Start(20, wxTIMER_ONE_SHOT);
944#else
945 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
946#endif
947}
948
949void ChartCanvas::OnSetFocus(wxFocusEvent &WXUNUSED(event)) {
950 m_routeFinishTimer.Stop();
951
952 // Try to keep the global top-line menubar selections up to date with the
953 // current "focus" canvas
954 gFrame->UpdateGlobalMenuItems(this);
955
956 RefreshRect(wxRect(0, 0, GetClientSize().x, m_focus_indicator_pix), false);
957}
958
959void ChartCanvas::OnRouteFinishTimerEvent(wxTimerEvent &event) {
960 if (m_routeState && m_FinishRouteOnKillFocus) FinishRoute();
961}
962
963#ifdef HAVE_WX_GESTURE_EVENTS
964void ChartCanvas::OnLongPress(wxLongPressEvent &event) {
965#ifdef __ANDROID__
966 /* we defer the popup menu call upon the leftup event
967 else the menu disappears immediately,
968 (see
969 http://wxwidgets.10942.n7.nabble.com/Popupmenu-disappears-immediately-if-called-from-QueueEvent-td92572.html)
970 */
971 m_popupWanted = true;
972#else
973 m_inLongPress = !g_bhide_context_menus;
974
975 // Send a synthetic mouse left-up event to sync the mouse pan logic.
976 m_menuPos = event.GetPosition();
977 wxMouseEvent ev(wxEVT_LEFT_UP);
978 ev.m_x = m_menuPos.x;
979 ev.m_y = m_menuPos.y;
980 wxPostEvent(this, ev);
981
982 // In touch mode, send a "RIGHT CLICK" event, for plugins
983 if (g_btouch) {
984 wxMouseEvent ev_right_click(wxEVT_RIGHT_DOWN);
985 ev_right_click.m_x = m_menuPos.x;
986 ev_right_click.m_y = m_menuPos.y;
987 MouseEvent(ev_right_click);
988 }
989#endif
990}
991
992void ChartCanvas::OnPressAndTap(wxPressAndTapEvent &event) {
993 // not implemented yet
994}
995
996void ChartCanvas::OnRightUp(wxMouseEvent &event) { MouseEvent(event); }
997
998void ChartCanvas::OnRightDown(wxMouseEvent &event) { MouseEvent(event); }
999
1000void ChartCanvas::OnLeftUp(wxMouseEvent &event) {
1001#ifdef __WXGTK__
1002 long dt = m_sw_left_up.Time() - m_sw_up_time;
1003 m_sw_up_time = m_sw_left_up.Time();
1004
1005 // printf(" dt %ld\n",dt);
1006 if (dt < 5) {
1007 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1008 // ignore it.
1009 return;
1010 }
1011#endif
1012 // printf("Left_UP\n");
1013
1014 wxPoint pos = event.GetPosition();
1015
1016 m_leftdown = false;
1017
1018 if (!m_popupWanted) {
1019 wxMouseEvent ev(wxEVT_LEFT_UP);
1020 ev.m_x = pos.x;
1021 ev.m_y = pos.y;
1022 MouseEvent(ev);
1023 return;
1024 }
1025
1026 m_popupWanted = false;
1027
1028 wxMouseEvent ev(wxEVT_RIGHT_DOWN);
1029 ev.m_x = pos.x;
1030 ev.m_y = pos.y;
1031
1032 MouseEvent(ev);
1033}
1034
1035void ChartCanvas::OnLeftDown(wxMouseEvent &event) {
1036 m_leftdown = true;
1037
1038 // Detect and manage multiple left-downs coming from GTK mouse emulation
1039#ifdef __WXGTK__
1040 long dt = m_sw_left_down.Time() - m_sw_down_time;
1041 m_sw_down_time = m_sw_left_down.Time();
1042
1043 // printf("Left_DOWN_Entry: dt: %ld\n", dt);
1044
1045 // In touch mode, GTK mouse emulation will send duplicate mouse-down events.
1046 // The timing between the two events is dependent upon the wxWidgets
1047 // message queue status, and the processing time required for intervening
1048 // events.
1049 // We detect and remove the duplicate events by measuring the elapsed time
1050 // between arrival of events.
1051 // Choose a duplicate detection time long enough to catch worst case time lag
1052 // between duplicating events, but considerably shorter than the nominal
1053 // "intentional double-click" time interval defined generally as 350 msec.
1054 if (dt < 100) { // 10 taps per sec. is about the maximum human rate.
1055 // printf(" Ignored %ld\n",dt );// This is a duplicate emulated event,
1056 // ignore it.
1057 return;
1058 }
1059#endif
1060
1061 // printf("Left_DOWN\n");
1062
1063 // detect and manage double-tap
1064#ifdef __WXGTK__
1065 int max_double_click_distance = wxSystemSettings::GetMetric(wxSYS_DCLICK_X) *
1066 2; // Use system setting for distance
1067 wxRect tap_area(m_lastTapPos.x - max_double_click_distance,
1068 m_lastTapPos.y - max_double_click_distance,
1069 max_double_click_distance * 2, max_double_click_distance * 2);
1070
1071 // A new tap has started, check if it's close enough and in time
1072 if (m_tap_timer.IsRunning() && tap_area.Contains(event.GetPosition())) {
1073 // printf(" TapBump 1\n");
1074 m_tap_count += 1;
1075 } else {
1076 // printf(" TapSet 1\n");
1077 m_tap_count = 1;
1078 m_lastTapPos = event.GetPosition();
1079 m_tap_timer.StartOnce(
1080 350); //(wxSystemSettings::GetMetric(wxSYS_DCLICK_MSEC));
1081 }
1082
1083 if (m_tap_count == 2) {
1084 // printf(" Doubletap detected\n");
1085 m_tap_count = 0; // Reset after a double-tap
1086
1087 wxMouseEvent ev(wxEVT_LEFT_DCLICK);
1088 ev.m_x = event.m_x;
1089 ev.m_y = event.m_y;
1090 // wxPostEvent(this, ev);
1091 MouseEvent(ev);
1092 return;
1093 }
1094
1095#endif
1096
1097 MouseEvent(event);
1098}
1099
1100void ChartCanvas::OnMotion(wxMouseEvent &event) {
1101 /* This is a workaround, to the fact that on touchscreen, OnMotion comes with
1102 dragging, upon simple click, and without the OnLeftDown event before Thus,
1103 this consists in skiping it, and setting the leftdown bit according to a
1104 status that we trust */
1105 event.m_leftDown = m_leftdown;
1106 MouseEvent(event);
1107}
1108
1109void ChartCanvas::OnZoom(wxZoomGestureEvent &event) {
1110 /* there are spurious end zoom events upon right-click */
1111 if (event.IsGestureEnd()) return;
1112
1113 double factor = event.GetZoomFactor();
1114
1115 if (event.IsGestureStart() || m_oldVPSScale < 0) {
1116 m_oldVPSScale = GetVPScale();
1117 }
1118
1119 double current_vps = GetVPScale();
1120 double wanted_factor = m_oldVPSScale / current_vps * factor;
1121
1122 ZoomCanvas(wanted_factor, true, false);
1123
1124 // Allow combined zoom/pan operation
1125 if (event.IsGestureStart()) {
1126 m_zoomStartPoint = event.GetPosition();
1127 } else {
1128 wxPoint delta = event.GetPosition() - m_zoomStartPoint;
1129 PanCanvas(-delta.x, -delta.y);
1130 m_zoomStartPoint = event.GetPosition();
1131 }
1132}
1133
1134void ChartCanvas::OnWheel(wxMouseEvent &event) { MouseEvent(event); }
1135
1136void ChartCanvas::OnDoubleLeftClick(wxMouseEvent &event) {
1137 DoRotateCanvas(0.0);
1138}
1139#endif /* HAVE_WX_GESTURE_EVENTS */
1140
1141void ChartCanvas::OnTapTimer(wxTimerEvent &event) {
1142 // printf("tap timer %d\n", m_tap_count);
1143 m_tap_count = 0;
1144}
1145
1146void ChartCanvas::OnMenuTimer(wxTimerEvent &event) {
1147 m_FinishRouteOnKillFocus = false;
1148 CallPopupMenu(m_menuPos.x, m_menuPos.y);
1149 m_FinishRouteOnKillFocus = true;
1150}
1151
1152void ChartCanvas::ApplyCanvasConfig(canvasConfig *pcc) {
1153 SetViewPoint(pcc->iLat, pcc->iLon, pcc->iScale, 0., pcc->iRotation);
1154 m_vLat = pcc->iLat;
1155 m_vLon = pcc->iLon;
1156
1157 m_restore_dbindex = pcc->DBindex;
1158 m_bFollow = pcc->bFollow;
1159 if (pcc->GroupID < 0) pcc->GroupID = 0;
1160
1161 if (pcc->GroupID > (int)g_pGroupArray->GetCount())
1162 m_groupIndex = 0;
1163 else
1164 m_groupIndex = pcc->GroupID;
1165
1166 if (pcc->bQuilt != GetQuiltMode()) ToggleCanvasQuiltMode();
1167
1168 ShowTides(pcc->bShowTides);
1169 ShowCurrents(pcc->bShowCurrents);
1170
1171 SetShowDepthUnits(pcc->bShowDepthUnits);
1172 SetShowGrid(pcc->bShowGrid);
1173 SetShowOutlines(pcc->bShowOutlines);
1174
1175 SetShowAIS(pcc->bShowAIS);
1176 SetAttenAIS(pcc->bAttenAIS);
1177
1178 // ENC options
1179 SetShowENCText(pcc->bShowENCText);
1180 m_encDisplayCategory = pcc->nENCDisplayCategory;
1181 m_encShowDepth = pcc->bShowENCDepths;
1182 m_encShowLightDesc = pcc->bShowENCLightDescriptions;
1183 m_encShowBuoyLabels = pcc->bShowENCBuoyLabels;
1184 m_encShowLights = pcc->bShowENCLights;
1185 m_bShowVisibleSectors = pcc->bShowENCVisibleSectorLights;
1186 m_encShowAnchor = pcc->bShowENCAnchorInfo;
1187 m_encShowDataQual = pcc->bShowENCDataQuality;
1188
1189 bool courseUp = pcc->bCourseUp;
1190 bool headUp = pcc->bHeadUp;
1191 m_upMode = NORTH_UP_MODE;
1192 if (courseUp)
1193 m_upMode = COURSE_UP_MODE;
1194 else if (headUp)
1195 m_upMode = HEAD_UP_MODE;
1196
1197 m_bLookAhead = pcc->bLookahead;
1198
1199 m_singleChart = NULL;
1200}
1201
1202void ChartCanvas::ApplyGlobalSettings() {
1203 // GPS compas window
1204 if (m_Compass) {
1205 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1206 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1207 }
1208 if (m_notification_button) m_notification_button->UpdateStatus();
1209}
1210
1211void ChartCanvas::CheckGroupValid(bool showMessage, bool switchGroup0) {
1212 bool groupOK = CheckGroup(m_groupIndex);
1213
1214 if (!groupOK) {
1215 SetGroupIndex(m_groupIndex, true);
1216 }
1217}
1218
1219void ChartCanvas::SetShowGPS(bool bshow) {
1220 if (m_bShowGPS != bshow) {
1221 delete m_Compass;
1222 m_Compass = new ocpnCompass(this, bshow);
1223 m_Compass->SetScaleFactor(g_compass_scalefactor);
1224 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1225 }
1226 m_bShowGPS = bshow;
1227}
1228
1229void ChartCanvas::SetShowGPSCompassWindow(bool bshow) {
1230 m_bShowCompassWin = bshow;
1231 if (m_Compass) {
1232 m_Compass->Show(m_bShowCompassWin && g_bShowCompassWin);
1233 if (m_bShowCompassWin && g_bShowCompassWin) m_Compass->UpdateStatus();
1234 }
1235}
1236
1237int ChartCanvas::GetPianoHeight() {
1238 int height = 0;
1239 if (g_bShowChartBar && GetPiano()) height = m_Piano->GetHeight();
1240
1241 return height;
1242}
1243
1244void ChartCanvas::ConfigureChartBar() {
1245 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
1246
1247 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
1248 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
1249
1250 if (GetQuiltMode()) {
1251 m_Piano->SetRoundedRectangles(true);
1252 }
1253 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
1254 m_Piano->SetPolyIcon(new wxBitmap(style->GetIcon("polyprj")));
1255 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
1256}
1257
1258void ChartCanvas::ShowTides(bool bShow) {
1259 gFrame->LoadHarmonics();
1260
1261 if (ptcmgr->IsReady()) {
1262 SetbShowTide(bShow);
1263
1264 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, bShow);
1265 } else {
1266 wxLogMessage("Chart1::Event...TCMgr Not Available");
1267 SetbShowTide(false);
1268 parent_frame->SetMenubarItemState(ID_MENU_SHOW_TIDES, false);
1269 }
1270
1271 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1272 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1273
1274 // TODO
1275 // if( GetbShowTide() ) {
1276 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1277 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1278 // update
1279 // } else
1280 // FrameTCTimer.Stop();
1281}
1282
1283void ChartCanvas::ShowCurrents(bool bShow) {
1284 gFrame->LoadHarmonics();
1285
1286 if (ptcmgr->IsReady()) {
1287 SetbShowCurrent(bShow);
1288 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, bShow);
1289 } else {
1290 wxLogMessage("Chart1::Event...TCMgr Not Available");
1291 SetbShowCurrent(false);
1292 parent_frame->SetMenubarItemState(ID_MENU_SHOW_CURRENTS, false);
1293 }
1294
1295 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
1296 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
1297
1298 // TODO
1299 // if( GetbShowCurrent() ) {
1300 // FrameTCTimer.Start( TIMER_TC_VALUE_SECONDS * 1000,
1301 // wxTIMER_CONTINUOUS ); SetbTCUpdate( true ); // force immediate
1302 // update
1303 // } else
1304 // FrameTCTimer.Stop();
1305}
1306
1307// TODO
1308static ChartDummy *pDummyChart;
1309
1312
1313void ChartCanvas::canvasRefreshGroupIndex() { SetGroupIndex(m_groupIndex); }
1314
1315void ChartCanvas::SetGroupIndex(int index, bool autoSwitch) {
1316 SetAlertString("");
1317
1318 int new_index = index;
1319 if (index > (int)g_pGroupArray->GetCount()) new_index = 0;
1320
1321 bool bgroup_override = false;
1322 int old_group_index = new_index;
1323
1324 if (!CheckGroup(new_index)) {
1325 new_index = 0;
1326 bgroup_override = true;
1327 }
1328
1329 if (!autoSwitch && (index <= (int)g_pGroupArray->GetCount()))
1330 new_index = index;
1331
1332 // Get the currently displayed chart native scale, and the current ViewPort
1333 int current_chart_native_scale = GetCanvasChartNativeScale();
1334 ViewPort vp = GetVP();
1335
1336 m_groupIndex = new_index;
1337
1338 // Are there ENCs in this group
1339 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
1340
1341 // Update the MUIBar for ENC availability
1342 if (m_muiBar) m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
1343
1344 // Allow the chart database to pre-calculate the MBTile inclusion test
1345 // boolean...
1346 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1347
1348 // Invalidate the "sticky" chart on group change, since it might not be in
1349 // the new group
1350 g_sticky_chart = -1;
1351
1352 // We need a chartstack and quilt to figure out which chart to open in the
1353 // new group
1354 UpdateCanvasOnGroupChange();
1355
1356 int dbi_now = -1;
1357 if (GetQuiltMode()) dbi_now = GetQuiltReferenceChartIndex();
1358
1359 int dbi_hint = FindClosestCanvasChartdbIndex(current_chart_native_scale);
1360
1361 // If a new reference chart is indicated, set a good scale for it.
1362 if ((dbi_now != dbi_hint) || !GetQuiltMode()) {
1363 double best_scale = GetBestStartScale(dbi_hint, vp);
1364 SetVPScale(best_scale);
1365 }
1366
1367 if (GetQuiltMode()) dbi_hint = GetQuiltReferenceChartIndex();
1368
1369 // Refresh the canvas, selecting the "best" chart,
1370 // applying the prior ViewPort exactly
1371 canvasChartsRefresh(dbi_hint);
1372
1373 UpdateCanvasControlBar();
1374
1375 if (!autoSwitch && bgroup_override) {
1376 // show a short timed message box
1377 wxString msg(_("Group \""));
1378
1379 ChartGroup *pGroup = g_pGroupArray->Item(new_index - 1);
1380 msg += pGroup->m_group_name;
1381
1382 msg += _("\" is empty.");
1383
1384 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxICON_INFORMATION, 2);
1385
1386 return;
1387 }
1388
1389 // Message box is deferred so that canvas refresh occurs properly before
1390 // dialog
1391 if (bgroup_override) {
1392 wxString msg(_("Group \""));
1393
1394 ChartGroup *pGroup = g_pGroupArray->Item(old_group_index - 1);
1395 msg += pGroup->m_group_name;
1396
1397 msg += _("\" is empty, switching to \"All Active Charts\" group.");
1398
1399 OCPNMessageBox(this, msg, _("OpenCPN Group Notice"), wxOK, 5);
1400 }
1401}
1402
1403bool ChartCanvas::CheckGroup(int igroup) {
1404 if (!ChartData) return true; // Not known yet...
1405
1406 if (igroup == 0) return true; // "all charts" is always OK
1407
1408 if (igroup < 0) // negative group is an error
1409 return false;
1410
1411 ChartGroup *pGroup = g_pGroupArray->Item(igroup - 1);
1412
1413 if (pGroup->m_element_array.empty()) // truly empty group prompts a warning,
1414 // and auto-shift to group 0
1415 return false;
1416
1417 for (const auto &elem : pGroup->m_element_array) {
1418 for (unsigned int ic = 0;
1419 ic < (unsigned int)ChartData->GetChartTableEntries(); ic++) {
1420 ChartTableEntry *pcte = ChartData->GetpChartTableEntry(ic);
1421 wxString chart_full_path(pcte->GetpFullPath(), wxConvUTF8);
1422
1423 if (chart_full_path.StartsWith(elem.m_element_name)) return true;
1424 }
1425 }
1426
1427 // If necessary, check for GSHHS
1428 for (const auto &elem : pGroup->m_element_array) {
1429 const wxString &element_root = elem.m_element_name;
1430 wxString test_string = "GSHH";
1431 if (element_root.Upper().Contains(test_string)) return true;
1432 }
1433
1434 return false;
1435}
1436
1437void ChartCanvas::canvasChartsRefresh(int dbi_hint) {
1438 if (!ChartData) return;
1439
1440 AbstractPlatform::ShowBusySpinner();
1441
1442 double old_scale = GetVPScale();
1443 InvalidateQuilt();
1444 SetQuiltRefChart(-1);
1445
1446 m_singleChart = NULL;
1447
1448 // delete m_pCurrentStack;
1449 // m_pCurrentStack = NULL;
1450
1451 // Build a new ChartStack
1452 if (!m_pCurrentStack) {
1453 m_pCurrentStack = new ChartStack;
1454 ChartData->BuildChartStack(m_pCurrentStack, m_vLat, m_vLon, m_groupIndex);
1455 }
1456
1457 if (-1 != dbi_hint) {
1458 if (GetQuiltMode()) {
1459 GetpCurrentStack()->SetCurrentEntryFromdbIndex(dbi_hint);
1460 SetQuiltRefChart(dbi_hint);
1461 } else {
1462 // Open the saved chart
1463 ChartBase *pTentative_Chart;
1464 pTentative_Chart = ChartData->OpenChartFromDB(dbi_hint, FULL_INIT);
1465
1466 if (pTentative_Chart) {
1467 /* m_singleChart is always NULL here, (set above) should this go before
1468 * that? */
1469 if (m_singleChart) m_singleChart->Deactivate();
1470
1471 m_singleChart = pTentative_Chart;
1472 m_singleChart->Activate();
1473
1474 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
1475 GetpCurrentStack(), m_singleChart->GetFullPath());
1476 }
1477 }
1478
1479 // refresh_Piano();
1480 } else {
1481 // Select reference chart from the stack, as though clicked by user
1482 // Make it the smallest scale chart on the stack
1483 GetpCurrentStack()->CurrentStackEntry = GetpCurrentStack()->nEntry - 1;
1484 int selected_index = GetpCurrentStack()->GetCurrentEntrydbIndex();
1485 SetQuiltRefChart(selected_index);
1486 }
1487
1488 // Validate the correct single chart, or set the quilt mode as appropriate
1489 SetupCanvasQuiltMode();
1490 if (!GetQuiltMode() && m_singleChart == 0) {
1491 // use a dummy like in DoChartUpdate
1492 if (NULL == pDummyChart) pDummyChart = new ChartDummy;
1493 m_singleChart = pDummyChart;
1494 SetVPScale(old_scale);
1495 }
1496
1497 ReloadVP();
1498
1499 UpdateCanvasControlBar();
1500 UpdateGPSCompassStatusBox(true);
1501
1502 SetCursor(wxCURSOR_ARROW);
1503
1504 AbstractPlatform::HideBusySpinner();
1505}
1506
1507bool ChartCanvas::DoCanvasUpdate() {
1508 double tLat, tLon; // Chart Stack location
1509 double vpLat, vpLon; // ViewPort location
1510 bool blong_jump = false;
1511 meters_to_shift = 0;
1512 dir_to_shift = 0;
1513
1514 bool bNewChart = false;
1515 bool bNewView = false;
1516 bool bCanvasChartAutoOpen = true; // debugging
1517
1518 bool bNewPiano = false;
1519 bool bOpenSpecified;
1520 ChartStack LastStack;
1521 ChartBase *pLast_Ch;
1522
1523 ChartStack WorkStack;
1524
1525 if (bDBUpdateInProgress) return false;
1526 if (!ChartData) return false;
1527
1528 if (ChartData->IsBusy()) return false;
1529 if (m_chart_drag_inertia_active) return false;
1530
1531 // Startup case:
1532 // Quilting is enabled, but the last chart seen was not quiltable
1533 // In this case, drop to single chart mode, set persistence flag,
1534 // And open the specified chart
1535 // TODO implement this
1536 // if( m_bFirstAuto && ( g_restore_dbindex >= 0 ) ) {
1537 // if( GetQuiltMode() ) {
1538 // if( !IsChartQuiltableRef( g_restore_dbindex ) ) {
1539 // gFrame->ToggleQuiltMode();
1540 // m_bpersistent_quilt = true;
1541 // m_singleChart = NULL;
1542 // }
1543 // }
1544 // }
1545
1546 // If in auto-follow mode, use the current glat,glon to build chart
1547 // stack. Otherwise, use vLat, vLon gotten from click on chart canvas, or
1548 // other means
1549
1550 if (m_bFollow) {
1551 tLat = gLat;
1552 tLon = gLon;
1553
1554 // Set the ViewPort center based on the OWNSHIP offset
1555 double dx = m_OSoffsetx;
1556 double dy = m_OSoffsety;
1557 double d_east = dx / GetVP().view_scale_ppm;
1558 double d_north = dy / GetVP().view_scale_ppm;
1559
1560 if (GetUpMode() == NORTH_UP_MODE) {
1561 fromSM(d_east, d_north, gLat, gLon, &vpLat, &vpLon);
1562 } else {
1563 double offset_angle = atan2(d_north, d_east);
1564 double offset_distance = sqrt((d_north * d_north) + (d_east * d_east));
1565 double chart_angle = GetVPRotation();
1566 double target_angle = chart_angle + offset_angle;
1567 double d_east_mod = offset_distance * cos(target_angle);
1568 double d_north_mod = offset_distance * sin(target_angle);
1569 fromSM(d_east_mod, d_north_mod, gLat, gLon, &vpLat, &vpLon);
1570 }
1571
1572 // on lookahead mode, adjust the vp center point
1573 if (m_bLookAhead && bGPSValid && !m_MouseDragging) {
1574 double cog_to_use = gCog;
1575 if (g_btenhertz &&
1576 (fabs(gCog - gCog_gt) > 20)) { // big COG change in process
1577 cog_to_use = gCog_gt;
1578 blong_jump = true;
1579 }
1580 if (!g_btenhertz) cog_to_use = g_COGAvg;
1581
1582 double angle = cog_to_use + (GetVPRotation() * 180. / PI);
1583
1584 double pixel_deltay = (cos(angle * PI / 180.)) * GetCanvasHeight() / 4;
1585 double pixel_deltax = (sin(angle * PI / 180.)) * GetCanvasWidth() / 4;
1586
1587 double pixel_delta_tent =
1588 sqrt((pixel_deltay * pixel_deltay) + (pixel_deltax * pixel_deltax));
1589
1590 double pixel_delta = 0;
1591
1592 // The idea here is to cancel the effect of LookAhead for slow gSog, to
1593 // avoid jumping of the vp center point during slow maneuvering, or at
1594 // anchor....
1595 if (!std::isnan(gSog)) {
1596 if (gSog < 2.0)
1597 pixel_delta = 0.;
1598 else
1599 pixel_delta = pixel_delta_tent;
1600 }
1601
1602 meters_to_shift = 0;
1603 dir_to_shift = 0;
1604 if (!std::isnan(gCog)) {
1605 meters_to_shift = cos(gLat * PI / 180.) * pixel_delta / GetVPScale();
1606 dir_to_shift = cog_to_use;
1607 ll_gc_ll(gLat, gLon, dir_to_shift, meters_to_shift / 1852., &vpLat,
1608 &vpLon);
1609 } else {
1610 vpLat = gLat;
1611 vpLon = gLon;
1612 }
1613 } else if (m_bLookAhead && (!bGPSValid || m_MouseDragging)) {
1614 m_OSoffsetx = 0; // center ownship on loss of GPS
1615 m_OSoffsety = 0;
1616 vpLat = gLat;
1617 vpLon = gLon;
1618 }
1619
1620 } else {
1621 tLat = m_vLat;
1622 tLon = m_vLon;
1623 vpLat = m_vLat;
1624 vpLon = m_vLon;
1625 }
1626
1627 if (GetQuiltMode()) {
1628 int current_db_index = -1;
1629 if (m_pCurrentStack)
1630 current_db_index =
1631 m_pCurrentStack
1632 ->GetCurrentEntrydbIndex(); // capture the currently selected Ref
1633 // chart dbIndex
1634 else
1635 m_pCurrentStack = new ChartStack;
1636
1637 // This logic added to enable opening a chart when there is no
1638 // previous chart indication, either from inital startup, or from adding
1639 // new chart directory
1640 if (m_bautofind && (-1 == GetQuiltReferenceChartIndex()) &&
1641 m_pCurrentStack) {
1642 if (m_pCurrentStack->nEntry) {
1643 int new_dbIndex = m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry -
1644 1); // smallest scale
1645 SelectQuiltRefdbChart(new_dbIndex, true);
1646 m_bautofind = false;
1647 }
1648 }
1649
1650 ChartData->BuildChartStack(m_pCurrentStack, tLat, tLon, m_groupIndex);
1651 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
1652
1653 if (m_bFirstAuto) {
1654 // Allow the chart database to pre-calculate the MBTile inclusion test
1655 // boolean...
1656 ChartData->CheckExclusiveTileGroup(m_canvasIndex);
1657
1658 // Calculate DPI compensation scale, i.e., the ratio of logical pixels to
1659 // physical pixels. On standard DPI displays where logical = physical
1660 // pixels, this ratio would be 1.0. On Retina displays where physical = 2x
1661 // logical pixels, this ratio would be 0.5.
1662 double proposed_scale_onscreen =
1663 GetCanvasScaleFactor() / GetVPScale(); // as set from config load
1664
1665 int initial_db_index = m_restore_dbindex;
1666 if (initial_db_index < 0) {
1667 if (m_pCurrentStack->nEntry) {
1668 initial_db_index =
1669 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
1670 } else
1671 m_bautofind = true; // initial_db_index = 0;
1672 }
1673
1674 if (m_pCurrentStack->nEntry) {
1675 int initial_type = ChartData->GetDBChartType(initial_db_index);
1676
1677 // Check to see if the target new chart is quiltable as a reference
1678 // chart
1679
1680 if (!IsChartQuiltableRef(initial_db_index)) {
1681 // If it is not quiltable, then walk the stack up looking for a
1682 // satisfactory chart i.e. one that is quiltable and of the same type
1683 // XXX if there's none?
1684 int stack_index = 0;
1685
1686 if (stack_index >= 0) {
1687 while ((stack_index < m_pCurrentStack->nEntry - 1)) {
1688 int test_db_index = m_pCurrentStack->GetDBIndex(stack_index);
1689 if (IsChartQuiltableRef(test_db_index) &&
1690 (initial_type ==
1691 ChartData->GetDBChartType(initial_db_index))) {
1692 initial_db_index = test_db_index;
1693 break;
1694 }
1695 stack_index++;
1696 }
1697 }
1698 }
1699
1700 ChartBase *pc = ChartData->OpenChartFromDB(initial_db_index, FULL_INIT);
1701 if (pc) {
1702 SetQuiltRefChart(initial_db_index);
1703 m_pCurrentStack->SetCurrentEntryFromdbIndex(initial_db_index);
1704 }
1705 }
1706 // TODO: GetCanvasScaleFactor() / proposed_scale_onscreen simplifies to
1707 // just GetVPScale(), so I'm not sure why it's necessary to define the
1708 // proposed_scale_onscreen variable.
1709 bNewView |= SetViewPoint(vpLat, vpLon,
1710 GetCanvasScaleFactor() / proposed_scale_onscreen,
1711 0, GetVPRotation());
1712 }
1713 // Measure rough jump distance if in bfollow mode
1714 // No good reason to do smooth pan for
1715 // jump distance more than one screen width at scale.
1716 bool super_jump = false;
1717 if (m_bFollow) {
1718 double pixlt = fabs(vpLat - m_vLat) * 1852 * 60 * GetVPScale();
1719 double pixlg = fabs(vpLon - m_vLon) * 1852 * 60 * GetVPScale();
1720 if (wxMax(pixlt, pixlg) > GetCanvasWidth()) super_jump = true;
1721 }
1722#if 0
1723 if (m_bFollow && g_btenhertz && !super_jump && !m_bLookAhead && !g_btouch && !m_bzooming) {
1724 int nstep = 5;
1725 if (blong_jump) nstep = 20;
1726 StartTimedMovementVP(vpLat, vpLon, nstep);
1727 } else
1728#endif
1729 {
1730 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(), 0, GetVPRotation());
1731 }
1732
1733 goto update_finish;
1734 }
1735
1736 // Single Chart Mode from here....
1737 pLast_Ch = m_singleChart;
1738 ChartTypeEnum new_open_type;
1739 ChartFamilyEnum new_open_family;
1740 if (pLast_Ch) {
1741 new_open_type = pLast_Ch->GetChartType();
1742 new_open_family = pLast_Ch->GetChartFamily();
1743 } else {
1744 new_open_type = CHART_TYPE_KAP;
1745 new_open_family = CHART_FAMILY_RASTER;
1746 }
1747
1748 bOpenSpecified = m_bFirstAuto;
1749
1750 // Make sure the target stack is valid
1751 if (NULL == m_pCurrentStack) m_pCurrentStack = new ChartStack;
1752
1753 // Build a chart stack based on tLat, tLon
1754 if (0 == ChartData->BuildChartStack(&WorkStack, tLat, tLon, g_sticky_chart,
1755 m_groupIndex)) { // Bogus Lat, Lon?
1756 if (NULL == pDummyChart) {
1757 pDummyChart = new ChartDummy;
1758 bNewChart = true;
1759 }
1760
1761 if (m_singleChart)
1762 if (m_singleChart->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1763
1764 m_singleChart = pDummyChart;
1765
1766 // If the current viewpoint is invalid, set the default scale to
1767 // something reasonable.
1768 double set_scale = GetVPScale();
1769 if (!GetVP().IsValid()) set_scale = 1. / 20000.;
1770
1771 bNewView |= SetViewPoint(tLat, tLon, set_scale, 0, GetVPRotation());
1772
1773 // If the chart stack has just changed, there is new status
1774 if (WorkStack.nEntry && m_pCurrentStack->nEntry) {
1775 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1776 bNewPiano = true;
1777 bNewChart = true;
1778 }
1779 }
1780
1781 // Copy the new (by definition empty) stack into the target stack
1782 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1783
1784 goto update_finish;
1785 }
1786
1787 // Check to see if Chart Stack has changed
1788 if (!ChartData->EqualStacks(&WorkStack, m_pCurrentStack)) {
1789 // New chart stack, so...
1790 bNewPiano = true;
1791
1792 // Save a copy of the current stack
1793 ChartData->CopyStack(&LastStack, m_pCurrentStack);
1794
1795 // Copy the new stack into the target stack
1796 ChartData->CopyStack(m_pCurrentStack, &WorkStack);
1797
1798 // Is Current Chart in new stack?
1799
1800 int tEntry = -1;
1801 if (NULL != m_singleChart) // this handles startup case
1802 tEntry = ChartData->GetStackEntry(m_pCurrentStack,
1803 m_singleChart->GetFullPath());
1804
1805 if (tEntry != -1) { // m_singleChart is in the new stack
1806 m_pCurrentStack->CurrentStackEntry = tEntry;
1807 bNewChart = false;
1808 }
1809
1810 else // m_singleChart is NOT in new stack
1811 { // So, need to open a new chart
1812 // Find the largest scale raster chart that opens OK
1813
1814 ChartBase *pProposed = NULL;
1815
1816 if (bCanvasChartAutoOpen) {
1817 bool search_direction =
1818 false; // default is to search from lowest to highest
1819 int start_index = 0;
1820
1821 // A special case: If panning at high scale, open largest scale
1822 // chart first
1823 if ((LastStack.CurrentStackEntry == LastStack.nEntry - 1) ||
1824 (LastStack.nEntry == 0)) {
1825 search_direction = true;
1826 start_index = m_pCurrentStack->nEntry - 1;
1827 }
1828
1829 // Another special case, open specified index on program start
1830 if (bOpenSpecified) {
1831 search_direction = false;
1832 start_index = 0;
1833 if ((start_index < 0) | (start_index >= m_pCurrentStack->nEntry))
1834 start_index = 0;
1835
1836 new_open_type = CHART_TYPE_DONTCARE;
1837 }
1838
1839 pProposed = ChartData->OpenStackChartConditional(
1840 m_pCurrentStack, start_index, search_direction, new_open_type,
1841 new_open_family);
1842
1843 // Try to open other types/families of chart in some priority
1844 if (NULL == pProposed)
1845 pProposed = ChartData->OpenStackChartConditional(
1846 m_pCurrentStack, start_index, search_direction,
1847 CHART_TYPE_CM93COMP, CHART_FAMILY_VECTOR);
1848
1849 if (NULL == pProposed)
1850 pProposed = ChartData->OpenStackChartConditional(
1851 m_pCurrentStack, start_index, search_direction,
1852 CHART_TYPE_CM93COMP, CHART_FAMILY_RASTER);
1853
1854 bNewChart = true;
1855
1856 } // bCanvasChartAutoOpen
1857
1858 else
1859 pProposed = NULL;
1860
1861 // If no go, then
1862 // Open a Dummy Chart
1863 if (NULL == pProposed) {
1864 if (NULL == pDummyChart) {
1865 pDummyChart = new ChartDummy;
1866 bNewChart = true;
1867 }
1868
1869 if (pLast_Ch)
1870 if (pLast_Ch->GetChartType() != CHART_TYPE_DUMMY) bNewChart = true;
1871
1872 pProposed = pDummyChart;
1873 }
1874
1875 // Arriving here, pProposed points to an opened chart, or NULL.
1876 if (m_singleChart) m_singleChart->Deactivate();
1877 m_singleChart = pProposed;
1878
1879 if (m_singleChart) {
1880 m_singleChart->Activate();
1881 m_pCurrentStack->CurrentStackEntry = ChartData->GetStackEntry(
1882 m_pCurrentStack, m_singleChart->GetFullPath());
1883 }
1884 } // need new chart
1885
1886 // Arriving here, m_singleChart is opened and OK, or NULL
1887 if (NULL != m_singleChart) {
1888 // Setup the view using the current scale
1889 double set_scale = GetVPScale();
1890
1891 // If the current viewpoint is invalid, set the default scale to
1892 // something reasonable.
1893 if (!GetVP().IsValid())
1894 set_scale = 1. / 20000.;
1895 else { // otherwise, match scale if elected.
1896 double proposed_scale_onscreen;
1897
1898 if (m_bFollow) { // autoset the scale only if in autofollow
1899 double new_scale_ppm =
1900 m_singleChart->GetNearestPreferredScalePPM(GetVPScale());
1901 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1902 } else
1903 proposed_scale_onscreen = GetCanvasScaleFactor() / set_scale;
1904
1905 // This logic will bring a new chart onscreen at roughly twice the true
1906 // paper scale equivalent. Note that first chart opened on application
1907 // startup (bOpenSpecified = true) will open at the config saved scale
1908 if (bNewChart && !g_bPreserveScaleOnX && !bOpenSpecified) {
1909 proposed_scale_onscreen = m_singleChart->GetNativeScale() / 2;
1910 double equivalent_vp_scale =
1911 GetCanvasScaleFactor() / proposed_scale_onscreen;
1912 double new_scale_ppm =
1913 m_singleChart->GetNearestPreferredScalePPM(equivalent_vp_scale);
1914 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1915 }
1916
1917 if (m_bFollow) { // bounds-check the scale only if in autofollow
1918 proposed_scale_onscreen =
1919 wxMin(proposed_scale_onscreen,
1920 m_singleChart->GetNormalScaleMax(GetCanvasScaleFactor(),
1921 GetCanvasWidth()));
1922 proposed_scale_onscreen =
1923 wxMax(proposed_scale_onscreen,
1924 m_singleChart->GetNormalScaleMin(GetCanvasScaleFactor(),
1926 }
1927
1928 set_scale = GetCanvasScaleFactor() / proposed_scale_onscreen;
1929 }
1930
1931 bNewView |= SetViewPoint(vpLat, vpLon, set_scale,
1932 m_singleChart->GetChartSkew() * PI / 180.,
1933 GetVPRotation());
1934 }
1935 } // new stack
1936
1937 else // No change in Chart Stack
1938 {
1939 if ((m_bFollow) && m_singleChart)
1940 bNewView |= SetViewPoint(vpLat, vpLon, GetVPScale(),
1941 m_singleChart->GetChartSkew() * PI / 180.,
1942 GetVPRotation());
1943 }
1944
1945update_finish:
1946
1947 // TODO
1948 // if( bNewPiano ) UpdateControlBar();
1949
1950 m_bFirstAuto = false; // Auto open on program start
1951
1952 // If we need a Refresh(), do it here...
1953 // But don't duplicate a Refresh() done by SetViewPoint()
1954 if (bNewChart && !bNewView) Refresh(false);
1955
1956#ifdef ocpnUSE_GL
1957 // If a new chart, need to invalidate gl viewport for refresh
1958 // so the fbo gets flushed
1959 if (m_glcc && g_bopengl && bNewChart) GetglCanvas()->Invalidate();
1960#endif
1961
1962 return bNewChart | bNewView;
1963}
1964
1965void ChartCanvas::SelectQuiltRefdbChart(int db_index, bool b_autoscale) {
1966 if (m_pCurrentStack) m_pCurrentStack->SetCurrentEntryFromdbIndex(db_index);
1967
1968 SetQuiltRefChart(db_index);
1969 if (ChartData) {
1970 ChartBase *pc = ChartData->OpenChartFromDB(db_index, FULL_INIT);
1971 if (pc) {
1972 if (b_autoscale) {
1973 double best_scale_ppm = GetBestVPScale(pc);
1974 SetVPScale(best_scale_ppm);
1975 }
1976 } else
1977 SetQuiltRefChart(-1);
1978 } else
1979 SetQuiltRefChart(-1);
1980}
1981
1982void ChartCanvas::SelectQuiltRefChart(int selected_index) {
1983 std::vector<int> piano_chart_index_array =
1984 GetQuiltExtendedStackdbIndexArray();
1985 int current_db_index = piano_chart_index_array[selected_index];
1986
1987 SelectQuiltRefdbChart(current_db_index);
1988}
1989
1990double ChartCanvas::GetBestVPScale(ChartBase *pchart) {
1991 if (pchart) {
1992 double proposed_scale_onscreen = GetCanvasScaleFactor() / GetVPScale();
1993
1994 if ((g_bPreserveScaleOnX) ||
1995 (CHART_TYPE_CM93COMP == pchart->GetChartType())) {
1996 double new_scale_ppm = GetVPScale();
1997 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
1998 } else {
1999 // This logic will bring the new chart onscreen at roughly twice the true
2000 // paper scale equivalent.
2001 proposed_scale_onscreen = pchart->GetNativeScale() / 2;
2002 double equivalent_vp_scale =
2003 GetCanvasScaleFactor() / proposed_scale_onscreen;
2004 double new_scale_ppm =
2005 pchart->GetNearestPreferredScalePPM(equivalent_vp_scale);
2006 proposed_scale_onscreen = GetCanvasScaleFactor() / new_scale_ppm;
2007 }
2008
2009 // Do not allow excessive underzoom, even if the g_bPreserveScaleOnX flag is
2010 // set. Otherwise, we get severe performance problems on all platforms
2011
2012 double max_underzoom_multiplier = 2.0;
2013 if (GetVP().b_quilt) {
2014 double scale_max = m_pQuilt->GetNomScaleMin(pchart->GetNativeScale(),
2015 pchart->GetChartType(),
2016 pchart->GetChartFamily());
2017 max_underzoom_multiplier = scale_max / pchart->GetNativeScale();
2018 }
2019
2020 proposed_scale_onscreen = wxMin(
2021 proposed_scale_onscreen,
2022 pchart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth()) *
2023 max_underzoom_multiplier);
2024
2025 // And, do not allow excessive overzoom either
2026 proposed_scale_onscreen =
2027 wxMax(proposed_scale_onscreen,
2028 pchart->GetNormalScaleMin(GetCanvasScaleFactor(), false));
2029
2030 return GetCanvasScaleFactor() / proposed_scale_onscreen;
2031 } else
2032 return 1.0;
2033}
2034
2035void ChartCanvas::SetupCanvasQuiltMode() {
2036 if (GetQuiltMode()) // going to quilt mode
2037 {
2038 ChartData->LockCache();
2039
2040 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
2041
2042 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2043
2044 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2045 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2046 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2047 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2048
2049 m_Piano->SetRoundedRectangles(true);
2050
2051 // Select the proper Ref chart
2052 int target_new_dbindex = -1;
2053 if (m_pCurrentStack) {
2054 target_new_dbindex =
2055 GetQuiltReferenceChartIndex(); // m_pCurrentStack->GetCurrentEntrydbIndex();
2056
2057 if (-1 != target_new_dbindex) {
2058 if (!IsChartQuiltableRef(target_new_dbindex)) {
2059 int proj = ChartData->GetDBChartProj(target_new_dbindex);
2060 int type = ChartData->GetDBChartType(target_new_dbindex);
2061
2062 // walk the stack up looking for a satisfactory chart
2063 int stack_index = m_pCurrentStack->CurrentStackEntry;
2064
2065 while ((stack_index < m_pCurrentStack->nEntry - 1) &&
2066 (stack_index >= 0)) {
2067 int proj_tent = ChartData->GetDBChartProj(
2068 m_pCurrentStack->GetDBIndex(stack_index));
2069 int type_tent = ChartData->GetDBChartType(
2070 m_pCurrentStack->GetDBIndex(stack_index));
2071
2072 if (IsChartQuiltableRef(m_pCurrentStack->GetDBIndex(stack_index))) {
2073 if ((proj == proj_tent) && (type_tent == type)) {
2074 target_new_dbindex = m_pCurrentStack->GetDBIndex(stack_index);
2075 break;
2076 }
2077 }
2078 stack_index++;
2079 }
2080 }
2081 }
2082 }
2083
2084 if (IsChartQuiltableRef(target_new_dbindex))
2085 SelectQuiltRefdbChart(target_new_dbindex,
2086 false); // Try not to allow a scale change
2087 else
2088 SelectQuiltRefdbChart(-1, false);
2089
2090 m_singleChart = NULL; // Bye....
2091
2092 // Re-qualify the quilt reference chart selection
2093 AdjustQuiltRefChart();
2094
2095 // Restore projection type saved on last quilt mode toggle
2096 // TODO
2097 // if(g_sticky_projection != -1)
2098 // GetVP().SetProjectionType(g_sticky_projection);
2099 // else
2100 // GetVP().SetProjectionType(PROJECTION_MERCATOR);
2101 GetVP().SetProjectionType(PROJECTION_UNKNOWN);
2102
2103 } else // going to SC Mode
2104 {
2105 std::vector<int> empty_array;
2106 m_Piano->SetActiveKeyArray(empty_array);
2107 m_Piano->SetNoshowIndexArray(empty_array);
2108 m_Piano->SetEclipsedIndexArray(empty_array);
2109
2110 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
2111 m_Piano->SetVizIcon(new wxBitmap(style->GetIcon("viz")));
2112 m_Piano->SetInVizIcon(new wxBitmap(style->GetIcon("redX")));
2113 m_Piano->SetTMercIcon(new wxBitmap(style->GetIcon("tmercprj")));
2114 m_Piano->SetSkewIcon(new wxBitmap(style->GetIcon("skewprj")));
2115
2116 m_Piano->SetRoundedRectangles(false);
2117 // TODO Make this a member g_sticky_projection = GetVP().m_projection_type;
2118 }
2119
2120 // When shifting from quilt to single chart mode, select the "best" single
2121 // chart to show
2122 if (!GetQuiltMode()) {
2123 if (ChartData && ChartData->IsValid()) {
2124 UnlockQuilt();
2125
2126 double tLat, tLon;
2127 if (m_bFollow == true) {
2128 tLat = gLat;
2129 tLon = gLon;
2130 } else {
2131 tLat = m_vLat;
2132 tLon = m_vLon;
2133 }
2134
2135 if (!m_singleChart) {
2136 // Build a temporary chart stack based on tLat, tLon
2137 ChartStack TempStack;
2138 ChartData->BuildChartStack(&TempStack, tLat, tLon, g_sticky_chart,
2139 m_groupIndex);
2140
2141 // Iterate over the quilt charts actually shown, looking for the
2142 // largest scale chart that will be in the new chartstack.... This
2143 // will (almost?) always be the reference chart....
2144
2145 ChartBase *Candidate_Chart = NULL;
2146 int cur_max_scale = (int)1e8;
2147
2148 ChartBase *pChart = GetFirstQuiltChart();
2149 while (pChart) {
2150 // Is this pChart in new stack?
2151 int tEntry =
2152 ChartData->GetStackEntry(&TempStack, pChart->GetFullPath());
2153 if (tEntry != -1) {
2154 if (pChart->GetNativeScale() < cur_max_scale) {
2155 Candidate_Chart = pChart;
2156 cur_max_scale = pChart->GetNativeScale();
2157 }
2158 }
2159 pChart = GetNextQuiltChart();
2160 }
2161
2162 m_singleChart = Candidate_Chart;
2163
2164 // If the quilt is empty, there is no "best" chart.
2165 // So, open the smallest scale chart in the current stack
2166 if (NULL == m_singleChart) {
2167 m_singleChart = ChartData->OpenStackChartConditional(
2168 &TempStack, TempStack.nEntry - 1, true, CHART_TYPE_DONTCARE,
2169 CHART_FAMILY_DONTCARE);
2170 }
2171 }
2172
2173 // Invalidate all the charts in the quilt,
2174 // as any cached data may be region based and not have fullscreen coverage
2175 InvalidateAllQuiltPatchs();
2176
2177 if (m_singleChart) {
2178 int dbi = ChartData->FinddbIndex(m_singleChart->GetFullPath());
2179 std::vector<int> one_array;
2180 one_array.push_back(dbi);
2181 m_Piano->SetActiveKeyArray(one_array);
2182 }
2183
2184 if (m_singleChart) {
2185 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
2186 }
2187 }
2188 // Invalidate the current stack so that it will be rebuilt on next tick
2189 if (m_pCurrentStack) m_pCurrentStack->b_valid = false;
2190 }
2191}
2192
2193bool ChartCanvas::IsTempMenuBarEnabled() {
2194#ifdef __WXMSW__
2195 int major;
2196 wxGetOsVersion(&major);
2197 return (major >
2198 5); // For Windows, function is only available on Vista and above
2199#else
2200 return true;
2201#endif
2202}
2203
2204double ChartCanvas::GetCanvasRangeMeters() {
2205 int width, height;
2206 GetSize(&width, &height);
2207 int minDimension = wxMin(width, height);
2208
2209 double range = (minDimension / GetVP().view_scale_ppm) / 2;
2210 range *= cos(GetVP().clat * PI / 180.);
2211 return range;
2212}
2213
2214void ChartCanvas::SetCanvasRangeMeters(double range) {
2215 int width, height;
2216 GetSize(&width, &height);
2217 int minDimension = wxMin(width, height);
2218
2219 double scale_ppm = minDimension / (range / cos(GetVP().clat * PI / 180.));
2220 SetVPScale(scale_ppm / 2);
2221}
2222
2223bool ChartCanvas::SetUserOwnship() {
2224 // Look for user defined ownship image
2225 // This may be found in the shared data location along with other user
2226 // defined icons. and will be called "ownship.xpm" or "ownship.png"
2227 if (pWayPointMan && pWayPointMan->DoesIconExist("ownship")) {
2228 double factor_dusk = 0.5;
2229 double factor_night = 0.25;
2230
2231 wxBitmap *pbmp = pWayPointMan->GetIconBitmap("ownship");
2232 m_pos_image_user_day = new wxImage;
2233 *m_pos_image_user_day = pbmp->ConvertToImage();
2234 if (!m_pos_image_user_day->HasAlpha()) m_pos_image_user_day->InitAlpha();
2235
2236 int gimg_width = m_pos_image_user_day->GetWidth();
2237 int gimg_height = m_pos_image_user_day->GetHeight();
2238
2239 // Make dusk and night images
2240 m_pos_image_user_dusk = new wxImage;
2241 m_pos_image_user_night = new wxImage;
2242
2243 *m_pos_image_user_dusk = m_pos_image_user_day->Copy();
2244 *m_pos_image_user_night = m_pos_image_user_day->Copy();
2245
2246 for (int iy = 0; iy < gimg_height; iy++) {
2247 for (int ix = 0; ix < gimg_width; ix++) {
2248 if (!m_pos_image_user_day->IsTransparent(ix, iy)) {
2249 wxImage::RGBValue rgb(m_pos_image_user_day->GetRed(ix, iy),
2250 m_pos_image_user_day->GetGreen(ix, iy),
2251 m_pos_image_user_day->GetBlue(ix, iy));
2252 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2253 hsv.value = hsv.value * factor_dusk;
2254 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2255 m_pos_image_user_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2256 nrgb.blue);
2257
2258 hsv = wxImage::RGBtoHSV(rgb);
2259 hsv.value = hsv.value * factor_night;
2260 nrgb = wxImage::HSVtoRGB(hsv);
2261 m_pos_image_user_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2262 nrgb.blue);
2263 }
2264 }
2265 }
2266
2267 // Make some alternate greyed out day/dusk/night images
2268 m_pos_image_user_grey_day = new wxImage;
2269 *m_pos_image_user_grey_day = m_pos_image_user_day->ConvertToGreyscale();
2270
2271 m_pos_image_user_grey_dusk = new wxImage;
2272 m_pos_image_user_grey_night = new wxImage;
2273
2274 *m_pos_image_user_grey_dusk = m_pos_image_user_grey_day->Copy();
2275 *m_pos_image_user_grey_night = m_pos_image_user_grey_day->Copy();
2276
2277 for (int iy = 0; iy < gimg_height; iy++) {
2278 for (int ix = 0; ix < gimg_width; ix++) {
2279 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2280 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2281 m_pos_image_user_grey_day->GetGreen(ix, iy),
2282 m_pos_image_user_grey_day->GetBlue(ix, iy));
2283 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2284 hsv.value = hsv.value * factor_dusk;
2285 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2286 m_pos_image_user_grey_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green,
2287 nrgb.blue);
2288
2289 hsv = wxImage::RGBtoHSV(rgb);
2290 hsv.value = hsv.value * factor_night;
2291 nrgb = wxImage::HSVtoRGB(hsv);
2292 m_pos_image_user_grey_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2293 nrgb.blue);
2294 }
2295 }
2296 }
2297
2298 // Make a yellow image for rendering under low accuracy chart conditions
2299 m_pos_image_user_yellow_day = new wxImage;
2300 m_pos_image_user_yellow_dusk = new wxImage;
2301 m_pos_image_user_yellow_night = new wxImage;
2302
2303 *m_pos_image_user_yellow_day = m_pos_image_user_grey_day->Copy();
2304 *m_pos_image_user_yellow_dusk = m_pos_image_user_grey_day->Copy();
2305 *m_pos_image_user_yellow_night = m_pos_image_user_grey_day->Copy();
2306
2307 for (int iy = 0; iy < gimg_height; iy++) {
2308 for (int ix = 0; ix < gimg_width; ix++) {
2309 if (!m_pos_image_user_grey_day->IsTransparent(ix, iy)) {
2310 wxImage::RGBValue rgb(m_pos_image_user_grey_day->GetRed(ix, iy),
2311 m_pos_image_user_grey_day->GetGreen(ix, iy),
2312 m_pos_image_user_grey_day->GetBlue(ix, iy));
2313
2314 // Simply remove all "blue" from the greyscaled image...
2315 // so, what is not black becomes yellow.
2316 wxImage::HSVValue hsv = wxImage::RGBtoHSV(rgb);
2317 wxImage::RGBValue nrgb = wxImage::HSVtoRGB(hsv);
2318 m_pos_image_user_yellow_day->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2319
2320 hsv = wxImage::RGBtoHSV(rgb);
2321 hsv.value = hsv.value * factor_dusk;
2322 nrgb = wxImage::HSVtoRGB(hsv);
2323 m_pos_image_user_yellow_dusk->SetRGB(ix, iy, nrgb.red, nrgb.green, 0);
2324
2325 hsv = wxImage::RGBtoHSV(rgb);
2326 hsv.value = hsv.value * factor_night;
2327 nrgb = wxImage::HSVtoRGB(hsv);
2328 m_pos_image_user_yellow_night->SetRGB(ix, iy, nrgb.red, nrgb.green,
2329 0);
2330 }
2331 }
2332 }
2333
2334 return true;
2335 } else
2336 return false;
2337}
2338
2340 m_display_size_mm = size;
2341
2342 // int sx, sy;
2343 // wxDisplaySize( &sx, &sy );
2344
2345 // Calculate logical pixels per mm for later reference.
2346 wxSize sd = g_Platform->getDisplaySize();
2347 double horizontal = sd.x;
2348 // Set DPI (Win) scale factor
2349 g_scaler = g_Platform->GetDisplayDIPMult(this);
2350
2351 m_pix_per_mm = (horizontal) / ((double)m_display_size_mm);
2352 m_canvas_scale_factor = (horizontal) / (m_display_size_mm / 1000.);
2353
2354 if (ps52plib) {
2355 ps52plib->SetDisplayWidth(g_monitor_info[g_current_monitor].width);
2356 ps52plib->SetPPMM(m_pix_per_mm);
2357 }
2358
2359 wxString msg;
2360 msg.Printf(
2361 "Metrics: m_display_size_mm: %g g_Platform->getDisplaySize(): "
2362 "%d:%d ",
2363 m_display_size_mm, sd.x, sd.y);
2364 wxLogMessage(msg);
2365
2366 int ssx, ssy;
2367 ssx = g_monitor_info[g_current_monitor].width;
2368 ssy = g_monitor_info[g_current_monitor].height;
2369 msg.Printf("monitor size: %d %d", ssx, ssy);
2370 wxLogMessage(msg);
2371
2372 m_focus_indicator_pix = /*std::round*/ wxRound(1 * GetPixPerMM());
2373}
2374#if 0
2375void ChartCanvas::OnEvtCompressProgress( OCPN_CompressProgressEvent & event )
2376{
2377 wxString msg(event.m_string.c_str(), wxConvUTF8);
2378 // if cpus are removed between runs
2379 if(pprog_threads > 0 && compress_msg_array.GetCount() > (unsigned int)pprog_threads) {
2380 compress_msg_array.RemoveAt(pprog_threads, compress_msg_array.GetCount() - pprog_threads);
2381 }
2382
2383 if(compress_msg_array.GetCount() > (unsigned int)event.thread )
2384 {
2385 compress_msg_array.RemoveAt(event.thread);
2386 compress_msg_array.Insert( msg, event.thread);
2387 }
2388 else
2389 compress_msg_array.Add(msg);
2390
2391
2392 wxString combined_msg;
2393 for(unsigned int i=0 ; i < compress_msg_array.GetCount() ; i++) {
2394 combined_msg += compress_msg_array[i];
2395 combined_msg += "\n";
2396 }
2397
2398 bool skip = false;
2399 pprog->Update(pprog_count, combined_msg, &skip );
2400 pprog->SetSize(pprog_size);
2401 if(skip)
2402 b_skipout = skip;
2403}
2404#endif
2405void ChartCanvas::InvalidateGL() {
2406 if (!m_glcc) return;
2407#ifdef ocpnUSE_GL
2408 if (g_bopengl) m_glcc->Invalidate();
2409#endif
2410 if (m_Compass) m_Compass->UpdateStatus(true);
2411}
2412
2413int ChartCanvas::GetCanvasChartNativeScale() {
2414 int ret = 1;
2415 if (!VPoint.b_quilt) {
2416 if (m_singleChart) ret = m_singleChart->GetNativeScale();
2417 } else
2418 ret = (int)m_pQuilt->GetRefNativeScale();
2419
2420 return ret;
2421}
2422
2423ChartBase *ChartCanvas::GetChartAtCursor() {
2424 ChartBase *target_chart;
2425 if (m_singleChart && (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR))
2426 target_chart = m_singleChart;
2427 else if (VPoint.b_quilt)
2428 target_chart = m_pQuilt->GetChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2429 else
2430 target_chart = NULL;
2431 return target_chart;
2432}
2433
2434ChartBase *ChartCanvas::GetOverlayChartAtCursor() {
2435 ChartBase *target_chart;
2436 if (VPoint.b_quilt)
2437 target_chart =
2438 m_pQuilt->GetOverlayChartAtPix(VPoint, wxPoint(mouse_x, mouse_y));
2439 else
2440 target_chart = NULL;
2441 return target_chart;
2442}
2443
2444int ChartCanvas::FindClosestCanvasChartdbIndex(int scale) {
2445 int new_dbIndex = -1;
2446 if (!VPoint.b_quilt) {
2447 if (m_pCurrentStack) {
2448 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
2449 int sc = ChartData->GetStackChartScale(m_pCurrentStack, i, NULL, 0);
2450 if (sc >= scale) {
2451 new_dbIndex = m_pCurrentStack->GetDBIndex(i);
2452 break;
2453 }
2454 }
2455 }
2456 } else {
2457 // Using the current quilt, select a useable reference chart
2458 // Said chart will be in the extended (possibly full-screen) stack,
2459 // And will have a scale equal to or just greater than the stipulated
2460 // value
2461 unsigned int im = m_pQuilt->GetExtendedStackIndexArray().size();
2462 if (im > 0) {
2463 for (unsigned int is = 0; is < im; is++) {
2464 const ChartTableEntry &m = ChartData->GetChartTableEntry(
2465 m_pQuilt->GetExtendedStackIndexArray()[is]);
2466 if ((m.Scale_ge(
2467 scale)) /* && (m_reference_family == m.GetChartFamily())*/) {
2468 new_dbIndex = m_pQuilt->GetExtendedStackIndexArray()[is];
2469 break;
2470 }
2471 }
2472 }
2473 }
2474
2475 return new_dbIndex;
2476}
2477
2478void ChartCanvas::EnablePaint(bool b_enable) {
2479 m_b_paint_enable = b_enable;
2480#ifdef ocpnUSE_GL
2481 if (m_glcc) m_glcc->EnablePaint(b_enable);
2482#endif
2483}
2484
2485bool ChartCanvas::IsQuiltDelta() { return m_pQuilt->IsQuiltDelta(VPoint); }
2486
2487void ChartCanvas::UnlockQuilt() { m_pQuilt->UnlockQuilt(); }
2488
2489std::vector<int> ChartCanvas::GetQuiltIndexArray() {
2490 return m_pQuilt->GetQuiltIndexArray();
2491 ;
2492}
2493
2494void ChartCanvas::SetQuiltMode(bool b_quilt) {
2495 VPoint.b_quilt = b_quilt;
2496 VPoint.b_FullScreenQuilt = g_bFullScreenQuilt;
2497}
2498
2499bool ChartCanvas::GetQuiltMode() { return VPoint.b_quilt; }
2500
2501int ChartCanvas::GetQuiltReferenceChartIndex() {
2502 return m_pQuilt->GetRefChartdbIndex();
2503}
2504
2505void ChartCanvas::InvalidateAllQuiltPatchs() {
2506 m_pQuilt->InvalidateAllQuiltPatchs();
2507}
2508
2509ChartBase *ChartCanvas::GetLargestScaleQuiltChart() {
2510 return m_pQuilt->GetLargestScaleChart();
2511}
2512
2513ChartBase *ChartCanvas::GetFirstQuiltChart() {
2514 return m_pQuilt->GetFirstChart();
2515}
2516
2517ChartBase *ChartCanvas::GetNextQuiltChart() { return m_pQuilt->GetNextChart(); }
2518
2519int ChartCanvas::GetQuiltChartCount() { return m_pQuilt->GetnCharts(); }
2520
2521void ChartCanvas::SetQuiltChartHiLiteIndex(int dbIndex) {
2522 m_pQuilt->SetHiliteIndex(dbIndex);
2523}
2524
2525void ChartCanvas::SetQuiltChartHiLiteIndexArray(std::vector<int> hilite_array) {
2526 m_pQuilt->SetHiliteIndexArray(hilite_array);
2527}
2528
2529void ChartCanvas::ClearQuiltChartHiLiteIndexArray() {
2530 m_pQuilt->ClearHiliteIndexArray();
2531}
2532
2533std::vector<int> ChartCanvas::GetQuiltCandidatedbIndexArray(bool flag1,
2534 bool flag2) {
2535 return m_pQuilt->GetCandidatedbIndexArray(flag1, flag2);
2536}
2537
2538int ChartCanvas::GetQuiltRefChartdbIndex() {
2539 return m_pQuilt->GetRefChartdbIndex();
2540}
2541
2542std::vector<int> &ChartCanvas::GetQuiltExtendedStackdbIndexArray() {
2543 return m_pQuilt->GetExtendedStackIndexArray();
2544}
2545
2546std::vector<int> &ChartCanvas::GetQuiltFullScreendbIndexArray() {
2547 return m_pQuilt->GetFullscreenIndexArray();
2548}
2549
2550std::vector<int> ChartCanvas::GetQuiltEclipsedStackdbIndexArray() {
2551 return m_pQuilt->GetEclipsedStackIndexArray();
2552}
2553
2554void ChartCanvas::InvalidateQuilt() { return m_pQuilt->Invalidate(); }
2555
2556double ChartCanvas::GetQuiltMaxErrorFactor() {
2557 return m_pQuilt->GetMaxErrorFactor();
2558}
2559
2560bool ChartCanvas::IsChartQuiltableRef(int db_index) {
2561 return m_pQuilt->IsChartQuiltableRef(db_index);
2562}
2563
2564bool ChartCanvas::IsChartLargeEnoughToRender(ChartBase *chart, ViewPort &vp) {
2565 double chartMaxScale =
2566 chart->GetNormalScaleMax(GetCanvasScaleFactor(), GetCanvasWidth());
2567 return (chartMaxScale * g_ChartNotRenderScaleFactor > vp.chart_scale);
2568}
2569
2570void ChartCanvas::StartMeasureRoute() {
2571 if (!m_routeState) { // no measure tool if currently creating route
2572 if (m_bMeasure_Active) {
2573 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2574 m_pMeasureRoute = NULL;
2575 }
2576
2577 m_bMeasure_Active = true;
2578 m_nMeasureState = 1;
2579 m_bDrawingRoute = false;
2580
2581 SetCursor(*pCursorPencil);
2582 Refresh();
2583 }
2584}
2585
2586void ChartCanvas::CancelMeasureRoute() {
2587 m_bMeasure_Active = false;
2588 m_nMeasureState = 0;
2589 m_bDrawingRoute = false;
2590
2591 g_pRouteMan->DeleteRoute(m_pMeasureRoute);
2592 m_pMeasureRoute = NULL;
2593
2594 SetCursor(*pCursorArrow);
2595}
2596
2597ViewPort &ChartCanvas::GetVP() { return VPoint; }
2598
2599void ChartCanvas::SetVP(ViewPort &vp) {
2600 VPoint = vp;
2601 VPoint.SetPixelScale(m_displayScale);
2602}
2603
2604// void ChartCanvas::SetFocus()
2605// {
2606// printf("set %d\n", m_canvasIndex);
2607// //wxWindow:SetFocus();
2608// }
2609
2610void ChartCanvas::TriggerDeferredFocus() {
2611 // #if defined(__WXGTK__) || defined(__WXOSX__)
2612
2613 m_deferredFocusTimer.Start(20, true);
2614
2615#if defined(__WXGTK__) || defined(__WXOSX__)
2616 gFrame->Raise();
2617#endif
2618
2619 // gFrame->Raise();
2620 // #else
2621 // SetFocus();
2622 // Refresh(true);
2623 // #endif
2624}
2625
2626void ChartCanvas::OnDeferredFocusTimerEvent(wxTimerEvent &event) {
2627 SetFocus();
2628 Refresh(true);
2629}
2630
2631void ChartCanvas::OnKeyChar(wxKeyEvent &event) {
2632 if (SendKeyEventToPlugins(event))
2633 return; // PlugIn did something, and does not want the canvas to do
2634 // anything else
2635
2636 int key_char = event.GetKeyCode();
2637 switch (key_char) {
2638 case '?':
2639 HotkeysDlg(wxWindow::FindWindowByName("MainWindow")).ShowModal();
2640 break;
2641 case '+':
2642 ZoomCanvas(g_plus_minus_zoom_factor, false);
2643 break;
2644 case '-':
2645 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2646 break;
2647 default:
2648 break;
2649 }
2650 if (g_benable_rotate) {
2651 switch (key_char) {
2652 case ']':
2653 RotateCanvas(1);
2654 Refresh();
2655 break;
2656
2657 case '[':
2658 RotateCanvas(-1);
2659 Refresh();
2660 break;
2661
2662 case '\\':
2663 DoRotateCanvas(0);
2664 break;
2665 }
2666 }
2667
2668 event.Skip();
2669}
2670
2671void ChartCanvas::OnKeyDown(wxKeyEvent &event) {
2672 if (SendKeyEventToPlugins(event))
2673 return; // PlugIn did something, and does not want the canvas to do
2674 // anything else
2675
2676 bool b_handled = false;
2677
2678 m_modkeys = event.GetModifiers();
2679
2680 int panspeed = m_modkeys == wxMOD_ALT ? 1 : 100;
2681
2682#ifdef OCPN_ALT_MENUBAR
2683#ifndef __WXOSX__
2684 // If the permanent menubar is disabled, we show it temporarily when Alt is
2685 // pressed or when Alt + a letter is presssed (for the top-menu-level
2686 // hotkeys). The toggling normally takes place in OnKeyUp, but here we handle
2687 // some special cases.
2688 if (IsTempMenuBarEnabled() && event.AltDown() && !g_bShowMenuBar) {
2689 // If Alt + a letter is pressed, and the menubar is hidden, show it now
2690 if (event.GetKeyCode() >= 'A' && event.GetKeyCode() <= 'Z') {
2691 if (!g_bTempShowMenuBar) {
2692 g_bTempShowMenuBar = true;
2693 parent_frame->ApplyGlobalSettings(false);
2694 }
2695 m_bMayToggleMenuBar = false; // don't hide it again when we release Alt
2696 event.Skip();
2697 return;
2698 }
2699 // If another key is pressed while Alt is down, do NOT toggle the menus when
2700 // Alt is released
2701 if (event.GetKeyCode() != WXK_ALT) {
2702 m_bMayToggleMenuBar = false;
2703 }
2704 }
2705#endif
2706#endif
2707
2708 // HOTKEYS
2709 switch (event.GetKeyCode()) {
2710 case WXK_TAB:
2711 // parent_frame->SwitchKBFocus( this );
2712 break;
2713
2714 case WXK_MENU:
2715 int x, y;
2716 event.GetPosition(&x, &y);
2717 m_FinishRouteOnKillFocus = false;
2718 CallPopupMenu(x, y);
2719 m_FinishRouteOnKillFocus = true;
2720 break;
2721
2722 case WXK_ALT:
2723 m_modkeys |= wxMOD_ALT;
2724 break;
2725
2726 case WXK_CONTROL:
2727 m_modkeys |= wxMOD_CONTROL;
2728 break;
2729
2730#ifdef __WXOSX__
2731 // On macOS Cmd generates WXK_CONTROL and Ctrl generates WXK_RAW_CONTROL
2732 case WXK_RAW_CONTROL:
2733 m_modkeys |= wxMOD_RAW_CONTROL;
2734 break;
2735#endif
2736
2737 case WXK_LEFT:
2738 if (m_modkeys == wxMOD_CONTROL)
2739 parent_frame->DoStackDown(this);
2740 else if (g_bsmoothpanzoom) {
2741 StartTimedMovement();
2742 m_panx = -1;
2743 } else {
2744 PanCanvas(-panspeed, 0);
2745 }
2746 b_handled = true;
2747 break;
2748
2749 case WXK_UP:
2750 if (g_bsmoothpanzoom) {
2751 StartTimedMovement();
2752 m_pany = -1;
2753 } else
2754 PanCanvas(0, -panspeed);
2755 b_handled = true;
2756 break;
2757
2758 case WXK_RIGHT:
2759 if (m_modkeys == wxMOD_CONTROL)
2760 parent_frame->DoStackUp(this);
2761 else if (g_bsmoothpanzoom) {
2762 StartTimedMovement();
2763 m_panx = 1;
2764 } else
2765 PanCanvas(panspeed, 0);
2766 b_handled = true;
2767
2768 break;
2769
2770 case WXK_DOWN:
2771 if (g_bsmoothpanzoom) {
2772 StartTimedMovement();
2773 m_pany = 1;
2774 } else
2775 PanCanvas(0, panspeed);
2776 b_handled = true;
2777 break;
2778
2779 case WXK_F2: {
2780 // TogglebFollow();
2781 if (event.ShiftDown()) {
2782 double scale = GetVP().view_scale_ppm;
2783 auto current_family = m_pQuilt->GetRefFamily();
2784 auto target_family = CHART_FAMILY_UNKNOWN;
2785 if (current_family == CHART_FAMILY_RASTER)
2786 target_family = CHART_FAMILY_VECTOR;
2787 else
2788 target_family = CHART_FAMILY_RASTER;
2789
2790 std::shared_ptr<HostApi> host_api;
2791 host_api = GetHostApi();
2792 auto api_121 = std::dynamic_pointer_cast<HostApi121>(host_api);
2793
2794 if (api_121)
2795 api_121->SelectChartFamily(m_canvasIndex,
2796 (ChartFamilyEnumPI)target_family);
2797
2798 } else
2799 TogglebFollow();
2800 break;
2801 }
2802 case WXK_F3: {
2803 SetShowENCText(!GetShowENCText());
2804 Refresh(true);
2805 InvalidateGL();
2806 break;
2807 }
2808 case WXK_F4:
2809 if (!m_bMeasure_Active) {
2810 if (event.ShiftDown())
2811 m_bMeasure_DistCircle = true;
2812 else
2813 m_bMeasure_DistCircle = false;
2814
2815 StartMeasureRoute();
2816 } else {
2817 CancelMeasureRoute();
2818
2819 SetCursor(*pCursorArrow);
2820
2821 // SurfaceToolbar();
2822 InvalidateGL();
2823 Refresh(false);
2824 }
2825
2826 break;
2827
2828 case WXK_F5:
2829 parent_frame->ToggleColorScheme();
2830 gFrame->Raise();
2831 TriggerDeferredFocus();
2832 break;
2833
2834 case WXK_F6: {
2835 int mod = m_modkeys & wxMOD_SHIFT;
2836 if (mod != m_brightmod) {
2837 m_brightmod = mod;
2838 m_bbrightdir = !m_bbrightdir;
2839 }
2840
2841 if (!m_bbrightdir) {
2842 g_nbrightness -= 10;
2843 if (g_nbrightness <= MIN_BRIGHT) {
2844 g_nbrightness = MIN_BRIGHT;
2845 m_bbrightdir = true;
2846 }
2847 } else {
2848 g_nbrightness += 10;
2849 if (g_nbrightness >= MAX_BRIGHT) {
2850 g_nbrightness = MAX_BRIGHT;
2851 m_bbrightdir = false;
2852 }
2853 }
2854
2855 SetScreenBrightness(g_nbrightness);
2856 ShowBrightnessLevelTimedPopup(g_nbrightness / 10, 1, 10);
2857
2858 SetFocus(); // just in case the external program steals it....
2859 gFrame->Raise(); // And reactivate the application main
2860
2861 break;
2862 }
2863
2864 case WXK_F7:
2865 parent_frame->DoStackDown(this);
2866 break;
2867
2868 case WXK_F8:
2869 parent_frame->DoStackUp(this);
2870 break;
2871
2872#ifndef __WXOSX__
2873 case WXK_F9: {
2874 ToggleCanvasQuiltMode();
2875 break;
2876 }
2877#endif
2878
2879 case WXK_F11:
2880 parent_frame->ToggleFullScreen();
2881 b_handled = true;
2882 break;
2883
2884 case WXK_F12: {
2885 if (m_modkeys == wxMOD_ALT) {
2886 // m_nMeasureState = *(volatile int *)(0); // generate a fault for
2887 } else {
2888 ToggleChartOutlines();
2889 }
2890 break;
2891 }
2892
2893 case WXK_PAUSE: // Drop MOB
2894 parent_frame->ActivateMOB();
2895 break;
2896
2897 // NUMERIC PAD
2898 case WXK_NUMPAD_ADD: // '+' on NUM PAD
2899 case WXK_PAGEUP: {
2900 ZoomCanvas(g_plus_minus_zoom_factor, false);
2901 break;
2902 }
2903 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
2904 case WXK_PAGEDOWN: {
2905 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2906 break;
2907 }
2908 case WXK_DELETE:
2909 case WXK_BACK:
2910 if (m_bMeasure_Active) {
2911 if (m_nMeasureState > 2) {
2912 m_pMeasureRoute->DeletePoint(m_pMeasureRoute->GetLastPoint());
2913 m_pMeasureRoute->m_lastMousePointIndex =
2914 m_pMeasureRoute->GetnPoints();
2915 m_nMeasureState--;
2916 gFrame->RefreshAllCanvas();
2917 } else {
2918 CancelMeasureRoute();
2919 StartMeasureRoute();
2920 }
2921 }
2922 break;
2923 default:
2924 break;
2925 }
2926
2927 if (event.GetKeyCode() < 128) // ascii
2928 {
2929 int key_char = event.GetKeyCode();
2930
2931 // Handle both QWERTY and AZERTY keyboard separately for a few control
2932 // codes
2933 if (!g_b_assume_azerty) {
2934#ifdef __WXMAC__
2935 if (g_benable_rotate) {
2936 switch (key_char) {
2937 // On other platforms these are handled in OnKeyChar, which
2938 // (apparently) works better in some locales. On OS X it is better
2939 // to handle them here, since pressing Alt (which should change the
2940 // rotation speed) changes the key char and so prevents the keys
2941 // from working.
2942 case ']':
2943 RotateCanvas(1);
2944 b_handled = true;
2945 break;
2946
2947 case '[':
2948 RotateCanvas(-1);
2949 b_handled = true;
2950 break;
2951
2952 case '\\':
2953 DoRotateCanvas(0);
2954 b_handled = true;
2955 break;
2956 }
2957 }
2958#endif
2959 } else { // AZERTY
2960 switch (key_char) {
2961 case 43:
2962 ZoomCanvas(g_plus_minus_zoom_factor, false);
2963 break;
2964
2965 case 54: // '-' alpha/num pad
2966 // case 56: // '_' alpha/num pad
2967 ZoomCanvas(1.0 / g_plus_minus_zoom_factor, false);
2968 break;
2969 }
2970 }
2971
2972#ifdef __WXOSX__
2973 // Ctrl+Cmd+F toggles fullscreen on macOS
2974 if (key_char == 'F' && m_modkeys & wxMOD_CONTROL &&
2975 m_modkeys & wxMOD_RAW_CONTROL) {
2976 parent_frame->ToggleFullScreen();
2977 return;
2978 }
2979#endif
2980
2981 if (event.ControlDown()) key_char -= 64;
2982
2983 if (key_char >= '0' && key_char <= '9')
2984 SetGroupIndex(key_char - '0');
2985 else
2986
2987 switch (key_char) {
2988 case 'A':
2989 SetShowENCAnchor(!GetShowENCAnchor());
2990 ReloadVP();
2991
2992 break;
2993
2994 case 'C':
2995 parent_frame->ToggleColorScheme();
2996 break;
2997
2998 case 'D': {
2999 int x, y;
3000 event.GetPosition(&x, &y);
3001 ChartTypeEnum ChartType = CHART_TYPE_UNKNOWN;
3002 ChartFamilyEnum ChartFam = CHART_FAMILY_UNKNOWN;
3003 // First find out what kind of chart is being used
3004 if (!pPopupDetailSlider) {
3005 if (VPoint.b_quilt) {
3006 if (m_pQuilt) {
3007 if (m_pQuilt->GetChartAtPix(
3008 VPoint,
3009 wxPoint(
3010 x, y))) // = null if no chart loaded for this point
3011 {
3012 ChartType = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3013 ->GetChartType();
3014 ChartFam = m_pQuilt->GetChartAtPix(VPoint, wxPoint(x, y))
3015 ->GetChartFamily();
3016 }
3017 }
3018 } else {
3019 if (m_singleChart) {
3020 ChartType = m_singleChart->GetChartType();
3021 ChartFam = m_singleChart->GetChartFamily();
3022 }
3023 }
3024 // If a charttype is found show the popupslider
3025 if ((ChartType != CHART_TYPE_UNKNOWN) ||
3026 (ChartFam != CHART_FAMILY_UNKNOWN)) {
3028 this, -1, ChartType, ChartFam,
3029 wxPoint(g_detailslider_dialog_x, g_detailslider_dialog_y),
3030 wxDefaultSize, wxSIMPLE_BORDER, "");
3032 }
3033 } else //( !pPopupDetailSlider ) close popupslider
3034 {
3036 pPopupDetailSlider = NULL;
3037 }
3038 break;
3039 }
3040
3041 case 'E':
3042 m_nmea_log->Show();
3043 m_nmea_log->Raise();
3044 break;
3045
3046 case 'L':
3047 SetShowENCLights(!GetShowENCLights());
3048 ReloadVP();
3049
3050 break;
3051
3052 case 'M':
3053 if (event.ShiftDown())
3054 m_bMeasure_DistCircle = true;
3055 else
3056 m_bMeasure_DistCircle = false;
3057
3058 StartMeasureRoute();
3059 break;
3060
3061 case 'N':
3062 if (g_bInlandEcdis && ps52plib) {
3063 SetENCDisplayCategory((_DisCat)STANDARD);
3064 }
3065 break;
3066
3067 case 'O':
3068 ToggleChartOutlines();
3069 break;
3070
3071 case 'Q':
3072 ToggleCanvasQuiltMode();
3073 break;
3074
3075 case 'P':
3076 parent_frame->ToggleTestPause();
3077 break;
3078 case 'R':
3079 g_bNavAidRadarRingsShown = !g_bNavAidRadarRingsShown;
3080 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible == 0)
3081 g_iNavAidRadarRingsNumberVisible = 1;
3082 else if (!g_bNavAidRadarRingsShown &&
3083 g_iNavAidRadarRingsNumberVisible == 1)
3084 g_iNavAidRadarRingsNumberVisible = 0;
3085 break;
3086 case 'S':
3087 SetShowENCDepth(!m_encShowDepth);
3088 ReloadVP();
3089 break;
3090
3091 case 'T':
3092 SetShowENCText(!GetShowENCText());
3093 ReloadVP();
3094 break;
3095
3096 case 'U':
3097 SetShowENCDataQual(!GetShowENCDataQual());
3098 ReloadVP();
3099 break;
3100
3101 case 'V':
3102 m_bShowNavobjects = !m_bShowNavobjects;
3103 Refresh(true);
3104 break;
3105
3106 case 'W': // W Toggle CPA alarm
3107 ToggleCPAWarn();
3108
3109 break;
3110
3111 case 1: // Ctrl A
3112 TogglebFollow();
3113
3114 break;
3115
3116 case 2: // Ctrl B
3117 if (g_bShowMenuBar == false) parent_frame->ToggleChartBar(this);
3118 break;
3119
3120 case 13: // Ctrl M // Drop Marker at cursor
3121 {
3122 if (event.ControlDown()) gFrame->DropMarker(false);
3123 break;
3124 }
3125
3126 case 14: // Ctrl N - Activate next waypoint in a route
3127 {
3128 if (Route *r = g_pRouteMan->GetpActiveRoute()) {
3129 int indexActive = r->GetIndexOf(r->m_pRouteActivePoint);
3130 if ((indexActive + 1) <= r->GetnPoints()) {
3132 InvalidateGL();
3133 Refresh(false);
3134 }
3135 }
3136 break;
3137 }
3138
3139 case 15: // Ctrl O - Drop Marker at boat's position
3140 {
3141 if (!g_bShowMenuBar) gFrame->DropMarker(true);
3142 break;
3143 }
3144
3145 case 32: // Special needs use space bar
3146 {
3147 if (g_bSpaceDropMark) gFrame->DropMarker(true);
3148 break;
3149 }
3150
3151 case -32: // Ctrl Space // Drop MOB
3152 {
3153 if (m_modkeys == wxMOD_CONTROL) parent_frame->ActivateMOB();
3154
3155 break;
3156 }
3157
3158 case -20: // Ctrl ,
3159 {
3160 parent_frame->DoSettings();
3161 break;
3162 }
3163 case 17: // Ctrl Q
3164 parent_frame->Close();
3165 return;
3166
3167 case 18: // Ctrl R
3168 StartRoute();
3169 return;
3170
3171 case 20: // Ctrl T
3172 if (NULL == pGoToPositionDialog) // There is one global instance of
3173 // the Go To Position Dialog
3175 pGoToPositionDialog->SetCanvas(this);
3176 pGoToPositionDialog->Show();
3177 break;
3178
3179 case 25: // Ctrl Y
3180 if (undo->AnythingToRedo()) {
3181 undo->RedoNextAction();
3182 InvalidateGL();
3183 Refresh(false);
3184 }
3185 break;
3186
3187 case 26:
3188 if (event.ShiftDown()) { // Shift-Ctrl-Z
3189 if (undo->AnythingToRedo()) {
3190 undo->RedoNextAction();
3191 InvalidateGL();
3192 Refresh(false);
3193 }
3194 } else { // Ctrl Z
3195 if (undo->AnythingToUndo()) {
3196 undo->UndoLastAction();
3197 InvalidateGL();
3198 Refresh(false);
3199 }
3200 }
3201 break;
3202
3203 case 27:
3204 // Generic break
3205 if (m_bMeasure_Active) {
3206 CancelMeasureRoute();
3207
3208 SetCursor(*pCursorArrow);
3209
3210 // SurfaceToolbar();
3211 gFrame->RefreshAllCanvas();
3212 }
3213
3214 if (m_routeState) // creating route?
3215 {
3216 FinishRoute();
3217 // SurfaceToolbar();
3218 InvalidateGL();
3219 Refresh(false);
3220 }
3221
3222 break;
3223
3224 case 7: // Ctrl G
3225 switch (gamma_state) {
3226 case (0):
3227 r_gamma_mult = 0;
3228 g_gamma_mult = 1;
3229 b_gamma_mult = 0;
3230 gamma_state = 1;
3231 break;
3232 case (1):
3233 r_gamma_mult = 1;
3234 g_gamma_mult = 0;
3235 b_gamma_mult = 0;
3236 gamma_state = 2;
3237 break;
3238 case (2):
3239 r_gamma_mult = 1;
3240 g_gamma_mult = 1;
3241 b_gamma_mult = 1;
3242 gamma_state = 0;
3243 break;
3244 }
3245 SetScreenBrightness(g_nbrightness);
3246
3247 break;
3248
3249 case 9: // Ctrl I
3250 if (event.ControlDown()) {
3251 m_bShowCompassWin = !m_bShowCompassWin;
3252 SetShowGPSCompassWindow(m_bShowCompassWin);
3253 Refresh(false);
3254 }
3255 break;
3256
3257 default:
3258 break;
3259
3260 } // switch
3261 }
3262
3263 // Allow OnKeyChar to catch the key events too.
3264 if (!b_handled) {
3265 event.Skip();
3266 }
3267}
3268
3269void ChartCanvas::OnKeyUp(wxKeyEvent &event) {
3270 if (SendKeyEventToPlugins(event))
3271 return; // PlugIn did something, and does not want the canvas to do
3272 // anything else
3273
3274 switch (event.GetKeyCode()) {
3275 case WXK_TAB:
3276 parent_frame->SwitchKBFocus(this);
3277 break;
3278
3279 case WXK_LEFT:
3280 case WXK_RIGHT:
3281 m_panx = 0;
3282 if (!m_pany) m_panspeed = 0;
3283 break;
3284
3285 case WXK_UP:
3286 case WXK_DOWN:
3287 m_pany = 0;
3288 if (!m_panx) m_panspeed = 0;
3289 break;
3290
3291 case WXK_NUMPAD_ADD: // '+' on NUM PAD
3292 case WXK_NUMPAD_SUBTRACT: // '-' on NUM PAD
3293 case WXK_PAGEUP:
3294 case WXK_PAGEDOWN:
3295 if (m_mustmove) DoMovement(m_mustmove);
3296
3297 m_zoom_factor = 1;
3298 break;
3299
3300 case WXK_ALT:
3301 m_modkeys &= ~wxMOD_ALT;
3302#ifdef OCPN_ALT_MENUBAR
3303#ifndef __WXOSX__
3304 // If the permanent menu bar is disabled, and we are not in the middle of
3305 // another key combo, then show the menu bar temporarily when Alt is
3306 // released (or hide it if already visible).
3307 if (IsTempMenuBarEnabled() && !g_bShowMenuBar && m_bMayToggleMenuBar) {
3308 g_bTempShowMenuBar = !g_bTempShowMenuBar;
3309 parent_frame->ApplyGlobalSettings(false);
3310 }
3311 m_bMayToggleMenuBar = true;
3312#endif
3313#endif
3314 break;
3315
3316 case WXK_CONTROL:
3317 m_modkeys &= ~wxMOD_CONTROL;
3318 break;
3319 }
3320
3321 if (event.GetKeyCode() < 128) // ascii
3322 {
3323 int key_char = event.GetKeyCode();
3324
3325 // Handle both QWERTY and AZERTY keyboard separately for a few control
3326 // codes
3327 if (!g_b_assume_azerty) {
3328 switch (key_char) {
3329 case '+':
3330 case '=':
3331 case '-':
3332 case '_':
3333 case 54:
3334 case 56: // '_' alpha/num pad
3335 DoMovement(m_mustmove);
3336
3337 // m_zoom_factor = 1;
3338 break;
3339 case '[':
3340 case ']':
3341 DoMovement(m_mustmove);
3342 m_rotation_speed = 0;
3343 break;
3344 }
3345 } else {
3346 switch (key_char) {
3347 case 43:
3348 case 54: // '-' alpha/num pad
3349 case 56: // '_' alpha/num pad
3350 DoMovement(m_mustmove);
3351
3352 m_zoom_factor = 1;
3353 break;
3354 }
3355 }
3356 }
3357 event.Skip();
3358}
3359
3360void ChartCanvas::ToggleChartOutlines() {
3361 m_bShowOutlines = !m_bShowOutlines;
3362
3363 Refresh(false);
3364
3365#ifdef ocpnUSE_GL // opengl renders chart outlines as part of the chart this
3366 // needs a full refresh
3367 if (g_bopengl) InvalidateGL();
3368#endif
3369}
3370
3371void ChartCanvas::ToggleLookahead() {
3372 m_bLookAhead = !m_bLookAhead;
3373 m_OSoffsetx = 0; // center ownship
3374 m_OSoffsety = 0;
3375}
3376
3377void ChartCanvas::SetUpMode(int mode) {
3378 m_upMode = mode;
3379
3380 if (mode != NORTH_UP_MODE) {
3381 // Stuff the COGAvg table in case COGUp is selected
3382 double stuff = 0;
3383 if (!std::isnan(gCog)) stuff = gCog;
3384
3385 if (g_COGAvgSec > 0) {
3386 for (int i = 0; i < g_COGAvgSec; i++) gFrame->COGTable[i] = stuff;
3387 }
3388 g_COGAvg = stuff;
3389 gFrame->FrameCOGTimer.Start(100, wxTIMER_CONTINUOUS);
3390 } else {
3391 if (!g_bskew_comp && (fabs(GetVPSkew()) > 0.0001))
3392 SetVPRotation(GetVPSkew());
3393 else
3394 SetVPRotation(0); /* reset to north up */
3395 }
3396
3397 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
3398 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
3399
3400 UpdateGPSCompassStatusBox(true);
3401 gFrame->DoChartUpdate();
3402}
3403
3404bool ChartCanvas::DoCanvasCOGSet() {
3405 if (GetUpMode() == NORTH_UP_MODE) return false;
3406 double cog_use = g_COGAvg;
3407 if (g_btenhertz) cog_use = gCog;
3408
3409 double rotation = 0;
3410 if ((GetUpMode() == HEAD_UP_MODE) && !std::isnan(gHdt)) {
3411 rotation = -gHdt * PI / 180.;
3412 } else if ((GetUpMode() == COURSE_UP_MODE) && !std::isnan(cog_use))
3413 rotation = -cog_use * PI / 180.;
3414
3415 SetVPRotation(rotation);
3416 return true;
3417}
3418
3419double easeOutCubic(double t) {
3420 // Starts quickly and slows down toward the end
3421 return 1.0 - pow(1.0 - t, 3.0);
3422}
3423
3424void ChartCanvas::StartChartDragInertia() {
3425 m_bChartDragging = false;
3426
3427 // Set some parameters
3428 m_chart_drag_inertia_time = 750; // msec
3429 m_chart_drag_inertia_start_time = wxGetLocalTimeMillis();
3430 m_last_elapsed = 0;
3431
3432 // Calculate ending drag velocity
3433 size_t n_vel = 10;
3434 n_vel = wxMin(n_vel, m_drag_vec_t.size());
3435 int xacc = 0;
3436 int yacc = 0;
3437 double tacc = 0;
3438 size_t length = m_drag_vec_t.size();
3439 for (size_t i = 0; i < n_vel; i++) {
3440 xacc += m_drag_vec_x.at(length - 1 - i);
3441 yacc += m_drag_vec_y.at(length - 1 - i);
3442 tacc += m_drag_vec_t.at(length - 1 - i);
3443 }
3444
3445 if (tacc == 0) return;
3446
3447 double drag_velocity_x = xacc / tacc;
3448 double drag_velocity_y = yacc / tacc;
3449 // printf("drag total %d %d %g %g %g\n", xacc, yacc, tacc, drag_velocity_x,
3450 // drag_velocity_y);
3451
3452 // Abort inertia drag if velocity is very slow, preventing jitters on sloppy
3453 // touch tap.
3454 if ((fabs(drag_velocity_x) < 200) && (fabs(drag_velocity_y) < 200)) return;
3455
3456 m_chart_drag_velocity_x = drag_velocity_x;
3457 m_chart_drag_velocity_y = drag_velocity_y;
3458
3459 m_chart_drag_inertia_active = true;
3460 // First callback as fast as possible.
3461 m_chart_drag_inertia_timer.Start(1, wxTIMER_ONE_SHOT);
3462}
3463
3464void ChartCanvas::OnChartDragInertiaTimer(wxTimerEvent &event) {
3465 if (!m_chart_drag_inertia_active) return;
3466 // Calculate time fraction from 0..1
3467 wxLongLong now = wxGetLocalTimeMillis();
3468 double elapsed = (now - m_chart_drag_inertia_start_time).ToDouble();
3469 double t = elapsed / m_chart_drag_inertia_time.ToDouble();
3470 if (t > 1.0) t = 1.0;
3471 double e = 1.0 - easeOutCubic(t); // 0..1
3472
3473 double dx =
3474 m_chart_drag_velocity_x * ((elapsed - m_last_elapsed) / 1000.) * e;
3475 double dy =
3476 m_chart_drag_velocity_y * ((elapsed - m_last_elapsed) / 1000.) * e;
3477
3478 m_last_elapsed = elapsed;
3479
3480 // Ensure that target destination lies on whole-pixel boundary
3481 // This allows the render engine to use a faster FBO copy method for drawing
3482 double destination_x = (GetCanvasWidth() / 2) + wxRound(dx);
3483 double destination_y = (GetCanvasHeight() / 2) + wxRound(dy);
3484 double inertia_lat, inertia_lon;
3485 GetCanvasPixPoint(destination_x, destination_y, inertia_lat, inertia_lon);
3486 SetViewPoint(inertia_lat, inertia_lon); // about 1 msec
3487 // Check if ownship has moved off-screen
3488 if (!IsOwnshipOnScreen()) {
3489 m_bFollow = false; // update the follow flag
3490 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
3491 UpdateFollowButtonState();
3492 m_OSoffsetx = 0;
3493 m_OSoffsety = 0;
3494 } else {
3495 m_OSoffsetx += dx;
3496 m_OSoffsety -= dy;
3497 }
3498
3499 Refresh(false);
3500
3501 // Stop condition
3502 if ((t >= 1) || (fabs(dx) < 1) || (fabs(dy) < 1)) {
3503 m_chart_drag_inertia_timer.Stop();
3504
3505 // Disable chart pan movement logic
3506 m_target_lat = GetVP().clat;
3507 m_target_lon = GetVP().clon;
3508 m_pan_drag.x = m_pan_drag.y = 0;
3509 m_panx = m_pany = 0;
3510 m_chart_drag_inertia_active = false;
3511 DoCanvasUpdate();
3512
3513 } else {
3514 int target_redraw_interval = 40; // msec
3515 m_chart_drag_inertia_timer.Start(target_redraw_interval, wxTIMER_ONE_SHOT);
3516 }
3517}
3518
3519void ChartCanvas::StopMovement() {
3520 m_panx = m_pany = 0;
3521 m_panspeed = 0;
3522 m_zoom_factor = 1;
3523 m_rotation_speed = 0;
3524 m_mustmove = 0;
3525#if 0
3526#if !defined(__WXGTK__) && !defined(__WXQT__)
3527 SetFocus();
3528 gFrame->Raise();
3529#endif
3530#endif
3531}
3532
3533/* instead of integrating in timer callbacks
3534 (which do not always get called fast enough)
3535 we can perform the integration of movement
3536 at each render frame based on the time change */
3537bool ChartCanvas::StartTimedMovement(bool stoptimer) {
3538 // Start/restart the stop movement timer
3539 if (stoptimer) pMovementStopTimer->Start(800, wxTIMER_ONE_SHOT);
3540
3541 if (!pMovementTimer->IsRunning()) {
3542 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
3543 }
3544
3545 if (m_panx || m_pany || m_zoom_factor != 1 || m_rotation_speed) {
3546 // already moving, gets called again because of key-repeat event
3547 return false;
3548 }
3549
3550 m_last_movement_time = wxDateTime::UNow();
3551
3552 return true;
3553}
3554void ChartCanvas::StartTimedMovementVP(double target_lat, double target_lon,
3555 int nstep) {
3556 // Save the target
3557 m_target_lat = target_lat;
3558 m_target_lon = target_lon;
3559
3560 // Save the start point
3561 m_start_lat = GetVP().clat;
3562 m_start_lon = GetVP().clon;
3563
3564 m_VPMovementTimer.Start(1, true); // oneshot
3565 m_timed_move_vp_active = true;
3566 m_stvpc = 0;
3567 m_timedVP_step = nstep;
3568}
3569
3570void ChartCanvas::DoTimedMovementVP() {
3571 if (!m_timed_move_vp_active) return; // not active
3572 if (m_stvpc++ > m_timedVP_step * 2) { // Backstop
3573 StopMovement();
3574 return;
3575 }
3576 // Stop condition
3577 double one_pix = (1. / (1852 * 60)) / GetVP().view_scale_ppm;
3578 double d2 =
3579 pow(m_run_lat - m_target_lat, 2) + pow(m_run_lon - m_target_lon, 2);
3580 d2 = pow(d2, 0.5);
3581
3582 if (d2 < one_pix) {
3583 SetViewPoint(m_target_lat, m_target_lon); // Embeds a refresh
3584 StopMovementVP();
3585 return;
3586 }
3587
3588 // if ((fabs(m_run_lat - m_target_lat) < one_pix) &&
3589 // (fabs(m_run_lon - m_target_lon) < one_pix)) {
3590 // StopMovementVP();
3591 // return;
3592 // }
3593
3594 double new_lat = GetVP().clat + (m_target_lat - m_start_lat) / m_timedVP_step;
3595 double new_lon = GetVP().clon + (m_target_lon - m_start_lon) / m_timedVP_step;
3596
3597 m_run_lat = new_lat;
3598 m_run_lon = new_lon;
3599
3600 SetViewPoint(new_lat, new_lon); // Embeds a refresh
3601}
3602
3603void ChartCanvas::StopMovementVP() { m_timed_move_vp_active = false; }
3604
3605void ChartCanvas::MovementVPTimerEvent(wxTimerEvent &) { DoTimedMovementVP(); }
3606
3607void ChartCanvas::StartTimedMovementTarget() {}
3608
3609void ChartCanvas::DoTimedMovementTarget() {}
3610
3611void ChartCanvas::StopMovementTarget() {}
3612int ntm;
3613
3614void ChartCanvas::DoTimedMovement() {
3615 if (m_pan_drag == wxPoint(0, 0) && !m_panx && !m_pany && m_zoom_factor == 1 &&
3616 !m_rotation_speed)
3617 return; /* not moving */
3618
3619 wxDateTime now = wxDateTime::UNow();
3620 long dt = 0;
3621 if (m_last_movement_time.IsValid())
3622 dt = (now - m_last_movement_time).GetMilliseconds().ToLong();
3623
3624 m_last_movement_time = now;
3625
3626 if (dt > 500) /* if we are running very slow, don't integrate too fast */
3627 dt = 500;
3628
3629 DoMovement(dt);
3630}
3631
3633 /* if we get here quickly assume 1ms so that some movement occurs */
3634 if (dt == 0) dt = 1;
3635
3636 m_mustmove -= dt;
3637 if (m_mustmove < 0) m_mustmove = 0;
3638
3639 if (!m_inPinch) { // this stops compound zoom/pan
3640 if (m_pan_drag.x || m_pan_drag.y) {
3641 PanCanvas(m_pan_drag.x, m_pan_drag.y);
3642 m_pan_drag.x = m_pan_drag.y = 0;
3643 }
3644
3645 if (m_panx || m_pany) {
3646 const double slowpan = .1, maxpan = 2;
3647 if (m_modkeys == wxMOD_ALT)
3648 m_panspeed = slowpan;
3649 else {
3650 m_panspeed += (double)dt / 500; /* apply acceleration */
3651 m_panspeed = wxMin(maxpan, m_panspeed);
3652 }
3653 PanCanvas(m_panspeed * m_panx * dt, m_panspeed * m_pany * dt);
3654 }
3655 }
3656 if (m_zoom_factor != 1) {
3657 double alpha = 400, beta = 1.5;
3658 double zoom_factor = (exp(dt / alpha) - 1) / beta + 1;
3659
3660 if (m_modkeys == wxMOD_ALT) zoom_factor = pow(zoom_factor, .15);
3661
3662 if (m_zoom_factor < 1) zoom_factor = 1 / zoom_factor;
3663
3664 // Try to hit the zoom target exactly.
3665 // if(m_wheelzoom_stop_oneshot > 0)
3666 {
3667 if (zoom_factor > 1) {
3668 if (VPoint.chart_scale / zoom_factor <= m_zoom_target)
3669 zoom_factor = VPoint.chart_scale / m_zoom_target;
3670 }
3671
3672 else if (zoom_factor < 1) {
3673 if (VPoint.chart_scale / zoom_factor >= m_zoom_target)
3674 zoom_factor = VPoint.chart_scale / m_zoom_target;
3675 }
3676 }
3677
3678 if (fabs(zoom_factor - 1) > 1e-4) {
3679 DoZoomCanvas(zoom_factor, m_bzooming_to_cursor);
3680 } else {
3681 StopMovement();
3682 }
3683
3684 if (m_wheelzoom_stop_oneshot > 0) {
3685 if (m_wheelstopwatch.Time() > m_wheelzoom_stop_oneshot) {
3686 m_wheelzoom_stop_oneshot = 0;
3687 StopMovement();
3688 }
3689
3690 // Don't overshoot the zoom target.
3691 if (zoom_factor > 1) {
3692 if (VPoint.chart_scale <= m_zoom_target) {
3693 m_wheelzoom_stop_oneshot = 0;
3694 StopMovement();
3695 }
3696 } else if (zoom_factor < 1) {
3697 if (VPoint.chart_scale >= m_zoom_target) {
3698 m_wheelzoom_stop_oneshot = 0;
3699 StopMovement();
3700 }
3701 }
3702 }
3703 }
3704
3705 if (m_rotation_speed) { /* in degrees per second */
3706 double speed = m_rotation_speed;
3707 if (m_modkeys == wxMOD_ALT) speed /= 10;
3708 DoRotateCanvas(VPoint.rotation + speed * PI / 180 * dt / 1000.0);
3709 }
3710}
3711
3712void ChartCanvas::SetColorScheme(ColorScheme cs) {
3713 SetAlertString("");
3714
3715 // Setup ownship image pointers
3716 switch (cs) {
3717 case GLOBAL_COLOR_SCHEME_DAY:
3718 m_pos_image_red = &m_os_image_red_day;
3719 m_pos_image_grey = &m_os_image_grey_day;
3720 m_pos_image_yellow = &m_os_image_yellow_day;
3721 m_pos_image_user = m_pos_image_user_day;
3722 m_pos_image_user_grey = m_pos_image_user_grey_day;
3723 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3724 m_cTideBitmap = m_bmTideDay;
3725 m_cCurrentBitmap = m_bmCurrentDay;
3726
3727 break;
3728 case GLOBAL_COLOR_SCHEME_DUSK:
3729 m_pos_image_red = &m_os_image_red_dusk;
3730 m_pos_image_grey = &m_os_image_grey_dusk;
3731 m_pos_image_yellow = &m_os_image_yellow_dusk;
3732 m_pos_image_user = m_pos_image_user_dusk;
3733 m_pos_image_user_grey = m_pos_image_user_grey_dusk;
3734 m_pos_image_user_yellow = m_pos_image_user_yellow_dusk;
3735 m_cTideBitmap = m_bmTideDusk;
3736 m_cCurrentBitmap = m_bmCurrentDusk;
3737 break;
3738 case GLOBAL_COLOR_SCHEME_NIGHT:
3739 m_pos_image_red = &m_os_image_red_night;
3740 m_pos_image_grey = &m_os_image_grey_night;
3741 m_pos_image_yellow = &m_os_image_yellow_night;
3742 m_pos_image_user = m_pos_image_user_night;
3743 m_pos_image_user_grey = m_pos_image_user_grey_night;
3744 m_pos_image_user_yellow = m_pos_image_user_yellow_night;
3745 m_cTideBitmap = m_bmTideNight;
3746 m_cCurrentBitmap = m_bmCurrentNight;
3747 break;
3748 default:
3749 m_pos_image_red = &m_os_image_red_day;
3750 m_pos_image_grey = &m_os_image_grey_day;
3751 m_pos_image_yellow = &m_os_image_yellow_day;
3752 m_pos_image_user = m_pos_image_user_day;
3753 m_pos_image_user_grey = m_pos_image_user_grey_day;
3754 m_pos_image_user_yellow = m_pos_image_user_yellow_day;
3755 m_cTideBitmap = m_bmTideDay;
3756 m_cCurrentBitmap = m_bmCurrentDay;
3757 break;
3758 }
3759
3760 CreateDepthUnitEmbossMaps(cs);
3761 CreateOZEmbossMapData(cs);
3762
3763 // Set up fog effect base color
3764 m_fog_color = wxColor(
3765 170, 195, 240); // this is gshhs (backgound world chart) ocean color
3766 float dim = 1.0;
3767 switch (cs) {
3768 case GLOBAL_COLOR_SCHEME_DUSK:
3769 dim = 0.5;
3770 break;
3771 case GLOBAL_COLOR_SCHEME_NIGHT:
3772 dim = 0.25;
3773 break;
3774 default:
3775 break;
3776 }
3777 m_fog_color.Set(m_fog_color.Red() * dim, m_fog_color.Green() * dim,
3778 m_fog_color.Blue() * dim);
3779
3780 // Really dark
3781#if 0
3782 if( cs == GLOBAL_COLOR_SCHEME_DUSK || cs == GLOBAL_COLOR_SCHEME_NIGHT ) {
3783 SetBackgroundColour( wxColour(0,0,0) );
3784
3785 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxSIMPLE_BORDER) | wxNO_BORDER);
3786 }
3787 else{
3788 SetWindowStyleFlag( (GetWindowStyleFlag() & ~wxNO_BORDER) | wxSIMPLE_BORDER);
3789#ifndef __WXMAC__
3790 SetBackgroundColour( wxNullColour );
3791#endif
3792 }
3793#endif
3794
3795 // UpdateToolbarColorScheme(cs);
3796
3797 m_Piano->SetColorScheme(cs);
3798
3799 m_Compass->SetColorScheme(cs);
3800
3801 if (m_muiBar) m_muiBar->SetColorScheme(cs);
3802
3803 if (pWorldBackgroundChart) pWorldBackgroundChart->SetColorScheme(cs);
3804
3805 if (m_NotificationsList) m_NotificationsList->SetColorScheme();
3806 if (m_notification_button) {
3807 m_notification_button->SetColorScheme(cs);
3808 }
3809
3810#ifdef ocpnUSE_GL
3811 if (g_bopengl && m_glcc) {
3812 m_glcc->SetColorScheme(cs);
3813 g_glTextureManager->ClearAllRasterTextures();
3814 // m_glcc->FlushFBO();
3815 }
3816#endif
3817 SetbTCUpdate(true); // force re-render of tide/current locators
3818 m_brepaint_piano = true;
3819
3820 ReloadVP();
3821
3822 m_cs = cs;
3823}
3824
3825wxBitmap ChartCanvas::CreateDimBitmap(wxBitmap &Bitmap, double factor) {
3826 wxImage img = Bitmap.ConvertToImage();
3827 int sx = img.GetWidth();
3828 int sy = img.GetHeight();
3829
3830 wxImage new_img(img);
3831
3832 for (int i = 0; i < sx; i++) {
3833 for (int j = 0; j < sy; j++) {
3834 if (!img.IsTransparent(i, j)) {
3835 new_img.SetRGB(i, j, (unsigned char)(img.GetRed(i, j) * factor),
3836 (unsigned char)(img.GetGreen(i, j) * factor),
3837 (unsigned char)(img.GetBlue(i, j) * factor));
3838 }
3839 }
3840 }
3841
3842 wxBitmap ret = wxBitmap(new_img);
3843
3844 return ret;
3845}
3846
3847void ChartCanvas::ShowBrightnessLevelTimedPopup(int brightness, int min,
3848 int max) {
3849 wxFont *pfont = FontMgr::Get().FindOrCreateFont(
3850 40, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
3851
3852 if (!m_pBrightPopup) {
3853 // Calculate size
3854 int x, y;
3855 GetTextExtent("MAX", &x, &y, NULL, NULL, pfont);
3856
3857 m_pBrightPopup = new TimedPopupWin(this, 3);
3858
3859 m_pBrightPopup->SetSize(x, y);
3860 m_pBrightPopup->Move(120, 120);
3861 }
3862
3863 int bmpsx = m_pBrightPopup->GetSize().x;
3864 int bmpsy = m_pBrightPopup->GetSize().y;
3865
3866 wxBitmap bmp(bmpsx, bmpsx);
3867 wxMemoryDC mdc(bmp);
3868
3869 mdc.SetTextForeground(GetGlobalColor("GREEN4"));
3870 mdc.SetBackground(wxBrush(GetGlobalColor("UINFD")));
3871 mdc.SetPen(wxPen(wxColour(0, 0, 0)));
3872 mdc.SetBrush(wxBrush(GetGlobalColor("UINFD")));
3873 mdc.Clear();
3874
3875 mdc.DrawRectangle(0, 0, bmpsx, bmpsy);
3876
3877 mdc.SetFont(*pfont);
3878 wxString val;
3879
3880 if (brightness == max)
3881 val = "MAX";
3882 else if (brightness == min)
3883 val = "MIN";
3884 else
3885 val.Printf("%3d", brightness);
3886
3887 mdc.DrawText(val, 0, 0);
3888
3889 mdc.SelectObject(wxNullBitmap);
3890
3891 m_pBrightPopup->SetBitmap(bmp);
3892 m_pBrightPopup->Show();
3893 m_pBrightPopup->Refresh();
3894}
3895
3896void ChartCanvas::RotateTimerEvent(wxTimerEvent &event) {
3897 m_b_rot_hidef = true;
3898 ReloadVP();
3899}
3900
3901void ChartCanvas::OnRolloverPopupTimerEvent(wxTimerEvent &event) {
3902 if (!g_bRollover) return;
3903
3904 bool b_need_refresh = false;
3905
3906 wxSize win_size = GetSize() * m_displayScale;
3907 if (console && console->IsShown()) win_size.x -= console->GetSize().x;
3908
3909 // Handle the AIS Rollover Window first
3910 bool showAISRollover = false;
3911 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
3912 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3913 SelectItem *pFind = pSelectAIS->FindSelection(
3914 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
3915 if (pFind) {
3916 int FoundAIS_MMSI = (wxIntPtr)pFind->m_pData1;
3917 auto ptarget = g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI);
3918
3919 if (ptarget) {
3920 showAISRollover = true;
3921
3922 if (NULL == m_pAISRolloverWin) {
3923 m_pAISRolloverWin = new RolloverWin(this);
3924 m_pAISRolloverWin->IsActive(false);
3925 b_need_refresh = true;
3926 } else if (m_pAISRolloverWin->IsActive() && m_AISRollover_MMSI &&
3927 m_AISRollover_MMSI != FoundAIS_MMSI) {
3928 // Sometimes the mouse moves fast enough to get over a new AIS
3929 // target before the one-shot has fired to remove the old target.
3930 // Result: wrong target data is shown.
3931 // Detect this case,close the existing rollover ASAP, and restart
3932 // the timer.
3933 m_RolloverPopupTimer.Start(50, wxTIMER_ONE_SHOT);
3934 m_pAISRolloverWin->IsActive(false);
3935 m_AISRollover_MMSI = 0;
3936 Refresh();
3937 return;
3938 }
3939
3940 m_AISRollover_MMSI = FoundAIS_MMSI;
3941
3942 if (!m_pAISRolloverWin->IsActive()) {
3943 wxString s = ptarget->GetRolloverString();
3944 m_pAISRolloverWin->SetString(s);
3945
3946 m_pAISRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
3947 AIS_ROLLOVER, win_size);
3948 m_pAISRolloverWin->SetBitmap(AIS_ROLLOVER);
3949 m_pAISRolloverWin->IsActive(true);
3950 b_need_refresh = true;
3951 }
3952 }
3953 } else {
3954 m_AISRollover_MMSI = 0;
3955 showAISRollover = false;
3956 }
3957 }
3958
3959 // Maybe turn the rollover off
3960 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive() && !showAISRollover) {
3961 m_pAISRolloverWin->IsActive(false);
3962 m_AISRollover_MMSI = 0;
3963 b_need_refresh = true;
3964 }
3965
3966 // Now the Route info rollover
3967 // Show the route segment info
3968 bool showRouteRollover = false;
3969
3970 if (NULL == m_pRolloverRouteSeg) {
3971 // Get a list of all selectable sgements, and search for the first
3972 // visible segment as the rollover target.
3973
3974 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
3975 SelectableItemList SelList = pSelect->FindSelectionList(
3976 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
3977 auto node = SelList.begin();
3978 while (node != SelList.end()) {
3979 SelectItem *pFindSel = *node;
3980
3981 Route *pr = (Route *)pFindSel->m_pData3; // candidate
3982
3983 if (pr && pr->IsVisible()) {
3984 m_pRolloverRouteSeg = pFindSel;
3985 showRouteRollover = true;
3986
3987 if (NULL == m_pRouteRolloverWin) {
3988 m_pRouteRolloverWin = new RolloverWin(this, 10);
3989 m_pRouteRolloverWin->IsActive(false);
3990 }
3991
3992 if (!m_pRouteRolloverWin->IsActive()) {
3993 wxString s;
3994 RoutePoint *segShow_point_a =
3995 (RoutePoint *)m_pRolloverRouteSeg->m_pData1;
3996 RoutePoint *segShow_point_b =
3997 (RoutePoint *)m_pRolloverRouteSeg->m_pData2;
3998
3999 double brg, dist;
4000 DistanceBearingMercator(
4001 segShow_point_b->m_lat, segShow_point_b->m_lon,
4002 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4003
4004 if (!pr->m_bIsInLayer)
4005 s.Append(_("Route") + ": ");
4006 else
4007 s.Append(_("Layer Route: "));
4008
4009 if (pr->m_RouteNameString.IsEmpty())
4010 s.Append(_("(unnamed)"));
4011 else
4012 s.Append(pr->m_RouteNameString);
4013
4014 s << "\n"
4015 << _("Total Length: ") << FormatDistanceAdaptive(pr->m_route_length)
4016 << "\n"
4017 << _("Leg: from ") << segShow_point_a->GetName() << _(" to ")
4018 << segShow_point_b->GetName() << "\n";
4019
4020 if (g_bShowTrue)
4021 s << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
4022 (int)floor(brg + 0.5), 0x00B0);
4023 if (g_bShowMag) {
4024 double latAverage =
4025 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4026 double lonAverage =
4027 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4028 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4029
4030 s << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
4031 (int)floor(varBrg + 0.5), 0x00B0);
4032 }
4033
4034 s << FormatDistanceAdaptive(dist);
4035
4036 // Compute and display cumulative distance from route start point to
4037 // current leg end point and RNG,TTG,ETA from ship to current leg end
4038 // point for active route
4039 double shiptoEndLeg = 0.;
4040 bool validActive = false;
4041 if (pr->IsActive() && (*pr->pRoutePointList->begin())->m_bIsActive)
4042 validActive = true;
4043
4044 if (segShow_point_a != *pr->pRoutePointList->begin()) {
4045 auto node = pr->pRoutePointList->begin();
4046 RoutePoint *prp;
4047 float dist_to_endleg = 0;
4048 wxString t;
4049
4050 for (++node; node != pr->pRoutePointList->end(); ++node) {
4051 prp = *node;
4052 if (validActive)
4053 shiptoEndLeg += prp->m_seg_len;
4054 else if (prp->m_bIsActive)
4055 validActive = true;
4056 dist_to_endleg += prp->m_seg_len;
4057 if (prp->IsSame(segShow_point_a)) break;
4058 }
4059 s << " (+" << FormatDistanceAdaptive(dist_to_endleg) << ")";
4060 }
4061 // write from ship to end selected leg point data if the route is
4062 // active
4063 if (validActive) {
4064 s << "\n"
4065 << _("From Ship To") << " " << segShow_point_b->GetName() << "\n";
4066 shiptoEndLeg +=
4068 ->GetCurrentRngToActivePoint(); // add distance from ship
4069 // to active point
4070 shiptoEndLeg +=
4071 segShow_point_b
4072 ->m_seg_len; // add the lenght of the selected leg
4073 s << FormatDistanceAdaptive(shiptoEndLeg);
4074 // ensure sog/cog are valid and vmg is positive to keep data
4075 // coherent
4076 double vmg = 0.;
4077 if (!std::isnan(gCog) && !std::isnan(gSog))
4078 vmg = gSog *
4079 cos((g_pRouteMan->GetCurrentBrgToActivePoint() - gCog) *
4080 PI / 180.);
4081 if (vmg > 0.) {
4082 float ttg_sec = (shiptoEndLeg / gSog) * 3600.;
4083 wxTimeSpan ttg_span = wxTimeSpan::Seconds((long)ttg_sec);
4084 s << " - "
4085 << wxString(ttg_sec > SECONDS_PER_DAY
4086 ? ttg_span.Format(_("%Dd %H:%M"))
4087 : ttg_span.Format(_("%H:%M")));
4088 wxDateTime dtnow, eta;
4089 eta = dtnow.SetToCurrent().Add(ttg_span);
4090 s << " - " << eta.Format("%b").Mid(0, 4)
4091 << eta.Format(" %d %H:%M");
4092 } else
4093 s << " ---- ----";
4094 }
4095 m_pRouteRolloverWin->SetString(s);
4096
4097 m_pRouteRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4098 LEG_ROLLOVER, win_size);
4099 m_pRouteRolloverWin->SetBitmap(LEG_ROLLOVER);
4100 m_pRouteRolloverWin->IsActive(true);
4101 b_need_refresh = true;
4102 showRouteRollover = true;
4103 break;
4104 }
4105 } else {
4106 ++node;
4107 }
4108 }
4109 } else {
4110 // Is the cursor still in select radius, and not timed out?
4111 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4112 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4113 m_pRolloverRouteSeg))
4114 showRouteRollover = false;
4115 else if (m_pRouteRolloverWin && !m_pRouteRolloverWin->IsActive())
4116 showRouteRollover = false;
4117 else
4118 showRouteRollover = true;
4119 }
4120
4121 // If currently creating a route, do not show this rollover window
4122 if (m_routeState) showRouteRollover = false;
4123
4124 // Similar for AIS target rollover window
4125 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4126 showRouteRollover = false;
4127
4128 if (m_pRouteRolloverWin /*&& m_pRouteRolloverWin->IsActive()*/ &&
4129 !showRouteRollover) {
4130 m_pRouteRolloverWin->IsActive(false);
4131 m_pRolloverRouteSeg = NULL;
4132 m_pRouteRolloverWin->Destroy();
4133 m_pRouteRolloverWin = NULL;
4134 b_need_refresh = true;
4135 } else if (m_pRouteRolloverWin && showRouteRollover) {
4136 m_pRouteRolloverWin->IsActive(true);
4137 b_need_refresh = true;
4138 }
4139
4140 // Now the Track info rollover
4141 // Show the track segment info
4142 bool showTrackRollover = false;
4143
4144 if (NULL == m_pRolloverTrackSeg) {
4145 // Get a list of all selectable sgements, and search for the first
4146 // visible segment as the rollover target.
4147
4148 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4149 SelectableItemList SelList = pSelect->FindSelectionList(
4150 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
4151
4152 auto node = SelList.begin();
4153 while (node != SelList.end()) {
4154 SelectItem *pFindSel = *node;
4155
4156 Track *pt = (Track *)pFindSel->m_pData3; // candidate
4157
4158 if (pt && pt->IsVisible()) {
4159 m_pRolloverTrackSeg = pFindSel;
4160 showTrackRollover = true;
4161
4162 if (NULL == m_pTrackRolloverWin) {
4163 m_pTrackRolloverWin = new RolloverWin(this, 10);
4164 m_pTrackRolloverWin->IsActive(false);
4165 }
4166
4167 if (!m_pTrackRolloverWin->IsActive()) {
4168 wxString s;
4169 TrackPoint *segShow_point_a =
4170 (TrackPoint *)m_pRolloverTrackSeg->m_pData1;
4171 TrackPoint *segShow_point_b =
4172 (TrackPoint *)m_pRolloverTrackSeg->m_pData2;
4173
4174 double brg, dist;
4175 DistanceBearingMercator(
4176 segShow_point_b->m_lat, segShow_point_b->m_lon,
4177 segShow_point_a->m_lat, segShow_point_a->m_lon, &brg, &dist);
4178
4179 if (!pt->m_bIsInLayer)
4180 s.Append(_("Track") + ": ");
4181 else
4182 s.Append(_("Layer Track: "));
4183
4184 if (pt->GetName().IsEmpty())
4185 s.Append(_("(unnamed)"));
4186 else
4187 s.Append(pt->GetName());
4188 double tlenght = pt->Length();
4189 s << "\n" << _("Total Track: ") << FormatDistanceAdaptive(tlenght);
4190 if (pt->GetLastPoint()->GetTimeString() &&
4191 pt->GetPoint(0)->GetTimeString()) {
4192 wxDateTime lastPointTime = pt->GetLastPoint()->GetCreateTime();
4193 wxDateTime zeroPointTime = pt->GetPoint(0)->GetCreateTime();
4194 if (lastPointTime.IsValid() && zeroPointTime.IsValid()) {
4195 wxTimeSpan ttime = lastPointTime - zeroPointTime;
4196 double htime = ttime.GetSeconds().ToDouble() / 3600.;
4197 s << wxString::Format(" %.1f ", (float)(tlenght / htime))
4198 << getUsrSpeedUnit();
4199 s << wxString(htime > 24. ? ttime.Format(" %Dd %H:%M")
4200 : ttime.Format(" %H:%M"));
4201 }
4202 }
4203
4204 if (g_bShowTrackPointTime &&
4205 strlen(segShow_point_b->GetTimeString())) {
4206 wxString stamp = segShow_point_b->GetTimeString();
4207 wxDateTime timestamp = segShow_point_b->GetCreateTime();
4208 if (timestamp.IsValid()) {
4209 // Format track rollover timestamp to OCPN global TZ setting
4212 stamp = ocpn::toUsrDateTimeFormat(timestamp.FromUTC(), opts);
4213 }
4214 s << "\n" << _("Segment Created: ") << stamp;
4215 }
4216
4217 s << "\n";
4218 if (g_bShowTrue)
4219 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)brg,
4220 0x00B0);
4221
4222 if (g_bShowMag) {
4223 double latAverage =
4224 (segShow_point_b->m_lat + segShow_point_a->m_lat) / 2;
4225 double lonAverage =
4226 (segShow_point_b->m_lon + segShow_point_a->m_lon) / 2;
4227 double varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
4228
4229 s << wxString::Format(wxString("%03d%c ", wxConvUTF8), (int)varBrg,
4230 0x00B0);
4231 }
4232
4233 s << FormatDistanceAdaptive(dist);
4234
4235 if (segShow_point_a->GetTimeString() &&
4236 segShow_point_b->GetTimeString()) {
4237 wxDateTime apoint = segShow_point_a->GetCreateTime();
4238 wxDateTime bpoint = segShow_point_b->GetCreateTime();
4239 if (apoint.IsValid() && bpoint.IsValid()) {
4240 double segmentSpeed = toUsrSpeed(
4241 dist / ((bpoint - apoint).GetSeconds().ToDouble() / 3600.));
4242 s << wxString::Format(" %.1f ", (float)segmentSpeed)
4243 << getUsrSpeedUnit();
4244 }
4245 }
4246
4247 m_pTrackRolloverWin->SetString(s);
4248
4249 m_pTrackRolloverWin->SetBestPosition(mouse_x, mouse_y, 16, 16,
4250 LEG_ROLLOVER, win_size);
4251 m_pTrackRolloverWin->SetBitmap(LEG_ROLLOVER);
4252 m_pTrackRolloverWin->IsActive(true);
4253 b_need_refresh = true;
4254 showTrackRollover = true;
4255 break;
4256 }
4257 } else {
4258 ++node;
4259 }
4260 }
4261 } else {
4262 // Is the cursor still in select radius, and not timed out?
4263 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
4264 if (!pSelect->IsSelectableSegmentSelected(ctx, m_cursor_lat, m_cursor_lon,
4265 m_pRolloverTrackSeg))
4266 showTrackRollover = false;
4267 else if (m_pTrackRolloverWin && !m_pTrackRolloverWin->IsActive())
4268 showTrackRollover = false;
4269 else
4270 showTrackRollover = true;
4271 }
4272
4273 // Similar for AIS target rollover window
4274 if (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())
4275 showTrackRollover = false;
4276
4277 // Similar for route rollover window
4278 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive())
4279 showTrackRollover = false;
4280
4281 // TODO We onlt show tracks on primary canvas....
4282 // if(!IsPrimaryCanvas())
4283 // showTrackRollover = false;
4284
4285 if (m_pTrackRolloverWin /*&& m_pTrackRolloverWin->IsActive()*/ &&
4286 !showTrackRollover) {
4287 m_pTrackRolloverWin->IsActive(false);
4288 m_pRolloverTrackSeg = NULL;
4289 m_pTrackRolloverWin->Destroy();
4290 m_pTrackRolloverWin = NULL;
4291 b_need_refresh = true;
4292 } else if (m_pTrackRolloverWin && showTrackRollover) {
4293 m_pTrackRolloverWin->IsActive(true);
4294 b_need_refresh = true;
4295 }
4296
4297 if (b_need_refresh) Refresh();
4298}
4299
4300void ChartCanvas::OnCursorTrackTimerEvent(wxTimerEvent &event) {
4301 if ((GetShowENCLights() || m_bsectors_shown) &&
4302 s57_CheckExtendedLightSectors(this, mouse_x, mouse_y, VPoint,
4303 extendedSectorLegs)) {
4304 if (!m_bsectors_shown) {
4305 ReloadVP(false);
4306 m_bsectors_shown = true;
4307 }
4308 } else {
4309 if (m_bsectors_shown) {
4310 ReloadVP(false);
4311 m_bsectors_shown = false;
4312 }
4313 }
4314
4315// This is here because GTK status window update is expensive..
4316// cairo using pango rebuilds the font every time so is very
4317// inefficient
4318// Anyway, only update the status bar when this timer expires
4319#if defined(__WXGTK__) || defined(__WXQT__)
4320 {
4321 // Check the absolute range of the cursor position
4322 // There could be a window wherein the chart geoereferencing is not
4323 // valid....
4324 double cursor_lat, cursor_lon;
4325 GetCanvasPixPoint(mouse_x, mouse_y, cursor_lat, cursor_lon);
4326
4327 if ((fabs(cursor_lat) < 90.) && (fabs(cursor_lon) < 360.)) {
4328 while (cursor_lon < -180.) cursor_lon += 360.;
4329
4330 while (cursor_lon > 180.) cursor_lon -= 360.;
4331
4332 SetCursorStatus(cursor_lat, cursor_lon);
4333 }
4334 }
4335#endif
4336}
4337
4338void ChartCanvas::SetCursorStatus(double cursor_lat, double cursor_lon) {
4339 if (!parent_frame->m_pStatusBar) return;
4340
4341 wxString s1;
4342 s1 += " ";
4343 s1 += toSDMM(1, cursor_lat);
4344 s1 += " ";
4345 s1 += toSDMM(2, cursor_lon);
4346
4347 if (STAT_FIELD_CURSOR_LL >= 0)
4348 parent_frame->SetStatusText(s1, STAT_FIELD_CURSOR_LL);
4349
4350 if (STAT_FIELD_CURSOR_BRGRNG < 0) return;
4351
4352 double brg, dist;
4353 wxString sm;
4354 wxString st;
4355 DistanceBearingMercator(cursor_lat, cursor_lon, gLat, gLon, &brg, &dist);
4356 if (g_bShowMag) sm.Printf("%03d%c(M) ", (int)toMagnetic(brg), 0x00B0);
4357 if (g_bShowTrue) st.Printf("%03d%c(T) ", (int)brg, 0x00B0);
4358
4359 wxString s = st + sm;
4360 s << FormatDistanceAdaptive(dist);
4361
4362 // CUSTOMIZATION - LIVE ETA OPTION
4363 // -------------------------------------------------------
4364 // Calculate an "live" ETA based on route starting from the current
4365 // position of the boat and goes to the cursor of the mouse.
4366 // In any case, an standard ETA will be calculated with a default speed
4367 // of the boat to give an estimation of the route (in particular if GPS
4368 // is off).
4369
4370 // Display only if option "live ETA" is selected in Settings > Display >
4371 // General.
4372 if (g_bShowLiveETA) {
4373 float realTimeETA;
4374 float boatSpeed;
4375 float boatSpeedDefault = g_defaultBoatSpeed;
4376
4377 // Calculate Estimate Time to Arrival (ETA) in minutes
4378 // Check before is value not closed to zero (it will make an very big
4379 // number...)
4380 if (!std::isnan(gSog)) {
4381 boatSpeed = gSog;
4382 if (boatSpeed < 0.5) {
4383 realTimeETA = 0;
4384 } else {
4385 realTimeETA = dist / boatSpeed * 60;
4386 }
4387 } else {
4388 realTimeETA = 0;
4389 }
4390
4391 // Add space after distance display
4392 s << " ";
4393 // Display ETA
4394 s << minutesToHoursDays(realTimeETA);
4395
4396 // In any case, display also an ETA with default speed at 6knts
4397
4398 s << " [@";
4399 s << wxString::Format("%d", (int)toUsrSpeed(boatSpeedDefault, -1));
4400 s << wxString::Format("%s", getUsrSpeedUnit(-1));
4401 s << " ";
4402 s << minutesToHoursDays(dist / boatSpeedDefault * 60);
4403 s << "]";
4404 }
4405 // END OF - LIVE ETA OPTION
4406
4407 parent_frame->SetStatusText(s, STAT_FIELD_CURSOR_BRGRNG);
4408}
4409
4410// CUSTOMIZATION - FORMAT MINUTES
4411// -------------------------------------------------------
4412// New function to format minutes into a more readable format:
4413// * Hours + minutes, or
4414// * Days + hours.
4415wxString minutesToHoursDays(float timeInMinutes) {
4416 wxString s;
4417
4418 if (timeInMinutes == 0) {
4419 s << "--min";
4420 }
4421
4422 // Less than 60min, keep time in minutes
4423 else if (timeInMinutes < 60 && timeInMinutes != 0) {
4424 s << wxString::Format("%d", (int)timeInMinutes);
4425 s << "min";
4426 }
4427
4428 // Between 1h and less than 24h, display time in hours, minutes
4429 else if (timeInMinutes >= 60 && timeInMinutes < 24 * 60) {
4430 int hours;
4431 int min;
4432 hours = (int)timeInMinutes / 60;
4433 min = (int)timeInMinutes % 60;
4434
4435 if (min == 0) {
4436 s << wxString::Format("%d", hours);
4437 s << "h";
4438 } else {
4439 s << wxString::Format("%d", hours);
4440 s << "h";
4441 s << wxString::Format("%d", min);
4442 s << "min";
4443 }
4444
4445 }
4446
4447 // More than 24h, display time in days, hours
4448 else if (timeInMinutes > 24 * 60) {
4449 int days;
4450 int hours;
4451 days = (int)(timeInMinutes / 60) / 24;
4452 hours = (int)(timeInMinutes / 60) % 24;
4453
4454 if (hours == 0) {
4455 s << wxString::Format("%d", days);
4456 s << "d";
4457 } else {
4458 s << wxString::Format("%d", days);
4459 s << "d";
4460 s << wxString::Format("%d", hours);
4461 s << "h";
4462 }
4463 }
4464
4465 return s;
4466}
4467
4468// END OF CUSTOMIZATION - FORMAT MINUTES
4469// Thanks open source code ;-)
4470// -------------------------------------------------------
4471
4472void ChartCanvas::GetCursorLatLon(double *lat, double *lon) {
4473 double clat, clon;
4474 GetCanvasPixPoint(mouse_x, mouse_y, clat, clon);
4475 *lat = clat;
4476 *lon = clon;
4477}
4478
4479void ChartCanvas::GetDoubleCanvasPointPix(double rlat, double rlon,
4480 wxPoint2DDouble *r) {
4481 return GetDoubleCanvasPointPixVP(GetVP(), rlat, rlon, r);
4482}
4483
4485 double rlon, wxPoint2DDouble *r) {
4486 // If the Current Chart is a raster chart, and the
4487 // requested lat/long is within the boundaries of the chart,
4488 // and the VP is not rotated,
4489 // then use the embedded BSB chart georeferencing algorithm
4490 // for greater accuracy
4491 // Additionally, use chart embedded georef if the projection is TMERC
4492 // i.e. NOT MERCATOR and NOT POLYCONIC
4493
4494 // If for some reason the chart rejects the request by returning an error,
4495 // then fall back to Viewport Projection estimate from canvas parameters
4496 if (!g_bopengl && m_singleChart &&
4497 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4498 (((fabs(vp.rotation) < .0001) && (fabs(vp.skew) < .0001)) ||
4499 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4500 (m_singleChart->GetChartProjectionType() !=
4501 PROJECTION_TRANSVERSE_MERCATOR) &&
4502 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4503 (m_singleChart->GetChartProjectionType() == vp.m_projection_type) &&
4504 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4505 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4506 // bool bInside = G_FloatPtInPolygon ( ( MyFlPoint *
4507 // ) Cur_BSB_Ch->GetCOVRTableHead ( 0 ),
4508 // Cur_BSB_Ch->GetCOVRTablenPoints
4509 // ( 0 ), rlon,
4510 // rlat );
4511 // bInside = true;
4512 // if ( bInside )
4513 if (Cur_BSB_Ch) {
4514 // This is a Raster chart....
4515 // If the VP is changing, the raster chart parameters may not yet be
4516 // setup So do that before accessing the chart's embedded
4517 // georeferencing
4518 Cur_BSB_Ch->SetVPRasterParms(vp);
4519 double rpixxd, rpixyd;
4520 if (0 == Cur_BSB_Ch->latlong_to_pix_vp(rlat, rlon, rpixxd, rpixyd, vp)) {
4521 r->m_x = rpixxd;
4522 r->m_y = rpixyd;
4523 return;
4524 }
4525 }
4526 }
4527
4528 // if needed, use the VPoint scaling estimator,
4529 *r = vp.GetDoublePixFromLL(rlat, rlon);
4530}
4531
4532// This routine might be deleted and all of the rendering improved
4533// to have floating point accuracy
4534bool ChartCanvas::GetCanvasPointPix(double rlat, double rlon, wxPoint *r) {
4535 return GetCanvasPointPixVP(GetVP(), rlat, rlon, r);
4536}
4537
4538bool ChartCanvas::GetCanvasPointPixVP(ViewPort &vp, double rlat, double rlon,
4539 wxPoint *r) {
4540 wxPoint2DDouble p;
4541 GetDoubleCanvasPointPixVP(vp, rlat, rlon, &p);
4542
4543 // some projections give nan values when invisible values (other side of
4544 // world) are requested we should stop using integer coordinates or return
4545 // false here (and test it everywhere)
4546 if (std::isnan(p.m_x)) {
4547 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4548 return false;
4549 }
4550
4551 if ((abs(p.m_x) < 1e6) && (abs(p.m_y) < 1e6))
4552 *r = wxPoint(wxRound(p.m_x), wxRound(p.m_y));
4553 else
4554 *r = wxPoint(INVALID_COORD, INVALID_COORD);
4555
4556 return true;
4557}
4558
4559void ChartCanvas::GetCanvasPixPoint(double x, double y, double &lat,
4560 double &lon) {
4561 // If the Current Chart is a raster chart, and the
4562 // requested x,y is within the boundaries of the chart,
4563 // and the VP is not rotated,
4564 // then use the embedded BSB chart georeferencing algorithm
4565 // for greater accuracy
4566 // Additionally, use chart embedded georef if the projection is TMERC
4567 // i.e. NOT MERCATOR and NOT POLYCONIC
4568
4569 // If for some reason the chart rejects the request by returning an error,
4570 // then fall back to Viewport Projection estimate from canvas parameters
4571 bool bUseVP = true;
4572
4573 if (!g_bopengl && m_singleChart &&
4574 (m_singleChart->GetChartFamily() == CHART_FAMILY_RASTER) &&
4575 (((fabs(GetVP().rotation) < .0001) && (fabs(GetVP().skew) < .0001)) ||
4576 ((m_singleChart->GetChartProjectionType() != PROJECTION_MERCATOR) &&
4577 (m_singleChart->GetChartProjectionType() !=
4578 PROJECTION_TRANSVERSE_MERCATOR) &&
4579 (m_singleChart->GetChartProjectionType() != PROJECTION_POLYCONIC))) &&
4580 (m_singleChart->GetChartProjectionType() == GetVP().m_projection_type) &&
4581 (m_singleChart->GetChartType() != CHART_TYPE_PLUGIN)) {
4582 ChartBaseBSB *Cur_BSB_Ch = dynamic_cast<ChartBaseBSB *>(m_singleChart);
4583
4584 // TODO maybe need iterative process to validate bInside
4585 // first pass is mercator, then check chart boundaries
4586
4587 if (Cur_BSB_Ch) {
4588 // This is a Raster chart....
4589 // If the VP is changing, the raster chart parameters may not yet be
4590 // setup So do that before accessing the chart's embedded
4591 // georeferencing
4592 Cur_BSB_Ch->SetVPRasterParms(GetVP());
4593
4594 double slat, slon;
4595 if (0 == Cur_BSB_Ch->vp_pix_to_latlong(GetVP(), x, y, &slat, &slon)) {
4596 lat = slat;
4597
4598 if (slon < -180.)
4599 slon += 360.;
4600 else if (slon > 180.)
4601 slon -= 360.;
4602
4603 lon = slon;
4604 bUseVP = false;
4605 }
4606 }
4607 }
4608
4609 // if needed, use the VPoint scaling estimator
4610 if (bUseVP) {
4611 GetVP().GetLLFromPix(wxPoint2DDouble(x, y), &lat, &lon);
4612 }
4613}
4614
4616 StopMovement();
4617 DoZoomCanvas(factor, false);
4618 extendedSectorLegs.clear();
4619}
4620
4621void ChartCanvas::ZoomCanvas(double factor, bool can_zoom_to_cursor,
4622 bool stoptimer) {
4623 m_bzooming_to_cursor = can_zoom_to_cursor && g_bEnableZoomToCursor;
4624
4625 if (g_bsmoothpanzoom) {
4626 if (StartTimedMovement(stoptimer)) {
4627 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4628 m_zoom_factor = factor;
4629 }
4630
4631 m_zoom_target = VPoint.chart_scale / factor;
4632 } else {
4633 if (m_modkeys == wxMOD_ALT) factor = pow(factor, .15);
4634
4635 DoZoomCanvas(factor, can_zoom_to_cursor);
4636 }
4637
4638 extendedSectorLegs.clear();
4639}
4640
4641void ChartCanvas::DoZoomCanvas(double factor, bool can_zoom_to_cursor) {
4642 // possible on startup
4643 if (!ChartData) return;
4644 if (!m_pCurrentStack) return;
4645
4646 /* TODO: queue the quilted loading code to a background thread
4647 so yield is never called from here, and also rendering is not delayed */
4648
4649 // Cannot allow Yield() re-entrancy here
4650 if (m_bzooming) return;
4651 m_bzooming = true;
4652
4653 double old_ppm = GetVP().view_scale_ppm;
4654
4655 // Capture current cursor position for zoom to cursor
4656 double zlat = m_cursor_lat;
4657 double zlon = m_cursor_lon;
4658
4659 double proposed_scale_onscreen =
4660 GetVP().chart_scale /
4661 factor; // GetCanvasScaleFactor() / ( GetVPScale() * factor );
4662 bool b_do_zoom = false;
4663
4664 if (factor > 1) {
4665 b_do_zoom = true;
4666
4667 // double zoom_factor = factor;
4668
4669 ChartBase *pc = NULL;
4670
4671 if (!VPoint.b_quilt) {
4672 pc = m_singleChart;
4673 } else {
4674 if (!m_disable_adjust_on_zoom) {
4675 int new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4676 if (new_db_index >= 0)
4677 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4678 else { // for whatever reason, no reference chart is known
4679 // Choose the smallest scale chart on the current stack
4680 // and then adjust for scale range
4681 int current_ref_stack_index = -1;
4682 if (m_pCurrentStack->nEntry) {
4683 int trial_index =
4684 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
4685 m_pQuilt->SetReferenceChart(trial_index);
4686 new_db_index = m_pQuilt->AdjustRefOnZoomIn(proposed_scale_onscreen);
4687 if (new_db_index >= 0)
4688 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4689 }
4690 }
4691
4692 if (m_pCurrentStack)
4693 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4694 new_db_index); // highlite the correct bar entry
4695 }
4696 }
4697
4698 if (pc) {
4699 // double target_scale_ppm = GetVPScale() * zoom_factor;
4700 // proposed_scale_onscreen = GetCanvasScaleFactor() /
4701 // target_scale_ppm;
4702
4703 // Query the chart to determine the appropriate zoom range
4704 double min_allowed_scale =
4705 g_maxzoomin; // Roughly, latitude dependent for mercator charts
4706
4707 if (proposed_scale_onscreen < min_allowed_scale) {
4708 if (min_allowed_scale == GetCanvasScaleFactor() / (GetVPScale())) {
4709 m_zoom_factor = 1; /* stop zooming */
4710 b_do_zoom = false;
4711 } else
4712 proposed_scale_onscreen = min_allowed_scale;
4713 }
4714
4715 } else {
4716 proposed_scale_onscreen = wxMax(proposed_scale_onscreen, g_maxzoomin);
4717 }
4718
4719 } else if (factor < 1) {
4720 b_do_zoom = true;
4721
4722 ChartBase *pc = NULL;
4723
4724 bool b_smallest = false;
4725
4726 if (!VPoint.b_quilt) { // not quilted
4727 pc = m_singleChart;
4728
4729 if (pc) {
4730 // If m_singleChart is not on the screen, unbound the zoomout
4731 LLBBox viewbox = VPoint.GetBBox();
4732 // BoundingBox chart_box;
4733 int current_index = ChartData->FinddbIndex(pc->GetFullPath());
4734 double max_allowed_scale;
4735
4736 max_allowed_scale = GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4737
4738 // We can allow essentially unbounded zoomout in single chart mode
4739 // if( ChartData->GetDBBoundingBox( current_index,
4740 // &chart_box ) &&
4741 // !viewbox.IntersectOut( chart_box ) )
4742 // // Clamp the minimum scale zoom-out to the value
4743 // specified by the chart max_allowed_scale =
4744 // wxMin(max_allowed_scale, 4.0 *
4745 // pc->GetNormalScaleMax(
4746 // GetCanvasScaleFactor(),
4747 // GetCanvasWidth() ) );
4748 if (proposed_scale_onscreen > max_allowed_scale) {
4749 m_zoom_factor = 1; /* stop zooming */
4750 proposed_scale_onscreen = max_allowed_scale;
4751 }
4752 }
4753
4754 } else {
4755 if (!m_disable_adjust_on_zoom) {
4756 int new_db_index =
4757 m_pQuilt->AdjustRefOnZoomOut(proposed_scale_onscreen);
4758 if (new_db_index >= 0)
4759 pc = ChartData->OpenChartFromDB(new_db_index, FULL_INIT);
4760
4761 if (m_pCurrentStack)
4762 m_pCurrentStack->SetCurrentEntryFromdbIndex(
4763 new_db_index); // highlite the correct bar entry
4764
4765 b_smallest = m_pQuilt->IsChartSmallestScale(new_db_index);
4766
4767 if (b_smallest || (0 == m_pQuilt->GetExtendedStackCount()))
4768 proposed_scale_onscreen =
4769 wxMin(proposed_scale_onscreen,
4770 GetCanvasScaleFactor() / m_absolute_min_scale_ppm);
4771 }
4772
4773 // set a minimum scale
4774 if ((GetCanvasScaleFactor() / proposed_scale_onscreen) <
4775 m_absolute_min_scale_ppm)
4776 proposed_scale_onscreen =
4777 GetCanvasScaleFactor() / m_absolute_min_scale_ppm;
4778 }
4779 }
4780 double new_scale =
4781 GetVPScale() * (GetVP().chart_scale / proposed_scale_onscreen);
4782
4783 if (b_do_zoom) {
4784 // Disable ZTC if lookahead is ON, and currently b_follow is active
4785 bool b_allow_ztc = true;
4786 if (m_bFollow && m_bLookAhead) b_allow_ztc = false;
4787 if (can_zoom_to_cursor && g_bEnableZoomToCursor && b_allow_ztc) {
4788 if (m_bLookAhead) {
4789 double brg, distance;
4790 ll_gc_ll_reverse(gLat, gLon, GetVP().clat, GetVP().clon, &brg,
4791 &distance);
4792 dir_to_shift = brg;
4793 meters_to_shift = distance * 1852;
4794 }
4795 // Arrange to combine the zoom and pan into one operation for smoother
4796 // appearance
4797 SetVPScale(new_scale, false); // adjust, but deferred refresh
4798 wxPoint r;
4799 GetCanvasPointPix(zlat, zlon, &r);
4800 // this will emit the Refresh()
4801 PanCanvas(r.x - mouse_x, r.y - mouse_y);
4802 } else {
4803 SetVPScale(new_scale);
4804 if (m_bFollow) DoCanvasUpdate();
4805 }
4806 }
4807
4808 m_bzooming = false;
4809}
4810
4811void ChartCanvas::SetAbsoluteMinScale(double min_scale) {
4812 double x_scale_ppm = GetCanvasScaleFactor() / min_scale;
4813 m_absolute_min_scale_ppm = wxMax(m_absolute_min_scale_ppm, x_scale_ppm);
4814}
4815
4816int rot;
4817void ChartCanvas::RotateCanvas(double dir) {
4818 // SetUpMode(NORTH_UP_MODE);
4819
4820 if (g_bsmoothpanzoom) {
4821 if (StartTimedMovement()) {
4822 m_mustmove += 150; /* for quick presses register as 200 ms duration */
4823 m_rotation_speed = dir * 60;
4824 }
4825 } else {
4826 double speed = dir * 10;
4827 if (m_modkeys == wxMOD_ALT) speed /= 20;
4828 DoRotateCanvas(VPoint.rotation + PI / 180 * speed);
4829 }
4830}
4831
4832void ChartCanvas::DoRotateCanvas(double rotation) {
4833 while (rotation < 0) rotation += 2 * PI;
4834 while (rotation > 2 * PI) rotation -= 2 * PI;
4835
4836 if (rotation == VPoint.rotation || std::isnan(rotation)) return;
4837
4838 SetVPRotation(rotation);
4839 parent_frame->UpdateRotationState(VPoint.rotation);
4840}
4841
4842void ChartCanvas::DoTiltCanvas(double tilt) {
4843 while (tilt < 0) tilt = 0;
4844 while (tilt > .95) tilt = .95;
4845
4846 if (tilt == VPoint.tilt || std::isnan(tilt)) return;
4847
4848 VPoint.tilt = tilt;
4849 Refresh(false);
4850}
4851
4852void ChartCanvas::TogglebFollow() {
4853 if (!m_bFollow)
4854 SetbFollow();
4855 else
4856 ClearbFollow();
4857}
4858
4859void ChartCanvas::ClearbFollow() {
4860 m_bFollow = false; // update the follow flag
4861
4862 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, false);
4863
4864 UpdateFollowButtonState();
4865
4866 DoCanvasUpdate();
4867 ReloadVP();
4868 parent_frame->SetChartUpdatePeriod();
4869}
4870
4871void ChartCanvas::SetbFollow() {
4872 // Is the OWNSHIP on-screen?
4873 // If not, then reset the OWNSHIP offset to 0 (center screen)
4874 if ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
4875 (fabs(m_OSoffsety) > VPoint.pix_height / 2)) {
4876 m_OSoffsetx = 0;
4877 m_OSoffsety = 0;
4878 }
4879
4880 // Apply the present b_follow offset values to ship position
4881 wxPoint2DDouble p;
4883 p.m_x += m_OSoffsetx;
4884 p.m_y -= m_OSoffsety;
4885
4886 // compute the target center screen lat/lon
4887 double dlat, dlon;
4888 GetCanvasPixPoint(p.m_x, p.m_y, dlat, dlon);
4889
4890 JumpToPosition(dlat, dlon, GetVPScale());
4891 m_bFollow = true;
4892
4893 parent_frame->SetMenubarItemState(ID_MENU_NAV_FOLLOW, true);
4894 UpdateFollowButtonState();
4895
4896 if (!g_bSmoothRecenter) {
4897 DoCanvasUpdate();
4898 ReloadVP();
4899 }
4900 parent_frame->SetChartUpdatePeriod();
4901}
4902
4903void ChartCanvas::UpdateFollowButtonState() {
4904 if (m_muiBar) {
4905 if (!m_bFollow)
4906 m_muiBar->SetFollowButtonState(0);
4907 else {
4908 if (m_bLookAhead)
4909 m_muiBar->SetFollowButtonState(2);
4910 else
4911 m_muiBar->SetFollowButtonState(1);
4912 }
4913 }
4914
4915#ifdef __ANDROID__
4916 if (!m_bFollow)
4917 androidSetFollowTool(0);
4918 else {
4919 if (m_bLookAhead)
4920 androidSetFollowTool(2);
4921 else
4922 androidSetFollowTool(1);
4923 }
4924#endif
4925
4926 // Look for plugin using API-121 or later
4927 // If found, make the follow state callback.
4928 if (g_pi_manager) {
4929 for (auto pic : *PluginLoader::GetInstance()->GetPlugInArray()) {
4930 if (pic->m_enabled && pic->m_init_state) {
4931 switch (pic->m_api_version) {
4932 case 121: {
4933 auto *ppi = dynamic_cast<opencpn_plugin_121 *>(pic->m_pplugin);
4934 if (ppi) ppi->UpdateFollowState(m_canvasIndex, m_bFollow);
4935 break;
4936 }
4937 default:
4938 break;
4939 }
4940 }
4941 }
4942 }
4943}
4944
4945void ChartCanvas::JumpToPosition(double lat, double lon, double scale_ppm) {
4946 if (g_bSmoothRecenter && !m_routeState) {
4947 if (StartSmoothJump(lat, lon, scale_ppm))
4948 return;
4949 else {
4950 // move closer to the target destination, and try again
4951 double gcDist, gcBearingEnd;
4952 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL,
4953 &gcBearingEnd);
4954 gcBearingEnd += 180;
4955 double lat_offset = cos(gcBearingEnd * PI / 180.) * 0.5 *
4956 GetCanvasWidth() / GetVPScale(); // meters
4957 double lon_offset =
4958 sin(gcBearingEnd * PI / 180.) * 0.5 * GetCanvasWidth() / GetVPScale();
4959 double new_lat = lat + (lat_offset / (1852 * 60));
4960 double new_lon = lon + (lon_offset / (1852 * 60));
4961 SetViewPoint(new_lat, new_lon);
4962 ReloadVP();
4963 StartSmoothJump(lat, lon, scale_ppm);
4964 return;
4965 }
4966 }
4967
4968 if (lon > 180.0) lon -= 360.0;
4969 m_vLat = lat;
4970 m_vLon = lon;
4971 StopMovement();
4972 m_bFollow = false;
4973
4974 if (!GetQuiltMode()) {
4975 double skew = 0;
4976 if (m_singleChart) skew = m_singleChart->GetChartSkew() * PI / 180.;
4977 SetViewPoint(lat, lon, scale_ppm, skew, GetVPRotation());
4978 } else {
4979 if (scale_ppm != GetVPScale()) {
4980 // XXX should be done in SetViewPoint
4981 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
4982 AdjustQuiltRefChart();
4983 }
4984 SetViewPoint(lat, lon, scale_ppm, 0, GetVPRotation());
4985 }
4986
4987 ReloadVP();
4988
4989 UpdateFollowButtonState();
4990
4991 // TODO
4992 // if( g_pi_manager ) {
4993 // g_pi_manager->SendViewPortToRequestingPlugIns( cc1->GetVP() );
4994 // }
4995}
4996
4997bool ChartCanvas::StartSmoothJump(double lat, double lon, double scale_ppm) {
4998 // Check distance to jump, in pixels at current chart scale
4999 // Modify smooth jump dynamics if jump distance is greater than 0.5x screen
5000 // width.
5001 double gcDist;
5002 Geodesic::GreatCircleDistBear(m_vLon, m_vLat, lon, lat, &gcDist, NULL, NULL);
5003 double distance_pixels = gcDist * GetVPScale();
5004 if (distance_pixels > 0.5 * GetCanvasWidth()) {
5005 // Jump is too far, try again
5006 return false;
5007 }
5008
5009 // Save where we're coming from
5010 m_startLat = m_vLat;
5011 m_startLon = m_vLon;
5012 m_startScale = GetVPScale(); // or VPoint.view_scale_ppm
5013
5014 // Save where we want to end up
5015 m_endLat = lat;
5016 m_endLon = (lon > 180.0) ? (lon - 360.0) : lon;
5017 m_endScale = scale_ppm;
5018
5019 // Setup timing
5020 m_animationDuration = 600; // ms
5021 m_animationStart = wxGetLocalTimeMillis();
5022
5023 // Stop any previous movement, ensure no conflicts
5024 StopMovement();
5025 m_bFollow = false;
5026
5027 // Start the timer with ~60 FPS (16 ms). Tweak as needed.
5028 m_easeTimer.Start(16, wxTIMER_CONTINUOUS);
5029 m_animationActive = true;
5030
5031 return true;
5032}
5033
5034void ChartCanvas::OnJumpEaseTimer(wxTimerEvent &event) {
5035 // Calculate time fraction from 0..1
5036 wxLongLong now = wxGetLocalTimeMillis();
5037 double elapsed = (now - m_animationStart).ToDouble();
5038 double t = elapsed / m_animationDuration.ToDouble();
5039 if (t > 1.0) t = 1.0;
5040
5041 // Ease function for smoother movement
5042 double e = easeOutCubic(t);
5043
5044 // Interpolate lat/lon/scale
5045 double curLat = m_startLat + (m_endLat - m_startLat) * e;
5046 double curLon = m_startLon + (m_endLon - m_startLon) * e;
5047 double curScale = m_startScale + (m_endScale - m_startScale) * e;
5048
5049 // Update viewpoint
5050 // (Essentially the same code used in JumpToPosition, but skipping the "snap"
5051 // portion)
5052 SetViewPoint(curLat, curLon, curScale, 0.0, GetVPRotation());
5053 ReloadVP();
5054
5055 // If we reached the end, stop the timer and finalize
5056 if (t >= 1.0) {
5057 m_easeTimer.Stop();
5058 m_animationActive = false;
5059 UpdateFollowButtonState();
5060 ZoomCanvasSimple(1.0001);
5061 DoCanvasUpdate();
5062 ReloadVP();
5063 }
5064}
5065
5066bool ChartCanvas::PanCanvas(double dx, double dy) {
5067 if (!ChartData) return false;
5068 extendedSectorLegs.clear();
5069
5070 double dlat, dlon;
5071 wxPoint2DDouble p(VPoint.pix_width / 2.0, VPoint.pix_height / 2.0);
5072
5073 int iters = 0;
5074 for (;;) {
5075 GetCanvasPixPoint(p.m_x + trunc(dx), p.m_y + trunc(dy), dlat, dlon);
5076
5077 if (iters++ > 5) return false;
5078 if (!std::isnan(dlat)) break;
5079
5080 dx *= .5, dy *= .5;
5081 if (fabs(dx) < 1 && fabs(dy) < 1) return false;
5082 }
5083
5084 // avoid overshooting the poles
5085 if (dlat > 90)
5086 dlat = 90;
5087 else if (dlat < -90)
5088 dlat = -90;
5089
5090 if (dlon > 360.) dlon -= 360.;
5091 if (dlon < -360.) dlon += 360.;
5092
5093 // This should not really be necessary, but round-trip georef on some
5094 // charts is not perfect, So we can get creep on repeated unidimensional
5095 // pans, and corrupt chart cacheing.......
5096
5097 // But this only works on north-up projections
5098 // TODO: can we remove this now?
5099 // if( ( ( fabs( GetVP().skew ) < .001 ) ) && ( fabs( GetVP().rotation ) <
5100 // .001 ) ) {
5101 //
5102 // if( dx == 0 ) dlon = clon;
5103 // if( dy == 0 ) dlat = clat;
5104 // }
5105
5106 int cur_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5107
5108 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew, VPoint.rotation);
5109
5110 if (VPoint.b_quilt) {
5111 int new_ref_dbIndex = m_pQuilt->GetRefChartdbIndex();
5112 if ((new_ref_dbIndex != cur_ref_dbIndex) && (new_ref_dbIndex != -1)) {
5113 // Tweak the scale slightly for a new ref chart
5114 ChartBase *pc = ChartData->OpenChartFromDB(new_ref_dbIndex, FULL_INIT);
5115 if (pc) {
5116 double tweak_scale_ppm =
5117 pc->GetNearestPreferredScalePPM(VPoint.view_scale_ppm);
5118 SetVPScale(tweak_scale_ppm);
5119 }
5120 }
5121
5122 if (new_ref_dbIndex == -1) {
5123#pragma GCC diagnostic push
5124#pragma GCC diagnostic ignored "-Warray-bounds"
5125 // The compiler sees a -1 index being used. Does not happen, though.
5126
5127 // for whatever reason, no reference chart is known
5128 // Probably panned out of the coverage region
5129 // If any charts are anywhere on-screen, choose the smallest
5130 // scale chart on the screen to be a new reference chart.
5131 int trial_index = -1;
5132 if (m_pCurrentStack->nEntry) {
5133 int trial_index =
5134 m_pCurrentStack->GetDBIndex(m_pCurrentStack->nEntry - 1);
5135 }
5136
5137 if (trial_index < 0) {
5138 auto full_screen_array = GetQuiltFullScreendbIndexArray();
5139 if (full_screen_array.size())
5140 trial_index = full_screen_array[full_screen_array.size() - 1];
5141 }
5142
5143 if (trial_index >= 0) {
5144 m_pQuilt->SetReferenceChart(trial_index);
5145 SetViewPoint(dlat, dlon, VPoint.view_scale_ppm, VPoint.skew,
5146 VPoint.rotation);
5147 ReloadVP();
5148 }
5149#pragma GCC diagnostic pop
5150 }
5151 }
5152
5153 // Turn off bFollow only if the ownship has left the screen
5154 if (m_bFollow) {
5155 double offx, offy;
5156 toSM(dlat, dlon, gLat, gLon, &offx, &offy);
5157
5158 double offset_angle = atan2(offy, offx);
5159 double offset_distance = sqrt((offy * offy) + (offx * offx));
5160 double chart_angle = GetVPRotation();
5161 double target_angle = chart_angle - offset_angle;
5162 double d_east_mod = offset_distance * cos(target_angle);
5163 double d_north_mod = offset_distance * sin(target_angle);
5164
5165 m_OSoffsetx = d_east_mod * VPoint.view_scale_ppm;
5166 m_OSoffsety = -d_north_mod * VPoint.view_scale_ppm;
5167
5168 if (m_bFollow && ((fabs(m_OSoffsetx) > VPoint.pix_width / 2) ||
5169 (fabs(m_OSoffsety) > VPoint.pix_height / 2))) {
5170 m_bFollow = false; // update the follow flag
5171 UpdateFollowButtonState();
5172 }
5173 }
5174
5175 Refresh(false);
5176
5177 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
5178
5179 return true;
5180}
5181
5182bool ChartCanvas::IsOwnshipOnScreen() {
5183 wxPoint r;
5185 if (((r.x > 0) && r.x < GetCanvasWidth()) &&
5186 ((r.y > 0) && r.y < GetCanvasHeight()))
5187 return true;
5188 else
5189 return false;
5190}
5191
5192void ChartCanvas::ReloadVP(bool b_adjust) {
5193 if (g_brightness_init) SetScreenBrightness(g_nbrightness);
5194
5195 LoadVP(VPoint, b_adjust);
5196}
5197
5198void ChartCanvas::LoadVP(ViewPort &vp, bool b_adjust) {
5199#ifdef ocpnUSE_GL
5200 if (g_bopengl && m_glcc) {
5201 m_glcc->Invalidate();
5202 if (m_glcc->GetSize() != GetSize()) {
5203 m_glcc->SetSize(GetSize());
5204 }
5205 } else
5206#endif
5207 {
5208 m_cache_vp.Invalidate();
5209 m_bm_cache_vp.Invalidate();
5210 }
5211
5212 VPoint.Invalidate();
5213
5214 if (m_pQuilt) m_pQuilt->Invalidate();
5215
5216 // Make sure that the Selected Group is sensible...
5217 // if( m_groupIndex > (int) g_pGroupArray->GetCount() )
5218 // m_groupIndex = 0;
5219 // if( !CheckGroup( m_groupIndex ) )
5220 // m_groupIndex = 0;
5221
5222 SetViewPoint(vp.clat, vp.clon, vp.view_scale_ppm, vp.skew, vp.rotation,
5223 vp.m_projection_type, b_adjust);
5224}
5225
5226void ChartCanvas::SetQuiltRefChart(int dbIndex) {
5227 m_pQuilt->SetReferenceChart(dbIndex);
5228 VPoint.Invalidate();
5229 m_pQuilt->Invalidate();
5230}
5231
5232double ChartCanvas::GetBestStartScale(int dbi_hint, const ViewPort &vp) {
5233 if (m_pQuilt)
5234 return m_pQuilt->GetBestStartScale(dbi_hint, vp);
5235 else
5236 return vp.view_scale_ppm;
5237}
5238
5239// Verify and adjust the current reference chart,
5240// so that it will not lead to excessive overzoom or underzoom onscreen
5241int ChartCanvas::AdjustQuiltRefChart() {
5242 int ret = -1;
5243 if (m_pQuilt) {
5244 wxASSERT(ChartData);
5245 ChartBase *pc =
5246 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5247 if (pc) {
5248 double min_ref_scale =
5249 pc->GetNormalScaleMin(m_canvas_scale_factor, false);
5250 double max_ref_scale =
5251 pc->GetNormalScaleMax(m_canvas_scale_factor, m_canvas_width);
5252
5253 if (VPoint.chart_scale < min_ref_scale) {
5254 ret = m_pQuilt->AdjustRefOnZoomIn(VPoint.chart_scale);
5255 } else if (VPoint.chart_scale > max_ref_scale * 64) {
5256 ret = m_pQuilt->AdjustRefOnZoomOut(VPoint.chart_scale);
5257 } else {
5258 bool brender_ok = IsChartLargeEnoughToRender(pc, VPoint);
5259
5260 if (!brender_ok) {
5261 int target_stack_index = wxNOT_FOUND;
5262 int il = 0;
5263 for (auto index : m_pQuilt->GetExtendedStackIndexArray()) {
5264 if (index == m_pQuilt->GetRefChartdbIndex()) {
5265 target_stack_index = il;
5266 break;
5267 }
5268 il++;
5269 }
5270 if (wxNOT_FOUND == target_stack_index) // should never happen...
5271 target_stack_index = 0;
5272
5273 int ref_family = pc->GetChartFamily();
5274 int extended_array_count =
5275 m_pQuilt->GetExtendedStackIndexArray().size();
5276 while ((!brender_ok) &&
5277 ((int)target_stack_index < (extended_array_count - 1))) {
5278 target_stack_index++;
5279 int test_db_index =
5280 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5281
5282 if ((ref_family == ChartData->GetDBChartFamily(test_db_index)) &&
5283 IsChartQuiltableRef(test_db_index)) {
5284 // open the target, and check the min_scale
5285 ChartBase *ptest_chart =
5286 ChartData->OpenChartFromDB(test_db_index, FULL_INIT);
5287 if (ptest_chart) {
5288 brender_ok = IsChartLargeEnoughToRender(ptest_chart, VPoint);
5289 }
5290 }
5291 }
5292
5293 if (brender_ok) { // found a better reference chart
5294 int new_db_index =
5295 m_pQuilt->GetExtendedStackIndexArray()[target_stack_index];
5296 if ((ref_family == ChartData->GetDBChartFamily(new_db_index)) &&
5297 IsChartQuiltableRef(new_db_index)) {
5298 m_pQuilt->SetReferenceChart(new_db_index);
5299 ret = new_db_index;
5300 } else
5301 ret = m_pQuilt->GetRefChartdbIndex();
5302 } else
5303 ret = m_pQuilt->GetRefChartdbIndex();
5304
5305 } else
5306 ret = m_pQuilt->GetRefChartdbIndex();
5307 }
5308 } else
5309 ret = -1;
5310 }
5311
5312 return ret;
5313}
5314
5315void ChartCanvas::UpdateCanvasOnGroupChange() {
5316 delete m_pCurrentStack;
5317 m_pCurrentStack = new ChartStack;
5318 wxASSERT(ChartData);
5319 ChartData->BuildChartStack(m_pCurrentStack, VPoint.clat, VPoint.clon,
5320 m_groupIndex);
5321
5322 if (m_pQuilt) {
5323 m_pQuilt->Compose(VPoint);
5324 SetFocus();
5325 }
5326}
5327
5328bool ChartCanvas::SetViewPointByCorners(double latSW, double lonSW,
5329 double latNE, double lonNE) {
5330 // Center Point
5331 double latc = (latSW + latNE) / 2.0;
5332 double lonc = (lonSW + lonNE) / 2.0;
5333
5334 // Get scale in ppm (latitude)
5335 double ne_easting, ne_northing;
5336 toSM(latNE, lonNE, latc, lonc, &ne_easting, &ne_northing);
5337
5338 double sw_easting, sw_northing;
5339 toSM(latSW, lonSW, latc, lonc, &sw_easting, &sw_northing);
5340
5341 double scale_ppm = VPoint.pix_height / fabs(ne_northing - sw_northing);
5342
5343 return SetViewPoint(latc, lonc, scale_ppm, VPoint.skew, VPoint.rotation);
5344}
5345
5346bool ChartCanvas::SetVPScale(double scale, bool refresh) {
5347 return SetViewPoint(VPoint.clat, VPoint.clon, scale, VPoint.skew,
5348 VPoint.rotation, VPoint.m_projection_type, true, refresh);
5349}
5350
5351bool ChartCanvas::SetVPProjection(int projection) {
5352 if (!g_bopengl) // alternative projections require opengl
5353 return false;
5354
5355 // the view scale varies depending on geographic location and projection
5356 // rescale to keep the relative scale on the screen the same
5357 double prev_true_scale_ppm = m_true_scale_ppm;
5358 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5359 VPoint.skew, VPoint.rotation, projection) &&
5360 SetVPScale(wxMax(
5361 VPoint.view_scale_ppm * prev_true_scale_ppm / m_true_scale_ppm,
5362 m_absolute_min_scale_ppm));
5363}
5364
5365bool ChartCanvas::SetViewPoint(double lat, double lon) {
5366 return SetViewPoint(lat, lon, VPoint.view_scale_ppm, VPoint.skew,
5367 VPoint.rotation);
5368}
5369
5370bool ChartCanvas::SetVPRotation(double angle) {
5371 return SetViewPoint(VPoint.clat, VPoint.clon, VPoint.view_scale_ppm,
5372 VPoint.skew, angle);
5373}
5374bool ChartCanvas::SetViewPoint(double lat, double lon, double scale_ppm,
5375 double skew, double rotation, int projection,
5376 bool b_adjust, bool b_refresh) {
5377 bool b_ret = false;
5378 if (skew > PI) /* so our difference tests work, put in range of +-Pi */
5379 skew -= 2 * PI;
5380 // Any sensible change?
5381 if (VPoint.IsValid()) {
5382 if ((fabs(VPoint.view_scale_ppm - scale_ppm) / scale_ppm < 1e-5) &&
5383 (fabs(VPoint.skew - skew) < 1e-9) &&
5384 (fabs(VPoint.rotation - rotation) < 1e-9) &&
5385 (fabs(VPoint.clat - lat) < 1e-9) && (fabs(VPoint.clon - lon) < 1e-9) &&
5386 (VPoint.m_projection_type == projection ||
5387 projection == PROJECTION_UNKNOWN))
5388 return false;
5389 }
5390 if (VPoint.m_projection_type != projection)
5391 VPoint.InvalidateTransformCache(); // invalidate
5392
5393 // Take a local copy of the last viewport
5394 ViewPort last_vp = VPoint;
5395
5396 VPoint.skew = skew;
5397 VPoint.clat = lat;
5398 VPoint.clon = lon;
5399 VPoint.rotation = rotation;
5400 VPoint.view_scale_ppm = scale_ppm;
5401 if (projection != PROJECTION_UNKNOWN)
5402 VPoint.SetProjectionType(projection);
5403 else if (VPoint.m_projection_type == PROJECTION_UNKNOWN)
5404 VPoint.SetProjectionType(PROJECTION_MERCATOR);
5405
5406 // don't allow latitude above 88 for mercator (90 is infinity)
5407 if (VPoint.m_projection_type == PROJECTION_MERCATOR ||
5408 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR) {
5409 if (VPoint.clat > 89.5)
5410 VPoint.clat = 89.5;
5411 else if (VPoint.clat < -89.5)
5412 VPoint.clat = -89.5;
5413 }
5414
5415 // don't zoom out too far for transverse mercator polyconic until we resolve
5416 // issues
5417 if (VPoint.m_projection_type == PROJECTION_POLYCONIC ||
5418 VPoint.m_projection_type == PROJECTION_TRANSVERSE_MERCATOR)
5419 VPoint.view_scale_ppm = wxMax(VPoint.view_scale_ppm, 2e-4);
5420
5421 // SetVPRotation(rotation);
5422
5423 if (!g_bopengl) // tilt is not possible without opengl
5424 VPoint.tilt = 0;
5425
5426 if ((VPoint.pix_width <= 0) ||
5427 (VPoint.pix_height <= 0)) // Canvas parameters not yet set
5428 return false;
5429
5430 bool bwasValid = VPoint.IsValid();
5431 VPoint.Validate(); // Mark this ViewPoint as OK
5432
5433 // Has the Viewport scale changed? If so, invalidate the vp
5434 if (last_vp.view_scale_ppm != scale_ppm) {
5435 m_cache_vp.Invalidate();
5436 InvalidateGL();
5437 }
5438
5439 // A preliminary value, may be tweaked below
5440 VPoint.chart_scale = m_canvas_scale_factor / (scale_ppm);
5441
5442 // recompute cursor position
5443 // and send to interested plugins if the mouse is actually in this window
5444 int mouseX = mouse_x;
5445 int mouseY = mouse_y;
5446 if ((mouseX > 0) && (mouseX < VPoint.pix_width) && (mouseY > 0) &&
5447 (mouseY < VPoint.pix_height)) {
5448 double lat, lon;
5449 GetCanvasPixPoint(mouseX, mouseY, lat, lon);
5450 m_cursor_lat = lat;
5451 m_cursor_lon = lon;
5452 SendCursorLatLonToAllPlugIns(lat, lon);
5453 }
5454
5455 if (!VPoint.b_quilt && m_singleChart) {
5456 VPoint.SetBoxes();
5457
5458 // Allow the chart to adjust the new ViewPort for performance optimization
5459 // This will normally be only a fractional (i.e.sub-pixel) adjustment...
5460 if (b_adjust) m_singleChart->AdjustVP(last_vp, VPoint);
5461
5462 // If there is a sensible change in the chart render, refresh the whole
5463 // screen
5464 if ((!m_cache_vp.IsValid()) ||
5465 (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm)) {
5466 Refresh(false);
5467 b_ret = true;
5468 } else {
5469 wxPoint cp_last, cp_this;
5470 GetCanvasPointPix(m_cache_vp.clat, m_cache_vp.clon, &cp_last);
5471 GetCanvasPointPix(VPoint.clat, VPoint.clon, &cp_this);
5472
5473 if (cp_last != cp_this) {
5474 Refresh(false);
5475 b_ret = true;
5476 }
5477 }
5478 // Create the stack
5479 if (m_pCurrentStack) {
5480 assert(ChartData != 0);
5481 int current_db_index;
5482 current_db_index =
5483 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5484
5485 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, current_db_index,
5486 m_groupIndex);
5487 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5488 }
5489
5490 if (!g_bopengl) VPoint.b_MercatorProjectionOverride = false;
5491 }
5492
5493 // Handle the quilted case
5494 if (VPoint.b_quilt) {
5495 VPoint.SetBoxes();
5496
5497 if (last_vp.view_scale_ppm != scale_ppm)
5498 m_pQuilt->InvalidateAllQuiltPatchs();
5499
5500 // Create the quilt
5501 if (ChartData /*&& ChartData->IsValid()*/) {
5502 if (!m_pCurrentStack) return false;
5503
5504 int current_db_index;
5505 current_db_index =
5506 m_pCurrentStack->GetCurrentEntrydbIndex(); // capture the current
5507
5508 ChartData->BuildChartStack(m_pCurrentStack, lat, lon, m_groupIndex);
5509 m_pCurrentStack->SetCurrentEntryFromdbIndex(current_db_index);
5510
5511 // Check to see if the current quilt reference chart is in the new stack
5512 int current_ref_stack_index = -1;
5513 for (int i = 0; i < m_pCurrentStack->nEntry; i++) {
5514 if (m_pQuilt->GetRefChartdbIndex() == m_pCurrentStack->GetDBIndex(i))
5515 current_ref_stack_index = i;
5516 }
5517
5518 if (g_bFullScreenQuilt) {
5519 current_ref_stack_index = m_pQuilt->GetRefChartdbIndex();
5520 }
5521
5522 // We might need a new Reference Chart
5523 bool b_needNewRef = false;
5524
5525 // If the new stack does not contain the current ref chart....
5526 if ((-1 == current_ref_stack_index) &&
5527 (m_pQuilt->GetRefChartdbIndex() >= 0))
5528 b_needNewRef = true;
5529
5530 // Would the current Ref Chart be excessively underzoomed?
5531 // We need to check this here to be sure, since we cannot know where the
5532 // reference chart was assigned. For instance, the reference chart may
5533 // have been selected from the config file, or from a long jump with a
5534 // chart family switch implicit. Anyway, we check to be sure....
5535 bool renderable = true;
5536 ChartBase *referenceChart =
5537 ChartData->OpenChartFromDB(m_pQuilt->GetRefChartdbIndex(), FULL_INIT);
5538 if (referenceChart) {
5539 double chartMaxScale = referenceChart->GetNormalScaleMax(
5540 GetCanvasScaleFactor(), GetCanvasWidth());
5541 renderable = chartMaxScale * 64 >= VPoint.chart_scale;
5542 }
5543 if (!renderable) b_needNewRef = true;
5544
5545 // Need new refchart?
5546 if (b_needNewRef && !m_disable_adjust_on_zoom) {
5547 const ChartTableEntry &cte_ref =
5548 ChartData->GetChartTableEntry(m_pQuilt->GetRefChartdbIndex());
5549 int target_scale = cte_ref.GetScale();
5550 int target_type = cte_ref.GetChartType();
5551 int candidate_stack_index;
5552
5553 // reset the ref chart in a way that does not lead to excessive
5554 // underzoom, for performance reasons Try to find a chart that is the
5555 // same type, and has a scale of just smaller than the current ref
5556 // chart
5557
5558 candidate_stack_index = 0;
5559 while (candidate_stack_index <= m_pCurrentStack->nEntry - 1) {
5560 const ChartTableEntry &cte_candidate = ChartData->GetChartTableEntry(
5561 m_pCurrentStack->GetDBIndex(candidate_stack_index));
5562 int candidate_scale = cte_candidate.GetScale();
5563 int candidate_type = cte_candidate.GetChartType();
5564
5565 if ((candidate_scale >= target_scale) &&
5566 (candidate_type == target_type)) {
5567 bool renderable = true;
5568 ChartBase *tentative_referenceChart = ChartData->OpenChartFromDB(
5569 m_pCurrentStack->GetDBIndex(candidate_stack_index), FULL_INIT);
5570 if (tentative_referenceChart) {
5571 double chartMaxScale =
5572 tentative_referenceChart->GetNormalScaleMax(
5573 GetCanvasScaleFactor(), GetCanvasWidth());
5574 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5575 }
5576
5577 if (renderable) break;
5578 }
5579
5580 candidate_stack_index++;
5581 }
5582
5583 // If that did not work, look for a chart of just larger scale and
5584 // same type
5585 if (candidate_stack_index >= m_pCurrentStack->nEntry) {
5586 candidate_stack_index = m_pCurrentStack->nEntry - 1;
5587 while (candidate_stack_index >= 0) {
5588 int idx = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5589 if (idx >= 0) {
5590 const ChartTableEntry &cte_candidate =
5591 ChartData->GetChartTableEntry(idx);
5592 int candidate_scale = cte_candidate.GetScale();
5593 int candidate_type = cte_candidate.GetChartType();
5594
5595 if ((candidate_scale <= target_scale) &&
5596 (candidate_type == target_type))
5597 break;
5598 }
5599 candidate_stack_index--;
5600 }
5601 }
5602
5603 // and if that did not work, chose stack entry 0
5604 if ((candidate_stack_index >= m_pCurrentStack->nEntry) ||
5605 (candidate_stack_index < 0))
5606 candidate_stack_index = 0;
5607
5608 int new_ref_index = m_pCurrentStack->GetDBIndex(candidate_stack_index);
5609
5610 m_pQuilt->SetReferenceChart(new_ref_index); // maybe???
5611 }
5612
5613 if (!g_bopengl) {
5614 // Preset the VPoint projection type to match what the quilt projection
5615 // type will be
5616 int ref_db_index = m_pQuilt->GetRefChartdbIndex(), proj;
5617
5618 // Always keep the default Mercator projection if the reference chart is
5619 // not in the PatchList or the scale is too small for it to render.
5620
5621 bool renderable = true;
5622 ChartBase *referenceChart =
5623 ChartData->OpenChartFromDB(ref_db_index, FULL_INIT);
5624 if (referenceChart) {
5625 double chartMaxScale = referenceChart->GetNormalScaleMax(
5626 GetCanvasScaleFactor(), GetCanvasWidth());
5627 renderable = chartMaxScale * 1.5 > VPoint.chart_scale;
5628 proj = ChartData->GetDBChartProj(ref_db_index);
5629 } else
5630 proj = PROJECTION_MERCATOR;
5631
5632 VPoint.b_MercatorProjectionOverride =
5633 (m_pQuilt->GetnCharts() == 0 || !renderable);
5634
5635 if (VPoint.b_MercatorProjectionOverride) proj = PROJECTION_MERCATOR;
5636
5637 VPoint.SetProjectionType(proj);
5638 }
5639
5640 // If this quilt will be a perceptible delta from the existing quilt,
5641 // then refresh the entire screen
5642 if (m_pQuilt->IsQuiltDelta(VPoint)) {
5643 // Allow the quilt to adjust the new ViewPort for performance
5644 // optimization This will normally be only a fractional (i.e.
5645 // sub-pixel) adjustment...
5646 if (b_adjust) {
5647 m_pQuilt->AdjustQuiltVP(last_vp, VPoint);
5648 }
5649
5650 // ChartData->ClearCacheInUseFlags();
5651 // unsigned long hash1 = m_pQuilt->GetXStackHash();
5652
5653 // wxStopWatch sw;
5654
5655#ifdef __ANDROID__
5656 // This is an optimization for panning on touch screen systems.
5657 // The quilt composition is deferred until the OnPaint() message gets
5658 // finally removed and processed from the message queue.
5659 // Takes advantage of the fact that touch-screen pan gestures are
5660 // usually short in distance,
5661 // so not requiring a full quilt rebuild until the pan gesture is
5662 // complete.
5663 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5664 // qDebug() << "Force compose";
5665 m_pQuilt->Compose(VPoint);
5666 } else {
5667 m_pQuilt->Invalidate();
5668 }
5669#else
5670 m_pQuilt->Compose(VPoint);
5671#endif
5672
5673 // printf("comp time %ld\n", sw.Time());
5674
5675 // If the extended chart stack has changed, invalidate any cached
5676 // render bitmap
5677 // if(m_pQuilt->GetXStackHash() != hash1) {
5678 // m_bm_cache_vp.Invalidate();
5679 // InvalidateGL();
5680 // }
5681
5682 ChartData->PurgeCacheUnusedCharts(0.7);
5683
5684 if (b_refresh) Refresh(false);
5685
5686 b_ret = true;
5687 }
5688 }
5689
5690 VPoint.skew = 0.; // Quilting supports 0 Skew
5691 } else if (!g_bopengl) {
5692 OcpnProjType projection = PROJECTION_UNKNOWN;
5693 if (m_singleChart) // viewport projection must match chart projection
5694 // without opengl
5695 projection = m_singleChart->GetChartProjectionType();
5696 if (projection == PROJECTION_UNKNOWN) projection = PROJECTION_MERCATOR;
5697 VPoint.SetProjectionType(projection);
5698 }
5699
5700 // Has the Viewport projection changed? If so, invalidate the vp
5701 if (last_vp.m_projection_type != VPoint.m_projection_type) {
5702 m_cache_vp.Invalidate();
5703 InvalidateGL();
5704 }
5705
5706 UpdateCanvasControlBar(); // Refresh the Piano
5707
5708 VPoint.chart_scale = 1.0; // fallback default value
5709
5710 if (VPoint.GetBBox().GetValid()) {
5711 // Update the viewpoint reference scale
5712 if (m_singleChart)
5713 VPoint.ref_scale = m_singleChart->GetNativeScale();
5714 else {
5715#ifdef __ANDROID__
5716 // This is an optimization for panning on touch screen systems.
5717 // See above.
5718 // Quilt might not be fully composed at this point, so for cm93
5719 // the reference scale may not be known.
5720 // In this case, do not update the VP ref_scale.
5721 if ((last_vp.view_scale_ppm != scale_ppm) || !bwasValid) {
5722 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5723 }
5724#else
5725 VPoint.ref_scale = m_pQuilt->GetRefNativeScale();
5726#endif
5727 }
5728
5729 // Calculate the on-screen displayed actual scale
5730 // by a simple traverse northward from the center point
5731 // of roughly one eighth of the canvas height
5732 wxPoint2DDouble r, r1; // Screen coordinates in physical pixels.
5733
5734 double delta_check =
5735 (VPoint.pix_height / VPoint.view_scale_ppm) / (1852. * 60);
5736 delta_check /= 8.;
5737
5738 double check_point = wxMin(89., VPoint.clat);
5739
5740 while ((delta_check + check_point) > 90.) delta_check /= 2.;
5741
5742 double rhumbDist;
5743 DistanceBearingMercator(check_point, VPoint.clon, check_point + delta_check,
5744 VPoint.clon, 0, &rhumbDist);
5745
5746 GetDoubleCanvasPointPix(check_point, VPoint.clon, &r1);
5747 GetDoubleCanvasPointPix(check_point + delta_check, VPoint.clon, &r);
5748 // Calculate the distance between r1 and r in physical pixels.
5749 double delta_p = sqrt(((r1.m_y - r.m_y) * (r1.m_y - r.m_y)) +
5750 ((r1.m_x - r.m_x) * (r1.m_x - r.m_x)));
5751
5752 m_true_scale_ppm = delta_p / (rhumbDist * 1852);
5753
5754 // A fall back in case of very high zoom-out, giving delta_y == 0
5755 // which can probably only happen with vector charts
5756 if (0.0 == m_true_scale_ppm) m_true_scale_ppm = scale_ppm;
5757
5758 // Another fallback, for highly zoomed out charts
5759 // This adjustment makes the displayed TrueScale correspond to the
5760 // same algorithm used to calculate the chart zoom-out limit for
5761 // ChartDummy.
5762 if (scale_ppm < 1e-4) m_true_scale_ppm = scale_ppm;
5763
5764 if (m_true_scale_ppm)
5765 VPoint.chart_scale = m_canvas_scale_factor / (m_true_scale_ppm);
5766 else
5767 VPoint.chart_scale = 1.0;
5768
5769 // Create a nice renderable string
5770 double round_factor = 1000.;
5771 if (VPoint.chart_scale <= 1000.)
5772 round_factor = 10.;
5773 else if (VPoint.chart_scale <= 10000.)
5774 round_factor = 100.;
5775 else if (VPoint.chart_scale <= 100000.)
5776 round_factor = 1000.;
5777
5778 // Fixme: Workaround the wrongly calculated scale on Retina displays (#3117)
5779 double retina_coef = 1;
5780#ifdef ocpnUSE_GL
5781#ifdef __WXOSX__
5782 if (g_bopengl) {
5783 retina_coef = GetContentScaleFactor();
5784 }
5785#endif
5786#endif
5787
5788 // The chart scale denominator (e.g., 50000 if the scale is 1:50000),
5789 // rounded to the nearest 10, 100 or 1000.
5790 //
5791 // @todo: on MacOS there is 2x ratio between VPoint.chart_scale and
5792 // true_scale_display. That does not make sense. The chart scale should be
5793 // the same as the true scale within the limits of the rounding factor.
5794 double true_scale_display =
5795 wxRound(VPoint.chart_scale / round_factor) * round_factor * retina_coef;
5796 wxString text;
5797
5798 m_displayed_scale_factor = VPoint.ref_scale / VPoint.chart_scale;
5799
5800 if (m_displayed_scale_factor > 10.0)
5801 text.Printf("%s %4.0f (%1.0fx)", _("Scale"), true_scale_display,
5802 m_displayed_scale_factor);
5803 else if (m_displayed_scale_factor > 1.0)
5804 text.Printf("%s %4.0f (%1.1fx)", _("Scale"), true_scale_display,
5805 m_displayed_scale_factor);
5806 else if (m_displayed_scale_factor > 0.1) {
5807 double sfr = wxRound(m_displayed_scale_factor * 10.) / 10.;
5808 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5809 } else if (m_displayed_scale_factor > 0.01) {
5810 double sfr = wxRound(m_displayed_scale_factor * 100.) / 100.;
5811 text.Printf("%s %4.0f (%1.2fx)", _("Scale"), true_scale_display, sfr);
5812 } else {
5813 text.Printf(
5814 "%s %4.0f (---)", _("Scale"),
5815 true_scale_display); // Generally, no chart, so no chart scale factor
5816 }
5817
5818 m_scaleValue = true_scale_display;
5819 m_scaleText = text;
5820 if (m_muiBar) m_muiBar->UpdateDynamicValues();
5821
5822 if (m_bShowScaleInStatusBar && parent_frame->GetStatusBar() &&
5823 (parent_frame->GetStatusBar()->GetFieldsCount() > STAT_FIELD_SCALE)) {
5824 // Check to see if the text will fit in the StatusBar field...
5825 bool b_noshow = false;
5826 {
5827 int w = 0;
5828 int h;
5829 wxClientDC dc(parent_frame->GetStatusBar());
5830 if (dc.IsOk()) {
5831 wxFont *templateFont = FontMgr::Get().GetFont(_("StatusBar"), 0);
5832 dc.SetFont(*templateFont);
5833 dc.GetTextExtent(text, &w, &h);
5834
5835 // If text is too long for the allocated field, try to reduce the text
5836 // string a bit.
5837 wxRect rect;
5838 parent_frame->GetStatusBar()->GetFieldRect(STAT_FIELD_SCALE, rect);
5839 if (w && w > rect.width) {
5840 text.Printf("%s (%1.1fx)", _("Scale"), m_displayed_scale_factor);
5841 }
5842
5843 // Test again...if too big still, then give it up.
5844 dc.GetTextExtent(text, &w, &h);
5845
5846 if (w && w > rect.width) {
5847 b_noshow = true;
5848 }
5849 }
5850 }
5851
5852 if (!b_noshow) parent_frame->SetStatusText(text, STAT_FIELD_SCALE);
5853 }
5854 }
5855
5856 // Maintain member vLat/vLon
5857 m_vLat = VPoint.clat;
5858 m_vLon = VPoint.clon;
5859
5860 return b_ret;
5861}
5862
5863// Static Icon definitions for some symbols requiring
5864// scaling/rotation/translation Very specific wxDC draw commands are
5865// necessary to properly render these icons...See the code in
5866// ShipDraw()
5867
5868// This icon was adapted and scaled from the S52 Presentation Library
5869// version 3_03.
5870// Symbol VECGND02
5871
5872static int s_png_pred_icon[] = {-10, -10, -10, 10, 10, 10, 10, -10};
5873
5874// This ownship icon was adapted and scaled from the S52 Presentation
5875// Library version 3_03 Symbol OWNSHP05
5876static int s_ownship_icon[] = {5, -42, 11, -28, 11, 42, -11, 42, -11, -28,
5877 -5, -42, -11, 0, 11, 0, 0, 42, 0, -42};
5878
5879wxColour ChartCanvas::PredColor() {
5880 // RAdjust predictor color change on LOW_ACCURACY ship state in interests of
5881 // visibility.
5882 if (SHIP_NORMAL == m_ownship_state)
5883 return GetGlobalColor("URED");
5884
5885 else if (SHIP_LOWACCURACY == m_ownship_state)
5886 return GetGlobalColor("YELO1");
5887
5888 return GetGlobalColor("NODTA");
5889}
5890
5891wxColour ChartCanvas::ShipColor() {
5892 // Establish ship color
5893 // It changes color based on GPS and Chart accuracy/availability
5894
5895 if (SHIP_NORMAL != m_ownship_state) return GetGlobalColor("GREY1");
5896
5897 if (SHIP_LOWACCURACY == m_ownship_state) return GetGlobalColor("YELO1");
5898
5899 return GetGlobalColor("URED"); // default is OK
5900}
5901
5902void ChartCanvas::ShipDrawLargeScale(ocpnDC &dc,
5903 wxPoint2DDouble lShipMidPoint) {
5904 dc.SetPen(wxPen(PredColor(), 2));
5905
5906 if (SHIP_NORMAL == m_ownship_state)
5907 dc.SetBrush(wxBrush(ShipColor(), wxBRUSHSTYLE_TRANSPARENT));
5908 else
5909 dc.SetBrush(wxBrush(GetGlobalColor("YELO1")));
5910
5911 dc.DrawEllipse(lShipMidPoint.m_x - 10, lShipMidPoint.m_y - 10, 20, 20);
5912 dc.DrawEllipse(lShipMidPoint.m_x - 6, lShipMidPoint.m_y - 6, 12, 12);
5913
5914 dc.DrawLine(lShipMidPoint.m_x - 12, lShipMidPoint.m_y, lShipMidPoint.m_x + 12,
5915 lShipMidPoint.m_y);
5916 dc.DrawLine(lShipMidPoint.m_x, lShipMidPoint.m_y - 12, lShipMidPoint.m_x,
5917 lShipMidPoint.m_y + 12);
5918}
5919
5920void ChartCanvas::ShipIndicatorsDraw(ocpnDC &dc, int img_height,
5921 wxPoint GPSOffsetPixels,
5922 wxPoint2DDouble lGPSPoint) {
5923 // if (m_animationActive) return;
5924 // Develop a uniform length for course predictor line dash length, based on
5925 // physical display size Use this reference length to size all other graphics
5926 // elements
5927 float ref_dim = m_display_size_mm / 24;
5928 ref_dim = wxMin(ref_dim, 12);
5929 ref_dim = wxMax(ref_dim, 6);
5930
5931 wxColour cPred;
5932 cPred.Set(g_cog_predictor_color);
5933 if (cPred == wxNullColour) cPred = PredColor();
5934
5935 // Establish some graphic element line widths dependent on the platform
5936 // display resolution
5937 // double nominal_line_width_pix = wxMax(1.0,
5938 // floor(g_Platform->GetDisplayDPmm() / 2)); //0.5 mm nominal, but
5939 // not less than 1 pixel
5940 double nominal_line_width_pix = wxMax(
5941 1.0,
5942 floor(m_pix_per_mm / 2)); // 0.5 mm nominal, but not less than 1 pixel
5943
5944 // If the calculated value is greater than the config file spec value, then
5945 // use it.
5946 if (nominal_line_width_pix > g_cog_predictor_width)
5947 g_cog_predictor_width = nominal_line_width_pix;
5948
5949 // Calculate ownship Position Predictor
5950 wxPoint lPredPoint, lHeadPoint;
5951
5952 float pCog = std::isnan(gCog) ? 0 : gCog;
5953 float pSog = std::isnan(gSog) ? 0 : gSog;
5954
5955 double pred_lat, pred_lon;
5956 ll_gc_ll(gLat, gLon, pCog, pSog * g_ownship_predictor_minutes / 60.,
5957 &pred_lat, &pred_lon);
5958 GetCanvasPointPix(pred_lat, pred_lon, &lPredPoint);
5959
5960 // test to catch the case where COG/HDG line crosses the screen
5961 LLBBox box;
5962
5963 // Should we draw the Head vector?
5964 // Compare the points lHeadPoint and lPredPoint
5965 // If they differ by more than n pixels, and the head vector is valid, then
5966 // render the head vector
5967
5968 float ndelta_pix = 10.;
5969 double hdg_pred_lat, hdg_pred_lon;
5970 bool b_render_hdt = false;
5971 if (!std::isnan(gHdt)) {
5972 // Calculate ownship Heading pointer as a predictor
5973 ll_gc_ll(gLat, gLon, gHdt, g_ownship_HDTpredictor_miles, &hdg_pred_lat,
5974 &hdg_pred_lon);
5975 GetCanvasPointPix(hdg_pred_lat, hdg_pred_lon, &lHeadPoint);
5976 float dist = sqrtf(powf((float)(lHeadPoint.x - lPredPoint.x), 2) +
5977 powf((float)(lHeadPoint.y - lPredPoint.y), 2));
5978 if (dist > ndelta_pix /*&& !std::isnan(gSog)*/) {
5979 box.SetFromSegment(gLat, gLon, hdg_pred_lat, hdg_pred_lon);
5980 if (!GetVP().GetBBox().IntersectOut(box)) b_render_hdt = true;
5981 }
5982 }
5983
5984 // draw course over ground if they are longer than the ship
5985 wxPoint lShipMidPoint;
5986 lShipMidPoint.x = lGPSPoint.m_x + GPSOffsetPixels.x;
5987 lShipMidPoint.y = lGPSPoint.m_y + GPSOffsetPixels.y;
5988 float lpp = sqrtf(powf((float)(lPredPoint.x - lShipMidPoint.x), 2) +
5989 powf((float)(lPredPoint.y - lShipMidPoint.y), 2));
5990
5991 if (lpp >= img_height / 2) {
5992 box.SetFromSegment(gLat, gLon, pred_lat, pred_lon);
5993 if (!GetVP().GetBBox().IntersectOut(box) && !std::isnan(gCog) &&
5994 !std::isnan(gSog)) {
5995 // COG Predictor
5996 float dash_length = ref_dim;
5997 wxDash dash_long[2];
5998 dash_long[0] =
5999 (int)(floor(g_Platform->GetDisplayDPmm() * dash_length) /
6000 g_cog_predictor_width); // Long dash , in mm <---------+
6001 dash_long[1] = dash_long[0] / 2.0; // Short gap
6002
6003 // On ultra-hi-res displays, do not allow the dashes to be greater than
6004 // 250, since it is defined as (char)
6005 if (dash_length > 250.) {
6006 dash_long[0] = 250. / g_cog_predictor_width;
6007 dash_long[1] = dash_long[0] / 2;
6008 }
6009
6010 wxPen ppPen2(cPred, g_cog_predictor_width,
6011 (wxPenStyle)g_cog_predictor_style);
6012 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6013 ppPen2.SetDashes(2, dash_long);
6014 dc.SetPen(ppPen2);
6015 dc.StrokeLine(
6016 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6017 lPredPoint.x + GPSOffsetPixels.x, lPredPoint.y + GPSOffsetPixels.y);
6018
6019 if (g_cog_predictor_width > 1) {
6020 float line_width = g_cog_predictor_width / 3.;
6021
6022 wxDash dash_long3[2];
6023 dash_long3[0] = g_cog_predictor_width / line_width * dash_long[0];
6024 dash_long3[1] = g_cog_predictor_width / line_width * dash_long[1];
6025
6026 wxPen ppPen3(GetGlobalColor("UBLCK"), wxMax(1, line_width),
6027 (wxPenStyle)g_cog_predictor_style);
6028 if (g_cog_predictor_style == (wxPenStyle)wxUSER_DASH)
6029 ppPen3.SetDashes(2, dash_long3);
6030 dc.SetPen(ppPen3);
6031 dc.StrokeLine(lGPSPoint.m_x + GPSOffsetPixels.x,
6032 lGPSPoint.m_y + GPSOffsetPixels.y,
6033 lPredPoint.x + GPSOffsetPixels.x,
6034 lPredPoint.y + GPSOffsetPixels.y);
6035 }
6036
6037 if (g_cog_predictor_endmarker) {
6038 // Prepare COG predictor endpoint icon
6039 double png_pred_icon_scale_factor = .4;
6040 if (g_ShipScaleFactorExp > 1.0)
6041 png_pred_icon_scale_factor *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6042 if (g_scaler) png_pred_icon_scale_factor *= 1.0 / g_scaler;
6043
6044 wxPoint icon[4];
6045
6046 float cog_rad = atan2f((float)(lPredPoint.y - lShipMidPoint.y),
6047 (float)(lPredPoint.x - lShipMidPoint.x));
6048 cog_rad += (float)PI;
6049
6050 for (int i = 0; i < 4; i++) {
6051 int j = i * 2;
6052 double pxa = (double)(s_png_pred_icon[j]);
6053 double pya = (double)(s_png_pred_icon[j + 1]);
6054
6055 pya *= png_pred_icon_scale_factor;
6056 pxa *= png_pred_icon_scale_factor;
6057
6058 double px = (pxa * sin(cog_rad)) + (pya * cos(cog_rad));
6059 double py = (pya * sin(cog_rad)) - (pxa * cos(cog_rad));
6060
6061 icon[i].x = (int)wxRound(px) + lPredPoint.x + GPSOffsetPixels.x;
6062 icon[i].y = (int)wxRound(py) + lPredPoint.y + GPSOffsetPixels.y;
6063 }
6064
6065 // Render COG endpoint icon
6066 wxPen ppPen1(GetGlobalColor("UBLCK"), g_cog_predictor_width / 2,
6067 wxPENSTYLE_SOLID);
6068 dc.SetPen(ppPen1);
6069 dc.SetBrush(wxBrush(cPred));
6070
6071 dc.StrokePolygon(4, icon);
6072 }
6073 }
6074 }
6075
6076 // HDT Predictor
6077 if (b_render_hdt) {
6078 float hdt_dash_length = ref_dim * 0.4;
6079
6080 cPred.Set(g_ownship_HDTpredictor_color);
6081 if (cPred == wxNullColour) cPred = PredColor();
6082 float hdt_width =
6083 (g_ownship_HDTpredictor_width > 0 ? g_ownship_HDTpredictor_width
6084 : g_cog_predictor_width * 0.8);
6085 wxDash dash_short[2];
6086 dash_short[0] =
6087 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length) /
6088 hdt_width); // Short dash , in mm <---------+
6089 dash_short[1] =
6090 (int)(floor(g_Platform->GetDisplayDPmm() * hdt_dash_length * 0.9) /
6091 hdt_width); // Short gap |
6092
6093 wxPen ppPen2(cPred, hdt_width, (wxPenStyle)g_ownship_HDTpredictor_style);
6094 if (g_ownship_HDTpredictor_style == (wxPenStyle)wxUSER_DASH)
6095 ppPen2.SetDashes(2, dash_short);
6096
6097 dc.SetPen(ppPen2);
6098 dc.StrokeLine(
6099 lGPSPoint.m_x + GPSOffsetPixels.x, lGPSPoint.m_y + GPSOffsetPixels.y,
6100 lHeadPoint.x + GPSOffsetPixels.x, lHeadPoint.y + GPSOffsetPixels.y);
6101
6102 wxPen ppPen1(cPred, g_cog_predictor_width / 3, wxPENSTYLE_SOLID);
6103 dc.SetPen(ppPen1);
6104 dc.SetBrush(wxBrush(GetGlobalColor("GREY2")));
6105
6106 if (g_ownship_HDTpredictor_endmarker) {
6107 double nominal_circle_size_pixels = wxMax(
6108 4.0, floor(m_pix_per_mm * (ref_dim / 5.0))); // not less than 4 pixel
6109
6110 // Scale the circle to ChartScaleFactor, slightly softened....
6111 if (g_ShipScaleFactorExp > 1.0)
6112 nominal_circle_size_pixels *= (log(g_ShipScaleFactorExp) + 1.0) * 1.1;
6113
6114 dc.StrokeCircle(lHeadPoint.x + GPSOffsetPixels.x,
6115 lHeadPoint.y + GPSOffsetPixels.y,
6116 nominal_circle_size_pixels / 2);
6117 }
6118 }
6119
6120 // Draw radar rings if activated
6121 if (g_bNavAidRadarRingsShown && g_iNavAidRadarRingsNumberVisible > 0) {
6122 double factor = 1.00;
6123 if (g_pNavAidRadarRingsStepUnits == 1) // kilometers
6124 factor = 1 / 1.852;
6125 else if (g_pNavAidRadarRingsStepUnits == 2) { // minutes (time)
6126 if (std::isnan(gSog))
6127 factor = 0.0;
6128 else
6129 factor = gSog / 60;
6130 }
6131 factor *= g_fNavAidRadarRingsStep;
6132
6133 double tlat, tlon;
6134 wxPoint r;
6135 ll_gc_ll(gLat, gLon, 0, factor, &tlat, &tlon);
6136 GetCanvasPointPix(tlat, tlon, &r);
6137
6138 double lpp = sqrt(pow((double)(lGPSPoint.m_x - r.x), 2) +
6139 pow((double)(lGPSPoint.m_y - r.y), 2));
6140 int pix_radius = (int)lpp;
6141
6142 wxColor rangeringcolour = GetDimColor(g_colourOwnshipRangeRingsColour);
6143
6144 wxPen ppPen1(rangeringcolour, g_cog_predictor_width);
6145
6146 dc.SetPen(ppPen1);
6147 dc.SetBrush(wxBrush(rangeringcolour, wxBRUSHSTYLE_TRANSPARENT));
6148
6149 for (int i = 1; i <= g_iNavAidRadarRingsNumberVisible; i++)
6150 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, i * pix_radius);
6151 }
6152}
6153
6154void ChartCanvas::ComputeShipScaleFactor(
6155 float icon_hdt, int ownShipWidth, int ownShipLength,
6156 wxPoint2DDouble &lShipMidPoint, wxPoint &GPSOffsetPixels,
6157 wxPoint2DDouble lGPSPoint, float &scale_factor_x, float &scale_factor_y) {
6158 float screenResolution = m_pix_per_mm;
6159
6160 // Calculate the true ship length in exact pixels
6161 double ship_bow_lat, ship_bow_lon;
6162 ll_gc_ll(gLat, gLon, icon_hdt, g_n_ownship_length_meters / 1852.,
6163 &ship_bow_lat, &ship_bow_lon);
6164 wxPoint lShipBowPoint;
6165 wxPoint2DDouble b_point =
6166 GetVP().GetDoublePixFromLL(ship_bow_lat, ship_bow_lon);
6167 wxPoint2DDouble a_point = GetVP().GetDoublePixFromLL(gLat, gLon);
6168
6169 float shipLength_px = sqrtf(powf((float)(b_point.m_x - a_point.m_x), 2) +
6170 powf((float)(b_point.m_y - a_point.m_y), 2));
6171
6172 // And in mm
6173 float shipLength_mm = shipLength_px / screenResolution;
6174
6175 // Set minimum ownship drawing size
6176 float ownship_min_mm = g_n_ownship_min_mm;
6177 ownship_min_mm = wxMax(ownship_min_mm, 1.0);
6178
6179 // Calculate Nautical Miles distance from midships to gps antenna
6180 float hdt_ant = icon_hdt + 180.;
6181 float dy = (g_n_ownship_length_meters / 2 - g_n_gps_antenna_offset_y) / 1852.;
6182 float dx = g_n_gps_antenna_offset_x / 1852.;
6183 if (g_n_gps_antenna_offset_y > g_n_ownship_length_meters / 2) // reverse?
6184 {
6185 hdt_ant = icon_hdt;
6186 dy = -dy;
6187 }
6188
6189 // If the drawn ship size is going to be clamped, adjust the gps antenna
6190 // offsets
6191 if (shipLength_mm < ownship_min_mm) {
6192 dy /= shipLength_mm / ownship_min_mm;
6193 dx /= shipLength_mm / ownship_min_mm;
6194 }
6195
6196 double ship_mid_lat, ship_mid_lon, ship_mid_lat1, ship_mid_lon1;
6197
6198 ll_gc_ll(gLat, gLon, hdt_ant, dy, &ship_mid_lat, &ship_mid_lon);
6199 ll_gc_ll(ship_mid_lat, ship_mid_lon, icon_hdt - 90., dx, &ship_mid_lat1,
6200 &ship_mid_lon1);
6201
6202 GetDoubleCanvasPointPixVP(GetVP(), ship_mid_lat1, ship_mid_lon1,
6203 &lShipMidPoint);
6204
6205 GPSOffsetPixels.x = lShipMidPoint.m_x - lGPSPoint.m_x;
6206 GPSOffsetPixels.y = lShipMidPoint.m_y - lGPSPoint.m_y;
6207
6208 float scale_factor = shipLength_px / ownShipLength;
6209
6210 // Calculate a scale factor that would produce a reasonably sized icon
6211 float scale_factor_min = ownship_min_mm / (ownShipLength / screenResolution);
6212
6213 // And choose the correct one
6214 scale_factor = wxMax(scale_factor, scale_factor_min);
6215
6216 scale_factor_y = scale_factor;
6217 scale_factor_x = scale_factor_y * ((float)ownShipLength / ownShipWidth) /
6218 ((float)g_n_ownship_length_meters / g_n_ownship_beam_meters);
6219}
6220
6221void ChartCanvas::ShipDraw(ocpnDC &dc) {
6222 if (!GetVP().IsValid()) return;
6223
6224 wxPoint GPSOffsetPixels(0, 0);
6225 wxPoint2DDouble lGPSPoint, lShipMidPoint;
6226
6227 // COG/SOG may be undefined in NMEA data stream
6228 float pCog = std::isnan(gCog) ? 0 : gCog;
6229 float pSog = std::isnan(gSog) ? 0 : gSog;
6230
6231 GetDoubleCanvasPointPixVP(GetVP(), gLat, gLon, &lGPSPoint);
6232
6233 lShipMidPoint = lGPSPoint;
6234
6235 // Draw the icon rotated to the COG
6236 // or to the Hdt if available
6237 float icon_hdt = pCog;
6238 if (!std::isnan(gHdt)) icon_hdt = gHdt;
6239
6240 // COG may be undefined in NMEA data stream
6241 if (std::isnan(icon_hdt)) icon_hdt = 0.0;
6242
6243 // Calculate the ownship drawing angle icon_rad using an assumed 10 minute
6244 // predictor
6245 double osd_head_lat, osd_head_lon;
6246 wxPoint osd_head_point;
6247
6248 ll_gc_ll(gLat, gLon, icon_hdt, pSog * 10. / 60., &osd_head_lat,
6249 &osd_head_lon);
6250
6251 GetCanvasPointPix(osd_head_lat, osd_head_lon, &osd_head_point);
6252
6253 float icon_rad = atan2f((float)(osd_head_point.y - lShipMidPoint.m_y),
6254 (float)(osd_head_point.x - lShipMidPoint.m_x));
6255 icon_rad += (float)PI;
6256
6257 if (pSog < 0.2) icon_rad = ((icon_hdt + 90.) * PI / 180) + GetVP().rotation;
6258
6259 // Another draw test ,based on pixels, assuming the ship icon is a fixed
6260 // nominal size and is just barely outside the viewport ....
6261 BoundingBox bb_screen(0, 0, GetVP().pix_width, GetVP().pix_height);
6262
6263 // TODO: fix to include actual size of boat that will be rendered
6264 int img_height = 0;
6265 if (bb_screen.PointInBox(lShipMidPoint, 20)) {
6266 if (GetVP().chart_scale >
6267 300000) // According to S52, this should be 50,000
6268 {
6269 ShipDrawLargeScale(dc, lShipMidPoint);
6270 img_height = 20;
6271 } else {
6272 wxImage pos_image;
6273
6274 // Substitute user ownship image if found
6275 if (m_pos_image_user)
6276 pos_image = m_pos_image_user->Copy();
6277 else if (SHIP_NORMAL == m_ownship_state)
6278 pos_image = m_pos_image_red->Copy();
6279 if (SHIP_LOWACCURACY == m_ownship_state)
6280 pos_image = m_pos_image_yellow->Copy();
6281 else if (SHIP_NORMAL != m_ownship_state)
6282 pos_image = m_pos_image_grey->Copy();
6283
6284 // Substitute user ownship image if found
6285 if (m_pos_image_user) {
6286 pos_image = m_pos_image_user->Copy();
6287
6288 if (SHIP_LOWACCURACY == m_ownship_state)
6289 pos_image = m_pos_image_user_yellow->Copy();
6290 else if (SHIP_NORMAL != m_ownship_state)
6291 pos_image = m_pos_image_user_grey->Copy();
6292 }
6293
6294 img_height = pos_image.GetHeight();
6295
6296 if (g_n_ownship_beam_meters > 0.0 && g_n_ownship_length_meters > 0.0 &&
6297 g_OwnShipIconType > 0) // use large ship
6298 {
6299 int ownShipWidth = 22; // Default values from s_ownship_icon
6300 int ownShipLength = 84;
6301 if (g_OwnShipIconType == 1) {
6302 ownShipWidth = pos_image.GetWidth();
6303 ownShipLength = pos_image.GetHeight();
6304 }
6305
6306 float scale_factor_x, scale_factor_y;
6307 ComputeShipScaleFactor(icon_hdt, ownShipWidth, ownShipLength,
6308 lShipMidPoint, GPSOffsetPixels, lGPSPoint,
6309 scale_factor_x, scale_factor_y);
6310
6311 if (g_OwnShipIconType == 1) { // Scaled bitmap
6312 pos_image.Rescale(ownShipWidth * scale_factor_x,
6313 ownShipLength * scale_factor_y,
6314 wxIMAGE_QUALITY_HIGH);
6315 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6316 wxImage rot_image =
6317 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6318
6319 // Simple sharpening algorithm.....
6320 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6321 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6322 if (rot_image.GetAlpha(ip, jp) > 64)
6323 rot_image.SetAlpha(ip, jp, 255);
6324
6325 wxBitmap os_bm(rot_image);
6326
6327 int w = os_bm.GetWidth();
6328 int h = os_bm.GetHeight();
6329 img_height = h;
6330
6331 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6332 lShipMidPoint.m_y - h / 2, true);
6333
6334 // Maintain dirty box,, missing in __WXMSW__ library
6335 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6336 lShipMidPoint.m_y - h / 2);
6337 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6338 lShipMidPoint.m_y - h / 2 + h);
6339 }
6340
6341 else if (g_OwnShipIconType == 2) { // Scaled Vector
6342 wxPoint ownship_icon[10];
6343
6344 for (int i = 0; i < 10; i++) {
6345 int j = i * 2;
6346 float pxa = (float)(s_ownship_icon[j]);
6347 float pya = (float)(s_ownship_icon[j + 1]);
6348 pya *= scale_factor_y;
6349 pxa *= scale_factor_x;
6350
6351 float px = (pxa * sinf(icon_rad)) + (pya * cosf(icon_rad));
6352 float py = (pya * sinf(icon_rad)) - (pxa * cosf(icon_rad));
6353
6354 ownship_icon[i].x = (int)(px) + lShipMidPoint.m_x;
6355 ownship_icon[i].y = (int)(py) + lShipMidPoint.m_y;
6356 }
6357
6358 wxPen ppPen1(GetGlobalColor("UBLCK"), 1, wxPENSTYLE_SOLID);
6359 dc.SetPen(ppPen1);
6360 dc.SetBrush(wxBrush(ShipColor()));
6361
6362 dc.StrokePolygon(6, &ownship_icon[0], 0, 0);
6363
6364 // draw reference point (midships) cross
6365 dc.StrokeLine(ownship_icon[6].x, ownship_icon[6].y, ownship_icon[7].x,
6366 ownship_icon[7].y);
6367 dc.StrokeLine(ownship_icon[8].x, ownship_icon[8].y, ownship_icon[9].x,
6368 ownship_icon[9].y);
6369 }
6370
6371 img_height = ownShipLength * scale_factor_y;
6372
6373 // Reference point, where the GPS antenna is
6374 int circle_rad = 3;
6375 if (m_pos_image_user) circle_rad = 1;
6376
6377 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6378 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6379 dc.StrokeCircle(lGPSPoint.m_x, lGPSPoint.m_y, circle_rad);
6380 } else { // Fixed bitmap icon.
6381 /* non opengl, or suboptimal opengl via ocpndc: */
6382 wxPoint rot_ctr(pos_image.GetWidth() / 2, pos_image.GetHeight() / 2);
6383 wxImage rot_image =
6384 pos_image.Rotate(-(icon_rad - (PI / 2.)), rot_ctr, true);
6385
6386 // Simple sharpening algorithm.....
6387 for (int ip = 0; ip < rot_image.GetWidth(); ip++)
6388 for (int jp = 0; jp < rot_image.GetHeight(); jp++)
6389 if (rot_image.GetAlpha(ip, jp) > 64)
6390 rot_image.SetAlpha(ip, jp, 255);
6391
6392 wxBitmap os_bm(rot_image);
6393
6394 if (g_ShipScaleFactorExp > 1) {
6395 wxImage scaled_image = os_bm.ConvertToImage();
6396 double factor = (log(g_ShipScaleFactorExp) + 1.0) *
6397 1.0; // soften the scale factor a bit
6398 os_bm = wxBitmap(scaled_image.Scale(scaled_image.GetWidth() * factor,
6399 scaled_image.GetHeight() * factor,
6400 wxIMAGE_QUALITY_HIGH));
6401 }
6402 int w = os_bm.GetWidth();
6403 int h = os_bm.GetHeight();
6404 img_height = h;
6405
6406 dc.DrawBitmap(os_bm, lShipMidPoint.m_x - w / 2,
6407 lShipMidPoint.m_y - h / 2, true);
6408
6409 // Reference point, where the GPS antenna is
6410 int circle_rad = 3;
6411 if (m_pos_image_user) circle_rad = 1;
6412
6413 dc.SetPen(wxPen(GetGlobalColor("UBLCK"), 1));
6414 dc.SetBrush(wxBrush(GetGlobalColor("UIBCK")));
6415 dc.StrokeCircle(lShipMidPoint.m_x, lShipMidPoint.m_y, circle_rad);
6416
6417 // Maintain dirty box,, missing in __WXMSW__ library
6418 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2,
6419 lShipMidPoint.m_y - h / 2);
6420 dc.CalcBoundingBox(lShipMidPoint.m_x - w / 2 + w,
6421 lShipMidPoint.m_y - h / 2 + h);
6422 }
6423 } // ownship draw
6424 }
6425
6426 ShipIndicatorsDraw(dc, img_height, GPSOffsetPixels, lGPSPoint);
6427}
6428
6429/* @ChartCanvas::CalcGridSpacing
6430 **
6431 ** Calculate the major and minor spacing between the lat/lon grid
6432 **
6433 ** @param [r] WindowDegrees [float] displayed number of lat or lan in the
6434 *window
6435 ** @param [w] MajorSpacing [float &] Major distance between grid lines
6436 ** @param [w] MinorSpacing [float &] Minor distance between grid lines
6437 ** @return [void]
6438 */
6439void CalcGridSpacing(float view_scale_ppm, float &MajorSpacing,
6440 float &MinorSpacing) {
6441 // table for calculating the distance between the grids
6442 // [0] view_scale ppm
6443 // [1] spacing between major grid lines in degrees
6444 // [2] spacing between minor grid lines in degrees
6445 const float lltab[][3] = {{0.0f, 90.0f, 30.0f},
6446 {.000001f, 45.0f, 15.0f},
6447 {.0002f, 30.0f, 10.0f},
6448 {.0003f, 10.0f, 2.0f},
6449 {.0008f, 5.0f, 1.0f},
6450 {.001f, 2.0f, 30.0f / 60.0f},
6451 {.003f, 1.0f, 20.0f / 60.0f},
6452 {.006f, 0.5f, 10.0f / 60.0f},
6453 {.03f, 15.0f / 60.0f, 5.0f / 60.0f},
6454 {.01f, 10.0f / 60.0f, 2.0f / 60.0f},
6455 {.06f, 5.0f / 60.0f, 1.0f / 60.0f},
6456 {.1f, 2.0f / 60.0f, 1.0f / 60.0f},
6457 {.4f, 1.0f / 60.0f, 0.5f / 60.0f},
6458 {.6f, 0.5f / 60.0f, 0.1f / 60.0f},
6459 {1.0f, 0.2f / 60.0f, 0.1f / 60.0f},
6460 {1e10f, 0.1f / 60.0f, 0.05f / 60.0f}};
6461
6462 unsigned int tabi;
6463 for (tabi = 0; tabi < ((sizeof lltab) / (sizeof *lltab)) - 1; tabi++)
6464 if (view_scale_ppm < lltab[tabi][0]) break;
6465 MajorSpacing = lltab[tabi][1]; // major latitude distance
6466 MinorSpacing = lltab[tabi][2]; // minor latitude distance
6467 return;
6468}
6469/* @ChartCanvas::CalcGridText *************************************
6470 **
6471 ** Calculates text to display at the major grid lines
6472 **
6473 ** @param [r] latlon [float] latitude or longitude of grid line
6474 ** @param [r] spacing [float] distance between two major grid lines
6475 ** @param [r] bPostfix [bool] true for latitudes, false for longitudes
6476 **
6477 ** @return
6478 */
6479
6480wxString CalcGridText(float latlon, float spacing, bool bPostfix) {
6481 int deg = (int)fabs(latlon); // degrees
6482 float min = fabs((fabs(latlon) - deg) * 60.0); // Minutes
6483 char postfix;
6484
6485 // calculate postfix letter (NSEW)
6486 if (latlon > 0.0) {
6487 if (bPostfix) {
6488 postfix = 'N';
6489 } else {
6490 postfix = 'E';
6491 }
6492 } else if (latlon < 0.0) {
6493 if (bPostfix) {
6494 postfix = 'S';
6495 } else {
6496 postfix = 'W';
6497 }
6498 } else {
6499 postfix = ' '; // no postfix for equator and greenwich
6500 }
6501 // calculate text, display minutes only if spacing is smaller than one degree
6502
6503 wxString ret;
6504 if (spacing >= 1.0) {
6505 ret.Printf("%3d%c %c", deg, 0x00b0, postfix);
6506 } else if (spacing >= (1.0 / 60.0)) {
6507 ret.Printf("%3d%c%02.0f %c", deg, 0x00b0, min, postfix);
6508 } else {
6509 ret.Printf("%3d%c%02.2f %c", deg, 0x00b0, min, postfix);
6510 }
6511
6512 return ret;
6513}
6514
6515/* @ChartCanvas::GridDraw *****************************************
6516 **
6517 ** Draws major and minor Lat/Lon Grid on the chart
6518 ** - distance between Grid-lm ines are calculated automatic
6519 ** - major grid lines will be across the whole chart window
6520 ** - minor grid lines will be 10 pixel at each edge of the chart window.
6521 **
6522 ** @param [w] dc [wxDC&] the wx drawing context
6523 **
6524 ** @return [void]
6525 ************************************************************************/
6526void ChartCanvas::GridDraw(ocpnDC &dc) {
6527 if (!(m_bDisplayGrid && (fabs(GetVP().rotation) < 1e-5))) return;
6528
6529 double nlat, elon, slat, wlon;
6530 float lat, lon;
6531 float dlon;
6532 float gridlatMajor, gridlatMinor, gridlonMajor, gridlonMinor;
6533 wxCoord w, h;
6534 wxPen GridPen(GetGlobalColor("SNDG1"), 1, wxPENSTYLE_SOLID);
6535 dc.SetPen(GridPen);
6536 if (!m_pgridFont) SetupGridFont();
6537 dc.SetFont(*m_pgridFont);
6538 dc.SetTextForeground(GetGlobalColor("SNDG1"));
6539
6540 w = m_canvas_width;
6541 h = m_canvas_height;
6542
6543 GetCanvasPixPoint(0, 0, nlat,
6544 wlon); // get lat/lon of upper left point of the window
6545 GetCanvasPixPoint(w, h, slat,
6546 elon); // get lat/lon of lower right point of the window
6547 dlon =
6548 elon -
6549 wlon; // calculate how many degrees of longitude are shown in the window
6550 if (dlon < 0.0) // concider datum border at 180 degrees longitude
6551 {
6552 dlon = dlon + 360.0;
6553 }
6554 // calculate distance between latitude grid lines
6555 CalcGridSpacing(GetVP().view_scale_ppm, gridlatMajor, gridlatMinor);
6556
6557 // calculate position of first major latitude grid line
6558 lat = ceil(slat / gridlatMajor) * gridlatMajor;
6559
6560 // Draw Major latitude grid lines and text
6561 while (lat < nlat) {
6562 wxPoint r;
6563 wxString st =
6564 CalcGridText(lat, gridlatMajor, true); // get text for grid line
6565 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6566 dc.DrawLine(0, r.y, w, r.y, false); // draw grid line
6567 dc.DrawText(st, 0, r.y); // draw text
6568 lat = lat + gridlatMajor;
6569
6570 if (fabs(lat - wxRound(lat)) < 1e-5) lat = wxRound(lat);
6571 }
6572
6573 // calculate position of first minor latitude grid line
6574 lat = ceil(slat / gridlatMinor) * gridlatMinor;
6575
6576 // Draw minor latitude grid lines
6577 while (lat < nlat) {
6578 wxPoint r;
6579 GetCanvasPointPix(lat, (elon + wlon) / 2, &r);
6580 dc.DrawLine(0, r.y, 10, r.y, false);
6581 dc.DrawLine(w - 10, r.y, w, r.y, false);
6582 lat = lat + gridlatMinor;
6583 }
6584
6585 // calculate distance between grid lines
6586 CalcGridSpacing(GetVP().view_scale_ppm, gridlonMajor, gridlonMinor);
6587
6588 // calculate position of first major latitude grid line
6589 lon = ceil(wlon / gridlonMajor) * gridlonMajor;
6590
6591 // draw major longitude grid lines
6592 for (int i = 0, itermax = (int)(dlon / gridlonMajor); i <= itermax; i++) {
6593 wxPoint r;
6594 wxString st = CalcGridText(lon, gridlonMajor, false);
6595 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6596 dc.DrawLine(r.x, 0, r.x, h, false);
6597 dc.DrawText(st, r.x, 0);
6598 lon = lon + gridlonMajor;
6599 if (lon > 180.0) {
6600 lon = lon - 360.0;
6601 }
6602
6603 if (fabs(lon - wxRound(lon)) < 1e-5) lon = wxRound(lon);
6604 }
6605
6606 // calculate position of first minor longitude grid line
6607 lon = ceil(wlon / gridlonMinor) * gridlonMinor;
6608 // draw minor longitude grid lines
6609 for (int i = 0, itermax = (int)(dlon / gridlonMinor); i <= itermax; i++) {
6610 wxPoint r;
6611 GetCanvasPointPix((nlat + slat) / 2, lon, &r);
6612 dc.DrawLine(r.x, 0, r.x, 10, false);
6613 dc.DrawLine(r.x, h - 10, r.x, h, false);
6614 lon = lon + gridlonMinor;
6615 if (lon > 180.0) {
6616 lon = lon - 360.0;
6617 }
6618 }
6619}
6620
6621void ChartCanvas::ScaleBarDraw(ocpnDC &dc) {
6622 if (0 ) {
6623 double blat, blon, tlat, tlon;
6624 wxPoint r;
6625
6626 int x_origin = m_bDisplayGrid ? 60 : 20;
6627 int y_origin = m_canvas_height - 50;
6628
6629 float dist;
6630 int count;
6631 wxPen pen1, pen2;
6632
6633 if (GetVP().chart_scale > 80000) // Draw 10 mile scale as SCALEB11
6634 {
6635 dist = 10.0;
6636 count = 5;
6637 pen1 = wxPen(GetGlobalColor("SNDG2"), 3, wxPENSTYLE_SOLID);
6638 pen2 = wxPen(GetGlobalColor("SNDG1"), 3, wxPENSTYLE_SOLID);
6639 } else // Draw 1 mile scale as SCALEB10
6640 {
6641 dist = 1.0;
6642 count = 10;
6643 pen1 = wxPen(GetGlobalColor("SCLBR"), 3, wxPENSTYLE_SOLID);
6644 pen2 = wxPen(GetGlobalColor("CHGRD"), 3, wxPENSTYLE_SOLID);
6645 }
6646
6647 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6648 double rotation = -VPoint.rotation;
6649
6650 ll_gc_ll(blat, blon, rotation * 180 / PI, dist, &tlat, &tlon);
6651 GetCanvasPointPix(tlat, tlon, &r);
6652 int l1 = (y_origin - r.y) / count;
6653
6654 for (int i = 0; i < count; i++) {
6655 int y = l1 * i;
6656 if (i & 1)
6657 dc.SetPen(pen1);
6658 else
6659 dc.SetPen(pen2);
6660
6661 dc.DrawLine(x_origin, y_origin - y, x_origin, y_origin - (y + l1));
6662 }
6663 } else {
6664 double blat, blon, tlat, tlon;
6665
6666 int x_origin = 5.0 * GetPixPerMM();
6667 int chartbar_height = GetChartbarHeight();
6668 // ocpnStyle::Style* style = g_StyleManager->GetCurrentStyle();
6669 // if (style->chartStatusWindowTransparent)
6670 // chartbar_height = 0;
6671 int y_origin = m_canvas_height - chartbar_height - 5;
6672#ifdef __WXOSX__
6673 if (!g_bopengl)
6674 y_origin =
6675 m_canvas_height / GetContentScaleFactor() - chartbar_height - 5;
6676#endif
6677
6678 GetCanvasPixPoint(x_origin, y_origin, blat, blon);
6679 GetCanvasPixPoint(x_origin + m_canvas_width, y_origin, tlat, tlon);
6680
6681 double d;
6682 ll_gc_ll_reverse(blat, blon, tlat, tlon, 0, &d);
6683 d /= 2;
6684
6685 int unit = g_iDistanceFormat;
6686 if (d < .5 &&
6687 (unit == DISTANCE_KM || unit == DISTANCE_MI || unit == DISTANCE_NMI))
6688 unit = (unit == DISTANCE_MI) ? DISTANCE_FT : DISTANCE_M;
6689
6690 // nice number
6691 float dist = toUsrDistance(d, unit), logdist = log(dist) / log(10.F);
6692 float places = floor(logdist), rem = logdist - places;
6693 dist = pow(10, places);
6694
6695 if (rem < .2)
6696 dist /= 5;
6697 else if (rem < .5)
6698 dist /= 2;
6699
6700 wxString s = wxString::Format("%g ", dist) + getUsrDistanceUnit(unit);
6701 wxPen pen1 = wxPen(GetGlobalColor("UBLCK"), 3, wxPENSTYLE_SOLID);
6702 double rotation = -VPoint.rotation;
6703
6704 ll_gc_ll(blat, blon, rotation * 180 / PI + 90, fromUsrDistance(dist, unit),
6705 &tlat, &tlon);
6706 wxPoint r;
6707 GetCanvasPointPix(tlat, tlon, &r);
6708 int l1 = r.x - x_origin;
6709
6710 m_scaleBarRect = wxRect(x_origin, y_origin - 12, l1,
6711 12); // Store this for later reference
6712
6713 dc.SetPen(pen1);
6714
6715 dc.DrawLine(x_origin, y_origin, x_origin, y_origin - 12);
6716 dc.DrawLine(x_origin, y_origin, x_origin + l1, y_origin);
6717 dc.DrawLine(x_origin + l1, y_origin, x_origin + l1, y_origin - 12);
6718
6719 if (!m_pgridFont) SetupGridFont();
6720 dc.SetFont(*m_pgridFont);
6721 dc.SetTextForeground(GetGlobalColor("UBLCK"));
6722 int w, h;
6723 dc.GetTextExtent(s, &w, &h);
6724 double dpi_factor = 1. / g_BasePlatform->GetDisplayDIPMult(this);
6725 if (g_bopengl) {
6726 w /= dpi_factor;
6727 h /= dpi_factor;
6728 }
6729 dc.DrawText(s, x_origin + l1 / 2 - w / 2, y_origin - h - 1);
6730 }
6731}
6732
6733void ChartCanvas::JaggyCircle(ocpnDC &dc, wxPen pen, int x, int y, int radius) {
6734 // Constants?
6735 double da_min = 2.;
6736 double da_max = 6.;
6737 double ra_min = 0.;
6738 double ra_max = 40.;
6739
6740 wxPen pen_save = dc.GetPen();
6741
6742 wxDateTime now = wxDateTime::Now();
6743
6744 dc.SetPen(pen);
6745
6746 int x0, y0, x1, y1;
6747
6748 x0 = x1 = x + radius; // Start point
6749 y0 = y1 = y;
6750 double angle = 0.;
6751 int i = 0;
6752
6753 while (angle < 360.) {
6754 double da = da_min + (((double)rand() / RAND_MAX) * (da_max - da_min));
6755 angle += da;
6756
6757 if (angle > 360.) angle = 360.;
6758
6759 double ra = ra_min + (((double)rand() / RAND_MAX) * (ra_max - ra_min));
6760
6761 double r;
6762 if (i & 1)
6763 r = radius + ra;
6764 else
6765 r = radius - ra;
6766
6767 x1 = (int)(x + cos(angle * PI / 180.) * r);
6768 y1 = (int)(y + sin(angle * PI / 180.) * r);
6769
6770 dc.DrawLine(x0, y0, x1, y1);
6771
6772 x0 = x1;
6773 y0 = y1;
6774
6775 i++;
6776 }
6777
6778 dc.DrawLine(x + radius, y, x1, y1); // closure
6779
6780 dc.SetPen(pen_save);
6781}
6782
6783static bool bAnchorSoundPlaying = false;
6784
6785static void onAnchorSoundFinished(void *ptr) {
6786 o_sound::g_anchorwatch_sound->UnLoad();
6787 bAnchorSoundPlaying = false;
6788}
6789
6790void ChartCanvas::AlertDraw(ocpnDC &dc) {
6791 using namespace o_sound;
6792 // Visual and audio alert for anchorwatch goes here
6793 bool play_sound = false;
6794 if (pAnchorWatchPoint1 && AnchorAlertOn1) {
6795 if (AnchorAlertOn1) {
6796 wxPoint TargetPoint;
6798 &TargetPoint);
6799 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6800 TargetPoint.y, 100);
6801 play_sound = true;
6802 }
6803 } else
6804 AnchorAlertOn1 = false;
6805
6806 if (pAnchorWatchPoint2 && AnchorAlertOn2) {
6807 if (AnchorAlertOn2) {
6808 wxPoint TargetPoint;
6810 &TargetPoint);
6811 JaggyCircle(dc, wxPen(GetGlobalColor("URED"), 2), TargetPoint.x,
6812 TargetPoint.y, 100);
6813 play_sound = true;
6814 }
6815 } else
6816 AnchorAlertOn2 = false;
6817
6818 if (play_sound) {
6819 if (!bAnchorSoundPlaying) {
6820 auto cmd_sound = dynamic_cast<SystemCmdSound *>(g_anchorwatch_sound);
6821 if (cmd_sound) cmd_sound->SetCmd(g_CmdSoundString.mb_str(wxConvUTF8));
6822 g_anchorwatch_sound->Load(g_anchorwatch_sound_file);
6823 if (g_anchorwatch_sound->IsOk()) {
6824 bAnchorSoundPlaying = true;
6825 g_anchorwatch_sound->SetFinishedCallback(onAnchorSoundFinished, NULL);
6826 g_anchorwatch_sound->Play();
6827 }
6828 }
6829 }
6830}
6831
6832void ChartCanvas::UpdateShips() {
6833 // Get the rectangle in the current dc which bounds the "ownship" symbol
6834
6835 wxClientDC dc(this);
6836 if (!dc.IsOk()) return;
6837
6838 wxBitmap test_bitmap(dc.GetSize().x, dc.GetSize().y);
6839 if (!test_bitmap.IsOk()) return;
6840
6841 wxMemoryDC temp_dc(test_bitmap);
6842
6843 temp_dc.ResetBoundingBox();
6844 temp_dc.DestroyClippingRegion();
6845 temp_dc.SetClippingRegion(0, 0, dc.GetSize().x, dc.GetSize().y);
6846
6847 // Draw the ownship on the temp_dc
6848 ocpnDC ocpndc = ocpnDC(temp_dc);
6849 ShipDraw(ocpndc);
6850
6851 if (g_pActiveTrack && g_pActiveTrack->IsRunning()) {
6852 TrackPoint *p = g_pActiveTrack->GetLastPoint();
6853 if (p) {
6854 wxPoint px;
6855 GetCanvasPointPix(p->m_lat, p->m_lon, &px);
6856 ocpndc.CalcBoundingBox(px.x, px.y);
6857 }
6858 }
6859
6860 ship_draw_rect =
6861 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6862 temp_dc.MaxY() - temp_dc.MinY());
6863
6864 wxRect own_ship_update_rect = ship_draw_rect;
6865
6866 if (!own_ship_update_rect.IsEmpty()) {
6867 // The required invalidate rectangle is the union of the last drawn
6868 // rectangle and this drawn rectangle
6869 own_ship_update_rect.Union(ship_draw_last_rect);
6870 own_ship_update_rect.Inflate(2);
6871 }
6872
6873 if (!own_ship_update_rect.IsEmpty()) RefreshRect(own_ship_update_rect, false);
6874
6875 ship_draw_last_rect = ship_draw_rect;
6876
6877 temp_dc.SelectObject(wxNullBitmap);
6878}
6879
6880void ChartCanvas::UpdateAlerts() {
6881 // Get the rectangle in the current dc which bounds the detected Alert
6882 // targets
6883
6884 // Use this dc
6885 wxClientDC dc(this);
6886
6887 // Get dc boundary
6888 int sx, sy;
6889 dc.GetSize(&sx, &sy);
6890
6891 // Need a bitmap
6892 wxBitmap test_bitmap(sx, sy, -1);
6893
6894 // Create a memory DC
6895 wxMemoryDC temp_dc;
6896 temp_dc.SelectObject(test_bitmap);
6897
6898 temp_dc.ResetBoundingBox();
6899 temp_dc.DestroyClippingRegion();
6900 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6901
6902 // Draw the Alert Targets on the temp_dc
6903 ocpnDC ocpndc = ocpnDC(temp_dc);
6904 AlertDraw(ocpndc);
6905
6906 // Retrieve the drawing extents
6907 wxRect alert_rect(temp_dc.MinX(), temp_dc.MinY(),
6908 temp_dc.MaxX() - temp_dc.MinX(),
6909 temp_dc.MaxY() - temp_dc.MinY());
6910
6911 if (!alert_rect.IsEmpty())
6912 alert_rect.Inflate(2); // clear all drawing artifacts
6913
6914 if (!alert_rect.IsEmpty() || !alert_draw_rect.IsEmpty()) {
6915 // The required invalidate rectangle is the union of the last drawn
6916 // rectangle and this drawn rectangle
6917 wxRect alert_update_rect = alert_draw_rect;
6918 alert_update_rect.Union(alert_rect);
6919
6920 // Invalidate the rectangular region
6921 RefreshRect(alert_update_rect, false);
6922 }
6923
6924 // Save this rectangle for next time
6925 alert_draw_rect = alert_rect;
6926
6927 temp_dc.SelectObject(wxNullBitmap); // clean up
6928}
6929
6930void ChartCanvas::UpdateAIS() {
6931 if (!g_pAIS) return;
6932
6933 // Get the rectangle in the current dc which bounds the detected AIS targets
6934
6935 // Use this dc
6936 wxClientDC dc(this);
6937
6938 // Get dc boundary
6939 int sx, sy;
6940 dc.GetSize(&sx, &sy);
6941
6942 wxRect ais_rect;
6943
6944 // How many targets are there?
6945
6946 // If more than "some number", it will be cheaper to refresh the entire
6947 // screen than to build update rectangles for each target.
6948 if (g_pAIS->GetTargetList().size() > 10) {
6949 ais_rect = wxRect(0, 0, sx, sy); // full screen
6950 } else {
6951 // Need a bitmap
6952 wxBitmap test_bitmap(sx, sy, -1);
6953
6954 // Create a memory DC
6955 wxMemoryDC temp_dc;
6956 temp_dc.SelectObject(test_bitmap);
6957
6958 temp_dc.ResetBoundingBox();
6959 temp_dc.DestroyClippingRegion();
6960 temp_dc.SetClippingRegion(wxRect(0, 0, sx, sy));
6961
6962 // Draw the AIS Targets on the temp_dc
6963 ocpnDC ocpndc = ocpnDC(temp_dc);
6964 AISDraw(ocpndc, GetVP(), this);
6965 AISDrawAreaNotices(ocpndc, GetVP(), this);
6966
6967 // Retrieve the drawing extents
6968 ais_rect =
6969 wxRect(temp_dc.MinX(), temp_dc.MinY(), temp_dc.MaxX() - temp_dc.MinX(),
6970 temp_dc.MaxY() - temp_dc.MinY());
6971
6972 if (!ais_rect.IsEmpty())
6973 ais_rect.Inflate(2); // clear all drawing artifacts
6974
6975 temp_dc.SelectObject(wxNullBitmap); // clean up
6976 }
6977
6978 if (!ais_rect.IsEmpty() || !ais_draw_rect.IsEmpty()) {
6979 // The required invalidate rectangle is the union of the last drawn
6980 // rectangle and this drawn rectangle
6981 wxRect ais_update_rect = ais_draw_rect;
6982 ais_update_rect.Union(ais_rect);
6983
6984 // Invalidate the rectangular region
6985 RefreshRect(ais_update_rect, false);
6986 }
6987
6988 // Save this rectangle for next time
6989 ais_draw_rect = ais_rect;
6990}
6991
6992void ChartCanvas::ToggleCPAWarn() {
6993 if (!g_AisFirstTimeUse) g_bCPAWarn = !g_bCPAWarn;
6994 wxString mess;
6995 if (g_bCPAWarn) {
6996 g_bTCPA_Max = true;
6997 mess = _("ON");
6998 } else {
6999 g_bTCPA_Max = false;
7000 mess = _("OFF");
7001 }
7002 // Print to status bar if available.
7003 if (STAT_FIELD_SCALE >= 4 && parent_frame->GetStatusBar()) {
7004 parent_frame->SetStatusText(_("CPA alarm ") + mess, STAT_FIELD_SCALE);
7005 } else {
7006 if (!g_AisFirstTimeUse) {
7007 OCPNMessageBox(this, _("CPA Alarm is switched") + " " + mess.MakeLower(),
7008 _("CPA") + " " + mess, 4, 4);
7009 }
7010 }
7011}
7012
7013void ChartCanvas::OnActivate(wxActivateEvent &event) { ReloadVP(); }
7014
7015void ChartCanvas::OnSize(wxSizeEvent &event) {
7016 if ((event.m_size.GetWidth() < 1) || (event.m_size.GetHeight() < 1)) return;
7017 // GetClientSize returns the size of the canvas area in logical pixels.
7018 GetClientSize(&m_canvas_width, &m_canvas_height);
7019
7020#ifdef __WXOSX__
7021 // Support scaled HDPI displays.
7022 m_displayScale = GetContentScaleFactor();
7023#endif
7024
7025 // Convert to physical pixels.
7026 m_canvas_width *= m_displayScale;
7027 m_canvas_height *= m_displayScale;
7028
7029 // Resize the current viewport
7030 VPoint.pix_width = m_canvas_width;
7031 VPoint.pix_height = m_canvas_height;
7032 VPoint.SetPixelScale(m_displayScale);
7033
7034 // Get some canvas metrics
7035
7036 // Rescale to current value, in order to rebuild VPoint data
7037 // structures for new canvas size
7039
7040 m_absolute_min_scale_ppm =
7041 m_canvas_width /
7042 (1.2 * WGS84_semimajor_axis_meters * PI); // something like 180 degrees
7043
7044 // Inform the parent Frame that I am being resized...
7045 gFrame->ProcessCanvasResize();
7046
7047 // if MUIBar is active, size the bar
7048 // if(g_useMUI && !m_muiBar){ // rebuild if
7049 // necessary
7050 // m_muiBar = new MUIBar(this, wxHORIZONTAL);
7051 // m_muiBarHOSize = m_muiBar->GetSize();
7052 // }
7053
7054 if (m_muiBar) {
7055 SetMUIBarPosition();
7056 UpdateFollowButtonState();
7057 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7058 }
7059
7060 // Set up the scroll margins
7061 xr_margin = m_canvas_width * 95 / 100;
7062 xl_margin = m_canvas_width * 5 / 100;
7063 yt_margin = m_canvas_height * 5 / 100;
7064 yb_margin = m_canvas_height * 95 / 100;
7065
7066 if (m_pQuilt)
7067 m_pQuilt->SetQuiltParameters(m_canvas_scale_factor, m_canvas_width);
7068
7069 // Resize the scratch BM
7070 delete pscratch_bm;
7071 pscratch_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7072 m_brepaint_piano = true;
7073
7074 // Resize the Route Calculation BM
7075 m_dc_route.SelectObject(wxNullBitmap);
7076 delete proute_bm;
7077 proute_bm = new wxBitmap(VPoint.pix_width, VPoint.pix_height, -1);
7078 m_dc_route.SelectObject(*proute_bm);
7079
7080 // Resize the saved Bitmap
7081 m_cached_chart_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7082
7083 // Resize the working Bitmap
7084 m_working_bm.Create(VPoint.pix_width, VPoint.pix_height, -1);
7085
7086 // Rescale again, to capture all the changes for new canvas size
7088
7089#ifdef ocpnUSE_GL
7090 if (/*g_bopengl &&*/ m_glcc) {
7091 // FIXME (dave) This can go away?
7092 m_glcc->OnSize(event);
7093 }
7094#endif
7095
7096 FormatPianoKeys();
7097 // Invalidate the whole window
7098 ReloadVP();
7099}
7100
7101void ChartCanvas::ProcessNewGUIScale() {
7102 // m_muiBar->Hide();
7103 delete m_muiBar;
7104 m_muiBar = 0;
7105
7106 CreateMUIBar();
7107}
7108
7109void ChartCanvas::CreateMUIBar() {
7110 if (g_useMUI && !m_muiBar) { // rebuild if necessary
7111 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7112 m_muiBar->SetColorScheme(m_cs);
7113 m_muiBarHOSize = m_muiBar->m_size;
7114 }
7115
7116 if (m_muiBar) {
7117 // We need to update the m_bENCGroup flag, not least for the initial
7118 // creation of a MUIBar
7119 if (ChartData) m_bENCGroup = ChartData->IsENCInGroup(m_groupIndex);
7120
7121 SetMUIBarPosition();
7122 UpdateFollowButtonState();
7123 m_muiBar->UpdateDynamicValues();
7124 m_muiBar->SetCanvasENCAvailable(m_bENCGroup);
7125 }
7126}
7127
7128void ChartCanvas::SetMUIBarPosition() {
7129 // if MUIBar is active, size the bar
7130 if (m_muiBar) {
7131 // We estimate the piano width based on the canvas width
7132 int pianoWidth = GetClientSize().x * 0.6f;
7133 // If the piano already exists, we can use its exact width
7134 // if(m_Piano)
7135 // pianoWidth = m_Piano->GetWidth();
7136
7137 if ((m_muiBar->GetOrientation() == wxHORIZONTAL)) {
7138 if (m_muiBarHOSize.x > (GetClientSize().x - pianoWidth)) {
7139 delete m_muiBar;
7140 m_muiBar = new MUIBar(this, wxVERTICAL, g_toolbar_scalefactor);
7141 m_muiBar->SetColorScheme(m_cs);
7142 }
7143 }
7144
7145 if ((m_muiBar->GetOrientation() == wxVERTICAL)) {
7146 if (m_muiBarHOSize.x < (GetClientSize().x - pianoWidth)) {
7147 delete m_muiBar;
7148 m_muiBar = new MUIBar(this, wxHORIZONTAL, g_toolbar_scalefactor);
7149 m_muiBar->SetColorScheme(m_cs);
7150 }
7151 }
7152
7153 m_muiBar->SetBestPosition();
7154 }
7155}
7156
7157void ChartCanvas::DestroyMuiBar() {
7158 if (m_muiBar) {
7159 delete m_muiBar;
7160 m_muiBar = NULL;
7161 }
7162}
7163
7164void ChartCanvas::ShowCompositeInfoWindow(
7165 int x, int n_charts, int scale, const std::vector<int> &index_vector) {
7166 if (n_charts > 0) {
7167 if (NULL == m_pCIWin) {
7168 m_pCIWin = new ChInfoWin(this);
7169 m_pCIWin->Hide();
7170 }
7171
7172 if (!m_pCIWin->IsShown() || (m_pCIWin->chart_scale != scale)) {
7173 wxString s;
7174
7175 s = _("Composite of ");
7176
7177 wxString s1;
7178 s1.Printf("%d ", n_charts);
7179 if (n_charts > 1)
7180 s1 += _("charts");
7181 else
7182 s1 += _("chart");
7183 s += s1;
7184 s += '\n';
7185
7186 s1.Printf(_("Chart scale"));
7187 s1 += ": ";
7188 wxString s2;
7189 s2.Printf("1:%d\n", scale);
7190 s += s1;
7191 s += s2;
7192
7193 s1 = _("Zoom in for more information");
7194 s += s1;
7195 s += '\n';
7196
7197 int char_width = s1.Length();
7198 int char_height = 3;
7199
7200 if (g_bChartBarEx) {
7201 s += '\n';
7202 int j = 0;
7203 for (int i : index_vector) {
7204 const ChartTableEntry &cte = ChartData->GetChartTableEntry(i);
7205 wxString path = cte.GetFullSystemPath();
7206 s += path;
7207 s += '\n';
7208 char_height++;
7209 char_width = wxMax(char_width, path.Length());
7210 if (j++ >= 9) break;
7211 }
7212 if (j >= 9) {
7213 s += " .\n .\n .\n";
7214 char_height += 3;
7215 }
7216 s += '\n';
7217 char_height += 1;
7218
7219 char_width += 4; // Fluff
7220 }
7221
7222 m_pCIWin->SetString(s);
7223
7224 m_pCIWin->FitToChars(char_width, char_height);
7225
7226 wxPoint p;
7227 p.x = x / GetContentScaleFactor();
7228 if ((p.x + m_pCIWin->GetWinSize().x) >
7229 (m_canvas_width / GetContentScaleFactor()))
7230 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7231 m_pCIWin->GetWinSize().x) /
7232 2; // centered
7233
7234 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7235 4 - m_pCIWin->GetWinSize().y;
7236
7237 m_pCIWin->dbIndex = 0;
7238 m_pCIWin->chart_scale = 0;
7239 m_pCIWin->SetPosition(p);
7240 m_pCIWin->SetBitmap();
7241 m_pCIWin->Refresh();
7242 m_pCIWin->Show();
7243 }
7244 } else {
7245 HideChartInfoWindow();
7246 }
7247}
7248
7249void ChartCanvas::ShowChartInfoWindow(int x, int dbIndex) {
7250 if (dbIndex >= 0) {
7251 if (NULL == m_pCIWin) {
7252 m_pCIWin = new ChInfoWin(this);
7253 m_pCIWin->Hide();
7254 }
7255
7256 if (!m_pCIWin->IsShown() || (m_pCIWin->dbIndex != dbIndex)) {
7257 wxString s;
7258 ChartBase *pc = NULL;
7259
7260 // TOCTOU race but worst case will reload chart.
7261 // need to lock it or the background spooler may evict charts in
7262 // OpenChartFromDBAndLock
7263 if ((ChartData->IsChartInCache(dbIndex)) && ChartData->IsValid())
7264 pc = ChartData->OpenChartFromDBAndLock(
7265 dbIndex, FULL_INIT); // this must come from cache
7266
7267 int char_width, char_height;
7268 s = ChartData->GetFullChartInfo(pc, dbIndex, &char_width, &char_height);
7269 if (pc) ChartData->UnLockCacheChart(dbIndex);
7270
7271 m_pCIWin->SetString(s);
7272 m_pCIWin->FitToChars(char_width, char_height);
7273
7274 wxPoint p;
7275 p.x = x / GetContentScaleFactor();
7276 if ((p.x + m_pCIWin->GetWinSize().x) >
7277 (m_canvas_width / GetContentScaleFactor()))
7278 p.x = ((m_canvas_width / GetContentScaleFactor()) -
7279 m_pCIWin->GetWinSize().x) /
7280 2; // centered
7281
7282 p.y = (m_canvas_height - m_Piano->GetHeight()) / GetContentScaleFactor() -
7283 4 - m_pCIWin->GetWinSize().y;
7284
7285 m_pCIWin->dbIndex = dbIndex;
7286 m_pCIWin->SetPosition(p);
7287 m_pCIWin->SetBitmap();
7288 m_pCIWin->Refresh();
7289 m_pCIWin->Show();
7290 }
7291 } else {
7292 HideChartInfoWindow();
7293 }
7294}
7295
7296void ChartCanvas::HideChartInfoWindow() {
7297 if (m_pCIWin /*&& m_pCIWin->IsShown()*/) {
7298 m_pCIWin->Hide();
7299 m_pCIWin->Destroy();
7300 m_pCIWin = NULL;
7301
7302#ifdef __ANDROID__
7303 androidForceFullRepaint();
7304#endif
7305 }
7306}
7307
7308void ChartCanvas::PanTimerEvent(wxTimerEvent &event) {
7309 wxMouseEvent ev(wxEVT_MOTION);
7310 ev.m_x = mouse_x;
7311 ev.m_y = mouse_y;
7312 ev.m_leftDown = mouse_leftisdown;
7313
7314 wxEvtHandler *evthp = GetEventHandler();
7315
7316 ::wxPostEvent(evthp, ev);
7317}
7318
7319void ChartCanvas::MovementTimerEvent(wxTimerEvent &) {
7320 if ((m_panx_target_final - m_panx_target_now) ||
7321 (m_pany_target_final - m_pany_target_now)) {
7322 DoTimedMovementTarget();
7323 } else
7324 DoTimedMovement();
7325}
7326
7327void ChartCanvas::MovementStopTimerEvent(wxTimerEvent &) { StopMovement(); }
7328
7329bool ChartCanvas::CheckEdgePan(int x, int y, bool bdragging, int margin,
7330 int delta) {
7331 if (m_disable_edge_pan) return false;
7332
7333 bool bft = false;
7334 int pan_margin = m_canvas_width * margin / 100;
7335 int pan_timer_set = 200;
7336 double pan_delta = GetVP().pix_width * delta / 100;
7337 int pan_x = 0;
7338 int pan_y = 0;
7339
7340 if (x > m_canvas_width - pan_margin) {
7341 bft = true;
7342 pan_x = pan_delta;
7343 }
7344
7345 else if (x < pan_margin) {
7346 bft = true;
7347 pan_x = -pan_delta;
7348 }
7349
7350 if (y < pan_margin) {
7351 bft = true;
7352 pan_y = -pan_delta;
7353 }
7354
7355 else if (y > m_canvas_height - pan_margin) {
7356 bft = true;
7357 pan_y = pan_delta;
7358 }
7359
7360 // Of course, if dragging, and the mouse left button is not down, we must
7361 // stop the event injection
7362 if (bdragging) {
7363 if (!g_btouch) {
7364 wxMouseState state = ::wxGetMouseState();
7365#if wxCHECK_VERSION(3, 0, 0)
7366 if (!state.LeftIsDown())
7367#else
7368 if (!state.LeftDown())
7369#endif
7370 bft = false;
7371 }
7372 }
7373 if ((bft) && !pPanTimer->IsRunning()) {
7374 PanCanvas(pan_x, pan_y);
7375 pPanTimer->Start(pan_timer_set, wxTIMER_ONE_SHOT);
7376 return true;
7377 }
7378
7379 // This mouse event must not be due to pan timer event injector
7380 // Mouse is out of the pan zone, so prevent any orphan event injection
7381 if ((!bft) && pPanTimer->IsRunning()) {
7382 pPanTimer->Stop();
7383 }
7384
7385 return (false);
7386}
7387
7388// Look for waypoints at the current position.
7389// Used to determine what a mouse event should act on.
7390
7391void ChartCanvas::FindRoutePointsAtCursor(float selectRadius,
7392 bool setBeingEdited) {
7393 m_lastRoutePointEditTarget = m_pRoutePointEditTarget; // save a copy
7394 m_pRoutePointEditTarget = NULL;
7395 m_pFoundPoint = NULL;
7396
7397 SelectItem *pFind = NULL;
7398 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7399 SelectableItemList SelList = pSelect->FindSelectionList(
7400 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
7401 for (SelectItem *pFind : SelList) {
7402 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
7403
7404 // Get an array of all routes using this point
7405 m_pEditRouteArray = g_pRouteMan->GetRouteArrayContaining(frp);
7406 // TODO: delete m_pEditRouteArray after use?
7407
7408 // Use route array to determine actual visibility for the point
7409 bool brp_viz = false;
7410 if (m_pEditRouteArray) {
7411 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7412 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7413 if (pr->IsVisible()) {
7414 brp_viz = true;
7415 break;
7416 }
7417 }
7418 } else
7419 brp_viz = frp->IsVisible(); // isolated point
7420
7421 if (brp_viz) {
7422 // Use route array to rubberband all affected routes
7423 if (m_pEditRouteArray) // Editing Waypoint as part of route
7424 {
7425 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
7426 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
7427 pr->m_bIsBeingEdited = setBeingEdited;
7428 }
7429 m_bRouteEditing = setBeingEdited;
7430 } else // editing Mark
7431 {
7432 frp->m_bRPIsBeingEdited = setBeingEdited;
7433 m_bMarkEditing = setBeingEdited;
7434 }
7435
7436 m_pRoutePointEditTarget = frp;
7437 m_pFoundPoint = pFind;
7438 break; // out of the while(node)
7439 }
7440 } // for (SelectItem...
7441}
7442std::shared_ptr<HostApi121::PiPointContext>
7443ChartCanvas::GetCanvasContextAtPoint(int x, int y) {
7444 // General Right Click
7445 // Look for selectable objects
7446 double slat, slon;
7447 GetCanvasPixPoint(x, y, slat, slon);
7448
7449 SelectItem *pFindAIS;
7450 SelectItem *pFindRP;
7451 SelectItem *pFindRouteSeg;
7452 SelectItem *pFindTrackSeg;
7453 SelectItem *pFindCurrent = NULL;
7454 SelectItem *pFindTide = NULL;
7455
7456 // Get all the selectable things at the selected point
7457 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7458 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
7459 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7460 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7461 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7462
7463 if (m_bShowCurrent)
7464 pFindCurrent =
7465 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
7466
7467 if (m_bShowTide) // look for tide stations
7468 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
7469
7470 int seltype = 0;
7471
7472 // Try for AIS targets first
7473 int FoundAIS_MMSI = 0;
7474 if (pFindAIS) {
7475 FoundAIS_MMSI = pFindAIS->GetUserData();
7476
7477 // Make sure the target data is available
7478 if (g_pAIS->Get_Target_Data_From_MMSI(FoundAIS_MMSI))
7479 seltype |= SELTYPE_AISTARGET;
7480 }
7481
7482 // Now the various Route Parts
7483
7484 RoutePoint *FoundRoutePoint = NULL;
7485 Route *SelectedRoute = NULL;
7486
7487 if (pFindRP) {
7488 RoutePoint *pFirstVizPoint = NULL;
7489 RoutePoint *pFoundActiveRoutePoint = NULL;
7490 RoutePoint *pFoundVizRoutePoint = NULL;
7491 Route *pSelectedActiveRoute = NULL;
7492 Route *pSelectedVizRoute = NULL;
7493
7494 // There is at least one routepoint, so get the whole list
7495 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7496 SelectableItemList SelList =
7497 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
7498 for (SelectItem *pFindSel : SelList) {
7499 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
7500
7501 // Get an array of all routes using this point
7502 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
7503
7504 // Use route array (if any) to determine actual visibility for this point
7505 bool brp_viz = false;
7506 if (proute_array) {
7507 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7508 Route *pr = (Route *)proute_array->Item(ir);
7509 if (pr->IsVisible()) {
7510 brp_viz = true;
7511 break;
7512 }
7513 }
7514 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
7515 // but still exists as a waypoint
7516 brp_viz = prp->IsVisible(); // so treat as isolated point
7517
7518 } else
7519 brp_viz = prp->IsVisible(); // isolated point
7520
7521 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
7522
7523 // Use route array to choose the appropriate route
7524 // Give preference to any active route, otherwise select the first visible
7525 // route in the array for this point
7526 if (proute_array) {
7527 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7528 Route *pr = (Route *)proute_array->Item(ir);
7529 if (pr->m_bRtIsActive) {
7530 pSelectedActiveRoute = pr;
7531 pFoundActiveRoutePoint = prp;
7532 break;
7533 }
7534 }
7535
7536 if (NULL == pSelectedVizRoute) {
7537 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
7538 Route *pr = (Route *)proute_array->Item(ir);
7539 if (pr->IsVisible()) {
7540 pSelectedVizRoute = pr;
7541 pFoundVizRoutePoint = prp;
7542 break;
7543 }
7544 }
7545 }
7546
7547 delete proute_array;
7548 }
7549 }
7550
7551 // Now choose the "best" selections
7552 if (pFoundActiveRoutePoint) {
7553 FoundRoutePoint = pFoundActiveRoutePoint;
7554 SelectedRoute = pSelectedActiveRoute;
7555 } else if (pFoundVizRoutePoint) {
7556 FoundRoutePoint = pFoundVizRoutePoint;
7557 SelectedRoute = pSelectedVizRoute;
7558 } else
7559 // default is first visible point in list
7560 FoundRoutePoint = pFirstVizPoint;
7561
7562 if (SelectedRoute) {
7563 if (SelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
7564 } else if (FoundRoutePoint) {
7565 seltype |= SELTYPE_MARKPOINT;
7566 }
7567
7568 // Highlight the selected point, to verify the proper right click selection
7569#if 0
7570 if (m_pFoundRoutePoint) {
7571 m_pFoundRoutePoint->m_bPtIsSelected = true;
7572 wxRect wp_rect;
7573 RoutePointGui(*m_pFoundRoutePoint)
7574 .CalculateDCRect(m_dc_route, this, &wp_rect);
7575 RefreshRect(wp_rect, true);
7576 }
7577#endif
7578 }
7579
7580 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
7581 // routes But call the popup handler with identifier appropriate to the type
7582 if (pFindRouteSeg) // there is at least one select item
7583 {
7584 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7585 SelectableItemList SelList =
7586 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
7587
7588 if (NULL == SelectedRoute) // the case where a segment only is selected
7589 {
7590 // Choose the first visible route containing segment in the list
7591 for (SelectItem *pFindSel : SelList) {
7592 Route *pr = (Route *)pFindSel->m_pData3;
7593 if (pr->IsVisible()) {
7594 SelectedRoute = pr;
7595 break;
7596 }
7597 }
7598 }
7599
7600 if (SelectedRoute) {
7601 if (NULL == FoundRoutePoint)
7602 FoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
7603
7604 SelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
7605 seltype |= SELTYPE_ROUTESEGMENT;
7606 }
7607 }
7608
7609 if (pFindTrackSeg) {
7610 m_pSelectedTrack = NULL;
7611 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
7612 SelectableItemList SelList =
7613 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
7614
7615 // Choose the first visible track containing segment in the list
7616 for (SelectItem *pFindSel : SelList) {
7617 Track *pt = (Track *)pFindSel->m_pData3;
7618 if (pt->IsVisible()) {
7619 m_pSelectedTrack = pt;
7620 break;
7621 }
7622 }
7623 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
7624 }
7625
7626 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
7627
7628 // Populate the return struct
7629 auto rstruct = std::make_shared<HostApi121::PiPointContext>();
7630 rstruct->object_type = HostApi121::PiContextObjectType::kObjectChart;
7631 rstruct->object_ident = "";
7632
7633 if (seltype == SELTYPE_AISTARGET) {
7634 rstruct->object_type = HostApi121::PiContextObjectType::kObjectAisTarget;
7635 wxString val;
7636 val.Printf("%d", FoundAIS_MMSI);
7637 rstruct->object_ident = val.ToStdString();
7638 } else if (seltype & SELTYPE_MARKPOINT) {
7639 if (FoundRoutePoint) {
7640 rstruct->object_type = HostApi121::PiContextObjectType::kObjectRoutepoint;
7641 rstruct->object_ident = FoundRoutePoint->m_GUID.ToStdString();
7642 }
7643 } else if (seltype & SELTYPE_ROUTESEGMENT) {
7644 if (SelectedRoute) {
7645 rstruct->object_type =
7646 HostApi121::PiContextObjectType::kObjectRoutesegment;
7647 rstruct->object_ident = SelectedRoute->m_GUID.ToStdString();
7648 }
7649 } else if (seltype & SELTYPE_TRACKSEGMENT) {
7650 if (m_pSelectedTrack) {
7651 rstruct->object_type =
7652 HostApi121::PiContextObjectType::kObjectTracksegment;
7653 rstruct->object_ident = m_pSelectedTrack->m_GUID.ToStdString();
7654 }
7655 }
7656
7657 return rstruct;
7658}
7659
7660void ChartCanvas::MouseTimedEvent(wxTimerEvent &event) {
7661 if (singleClickEventIsValid) MouseEvent(singleClickEvent);
7662 singleClickEventIsValid = false;
7663 m_DoubleClickTimer->Stop();
7664}
7665
7666bool leftIsDown;
7667
7668bool ChartCanvas::MouseEventOverlayWindows(wxMouseEvent &event) {
7669 if (!m_bChartDragging && !m_bDrawingRoute) {
7670 /*
7671 * The m_Compass->GetRect() coordinates are in physical pixels, whereas the
7672 * mouse event coordinates are in logical pixels.
7673 */
7674 if (m_Compass && m_Compass->IsShown()) {
7675 wxRect logicalRect = m_Compass->GetLogicalRect();
7676 bool isInCompass = logicalRect.Contains(event.GetPosition());
7677 if (isInCompass || m_mouseWasInCompass) {
7678 if (m_Compass->MouseEvent(event)) {
7679 cursor_region = CENTER;
7680 if (!g_btouch) SetCanvasCursor(event);
7681 m_mouseWasInCompass = isInCompass;
7682 return true;
7683 }
7684 }
7685 m_mouseWasInCompass = isInCompass;
7686 }
7687
7688 if (m_notification_button && m_notification_button->IsShown()) {
7689 wxRect logicalRect = m_notification_button->GetLogicalRect();
7690 bool isinButton = logicalRect.Contains(event.GetPosition());
7691 if (isinButton) {
7692 SetCursor(*pCursorArrow);
7693 if (event.LeftDown()) HandleNotificationMouseClick();
7694 return true;
7695 }
7696 }
7697
7698 if (MouseEventToolbar(event)) return true;
7699
7700 if (MouseEventChartBar(event)) return true;
7701
7702 if (MouseEventMUIBar(event)) return true;
7703
7704 if (MouseEventIENCBar(event)) return true;
7705 }
7706 return false;
7707}
7708
7709void ChartCanvas::HandleNotificationMouseClick() {
7710 if (!m_NotificationsList) {
7711 m_NotificationsList = new NotificationsList(this);
7712
7713 // calculate best size for Notification list
7714 m_NotificationsList->RecalculateSize();
7715 m_NotificationsList->Hide();
7716 }
7717
7718 if (m_NotificationsList->IsShown()) {
7719 m_NotificationsList->Hide();
7720 } else {
7721 m_NotificationsList->RecalculateSize();
7722 m_NotificationsList->ReloadNotificationList();
7723 m_NotificationsList->Show();
7724 }
7725}
7726bool ChartCanvas::MouseEventChartBar(wxMouseEvent &event) {
7727 if (!g_bShowChartBar) return false;
7728
7729 if (!m_Piano->MouseEvent(event)) return false;
7730
7731 cursor_region = CENTER;
7732 if (!g_btouch) SetCanvasCursor(event);
7733 return true;
7734}
7735
7736bool ChartCanvas::MouseEventToolbar(wxMouseEvent &event) {
7737 if (!IsPrimaryCanvas()) return false;
7738
7739 if (g_MainToolbar) {
7740 if (!g_MainToolbar->MouseEvent(event))
7741 return false;
7742 else
7743 g_MainToolbar->RefreshToolbar();
7744 }
7745
7746 cursor_region = CENTER;
7747 if (!g_btouch) SetCanvasCursor(event);
7748 return true;
7749}
7750
7751bool ChartCanvas::MouseEventIENCBar(wxMouseEvent &event) {
7752 if (!IsPrimaryCanvas()) return false;
7753
7754 if (g_iENCToolbar) {
7755 if (!g_iENCToolbar->MouseEvent(event))
7756 return false;
7757 else {
7758 g_iENCToolbar->RefreshToolbar();
7759 return true;
7760 }
7761 }
7762 return false;
7763}
7764
7765bool ChartCanvas::MouseEventMUIBar(wxMouseEvent &event) {
7766 if (m_muiBar) {
7767 if (!m_muiBar->MouseEvent(event)) return false;
7768 }
7769
7770 cursor_region = CENTER;
7771 if (!g_btouch) SetCanvasCursor(event);
7772 if (m_muiBar)
7773 return true;
7774 else
7775 return false;
7776}
7777
7778bool ChartCanvas::MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick) {
7779 int x, y;
7780
7781 bool bret = false;
7782
7783 event.GetPosition(&x, &y);
7784
7785 x *= m_displayScale;
7786 y *= m_displayScale;
7787
7788 m_MouseDragging = event.Dragging();
7789
7790 // Some systems produce null drag events, where the pointer position has not
7791 // changed from the previous value. Detect this case, and abort further
7792 // processing (FS#1748)
7793#ifdef __WXMSW__
7794 if (event.Dragging()) {
7795 if ((x == mouse_x) && (y == mouse_y)) return true;
7796 }
7797#endif
7798
7799 mouse_x = x;
7800 mouse_y = y;
7801 mouse_leftisdown = event.LeftDown();
7803
7804 // Establish the event region
7805 cursor_region = CENTER;
7806
7807 int chartbar_height = GetChartbarHeight();
7808
7809 if (m_Compass && m_Compass->IsShown() &&
7810 m_Compass->GetRect().Contains(event.GetPosition())) {
7811 cursor_region = CENTER;
7812 } else if (x > xr_margin) {
7813 cursor_region = MID_RIGHT;
7814 } else if (x < xl_margin) {
7815 cursor_region = MID_LEFT;
7816 } else if (y > yb_margin - chartbar_height &&
7817 y < m_canvas_height - chartbar_height) {
7818 cursor_region = MID_TOP;
7819 } else if (y < yt_margin) {
7820 cursor_region = MID_BOT;
7821 } else {
7822 cursor_region = CENTER;
7823 }
7824
7825 if (!g_btouch) SetCanvasCursor(event);
7826
7827 // Protect from leftUp's coming from event handlers in child
7828 // windows who return focus to the canvas.
7829 leftIsDown = event.LeftDown();
7830
7831#ifndef __WXOSX__
7832 if (event.LeftDown()) {
7833 if (g_bShowMenuBar == false && g_bTempShowMenuBar == true) {
7834 // The menu bar is temporarily visible due to alt having been pressed.
7835 // Clicking will hide it, and do nothing else.
7836 g_bTempShowMenuBar = false;
7837 parent_frame->ApplyGlobalSettings(false);
7838 return (true);
7839 }
7840 }
7841#endif
7842
7843 // Update modifiers here; some window managers never send the key event
7844 m_modkeys = 0;
7845 if (event.ControlDown()) m_modkeys |= wxMOD_CONTROL;
7846 if (event.AltDown()) m_modkeys |= wxMOD_ALT;
7847
7848#ifdef __WXMSW__
7849 // TODO Test carefully in other platforms, remove ifdef....
7850 if (event.ButtonDown() && !HasCapture()) CaptureMouse();
7851 if (event.ButtonUp() && HasCapture()) ReleaseMouse();
7852#endif
7853
7854 event.SetEventObject(this);
7855 if (SendMouseEventToPlugins(event))
7856 return (true); // PlugIn did something, and does not want the canvas to
7857 // do anything else
7858
7859 // Capture LeftUp's and time them, unless it already came from the timer.
7860
7861 // Detect end of chart dragging
7862 if (g_btouch && !m_inPinch && m_bChartDragging && event.LeftUp()) {
7863 StartChartDragInertia();
7864 }
7865
7866 if (!g_btouch && b_handle_dclick && event.LeftUp() &&
7867 !singleClickEventIsValid) {
7868 // Ignore the second LeftUp after the DClick.
7869 if (m_DoubleClickTimer->IsRunning()) {
7870 m_DoubleClickTimer->Stop();
7871 return (true);
7872 }
7873
7874 // Save the event for later running if there is no DClick.
7875 m_DoubleClickTimer->Start(350, wxTIMER_ONE_SHOT);
7876 singleClickEvent = event;
7877 singleClickEventIsValid = true;
7878 return (true);
7879 }
7880
7881 // This logic is necessary on MSW to handle the case where
7882 // a context (right-click) menu is dismissed without action
7883 // by clicking on the chart surface.
7884 // We need to avoid an unintentional pan by eating some clicks...
7885#ifdef __WXMSW__
7886 if (event.LeftDown() || event.LeftUp() || event.Dragging()) {
7887 if (g_click_stop > 0) {
7888 g_click_stop--;
7889 return (true);
7890 }
7891 }
7892#endif
7893
7894 // Kick off the Rotation control timer
7895 if (GetUpMode() == COURSE_UP_MODE) {
7896 m_b_rot_hidef = false;
7897 pRotDefTimer->Start(500, wxTIMER_ONE_SHOT);
7898 } else
7899 pRotDefTimer->Stop();
7900
7901 // Retrigger the route leg / AIS target popup timer
7902 bool bRoll = !g_btouch;
7903#ifdef __ANDROID__
7904 bRoll = g_bRollover;
7905#endif
7906 if (bRoll) {
7907 if ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
7908 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
7909 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive()))
7910 m_RolloverPopupTimer.Start(
7911 10,
7912 wxTIMER_ONE_SHOT); // faster response while the rollover is turned on
7913 else
7914 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec, wxTIMER_ONE_SHOT);
7915 }
7916
7917 // Retrigger the cursor tracking timer
7918 pCurTrackTimer->Start(m_curtrack_timer_msec, wxTIMER_ONE_SHOT);
7919
7920// Show cursor position on Status Bar, if present
7921// except for GTK, under which status bar updates are very slow
7922// due to Update() call.
7923// In this case, as a workaround, update the status window
7924// after an interval timer (pCurTrackTimer) pops, which will happen
7925// whenever the mouse has stopped moving for specified interval.
7926// See the method OnCursorTrackTimerEvent()
7927#if !defined(__WXGTK__) && !defined(__WXQT__)
7928 SetCursorStatus(m_cursor_lat, m_cursor_lon);
7929#endif
7930
7931 // Send the current cursor lat/lon to all PlugIns requesting it
7932 if (g_pi_manager) {
7933 // Occasionally, MSW will produce nonsense events on right click....
7934 // This results in an error in cursor geo position, so we skip this case
7935 if ((x >= 0) && (y >= 0))
7936 SendCursorLatLonToAllPlugIns(m_cursor_lat, m_cursor_lon);
7937 }
7938
7939 if (!g_btouch) {
7940 if ((m_bMeasure_Active && (m_nMeasureState >= 2)) || (m_routeState > 1)) {
7941 wxPoint p = ClientToScreen(wxPoint(x, y));
7942 }
7943 }
7944
7945 if (1 ) {
7946 // Route Creation Rubber Banding
7947 if (m_routeState >= 2) {
7948 r_rband.x = x;
7949 r_rband.y = y;
7950 m_bDrawingRoute = true;
7951
7952 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7953 Refresh(false);
7954 }
7955
7956 // Measure Tool Rubber Banding
7957 if (m_bMeasure_Active && (m_nMeasureState >= 2)) {
7958 r_rband.x = x;
7959 r_rband.y = y;
7960 m_bDrawingRoute = true;
7961
7962 if (!g_btouch) CheckEdgePan(x, y, event.Dragging(), 5, 2);
7963 Refresh(false);
7964 }
7965 }
7966 return bret;
7967}
7968
7969int ChartCanvas::PrepareContextSelections(double lat, double lon) {
7970 // On general Right Click
7971 // Look for selectable objects
7972 double slat = lat;
7973 double slon = lon;
7974
7975#if defined(__WXMAC__) || defined(__ANDROID__)
7976 wxScreenDC sdc;
7977 ocpnDC dc(sdc);
7978#else
7979 wxClientDC cdc(GetParent());
7980 ocpnDC dc(cdc);
7981#endif
7982
7983 SelectItem *pFindAIS;
7984 SelectItem *pFindRP;
7985 SelectItem *pFindRouteSeg;
7986 SelectItem *pFindTrackSeg;
7987 SelectItem *pFindCurrent = NULL;
7988 SelectItem *pFindTide = NULL;
7989
7990 // Deselect any current objects
7991 if (m_pSelectedRoute) {
7992 m_pSelectedRoute->m_bRtIsSelected = false; // Only one selection at a time
7993 m_pSelectedRoute->DeSelectRoute();
7994#ifdef ocpnUSE_GL
7995 if (g_bopengl && m_glcc) {
7996 InvalidateGL();
7997 Update();
7998 } else
7999#endif
8000 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8001 }
8002
8003 if (m_pFoundRoutePoint) {
8004 m_pFoundRoutePoint->m_bPtIsSelected = false;
8005 RoutePointGui(*m_pFoundRoutePoint).Draw(dc, this);
8006 RefreshRect(m_pFoundRoutePoint->CurrentRect_in_DC);
8007 }
8008
8011 if (g_btouch && m_pRoutePointEditTarget) {
8012 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8013 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8014 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8015 }
8016
8017 // Get all the selectable things at the cursor
8018 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8019 pFindAIS = pSelectAIS->FindSelection(ctx, slat, slon, SELTYPE_AISTARGET);
8020 pFindRP = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8021 pFindRouteSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8022 pFindTrackSeg = pSelect->FindSelection(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8023
8024 if (m_bShowCurrent)
8025 pFindCurrent =
8026 pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_CURRENTPOINT);
8027
8028 if (m_bShowTide) // look for tide stations
8029 pFindTide = pSelectTC->FindSelection(ctx, slat, slon, SELTYPE_TIDEPOINT);
8030
8031 int seltype = 0;
8032
8033 // Try for AIS targets first
8034 if (pFindAIS) {
8035 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8036
8037 // Make sure the target data is available
8038 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI))
8039 seltype |= SELTYPE_AISTARGET;
8040 }
8041
8042 // Now examine the various Route parts
8043
8044 m_pFoundRoutePoint = NULL;
8045 if (pFindRP) {
8046 RoutePoint *pFirstVizPoint = NULL;
8047 RoutePoint *pFoundActiveRoutePoint = NULL;
8048 RoutePoint *pFoundVizRoutePoint = NULL;
8049 Route *pSelectedActiveRoute = NULL;
8050 Route *pSelectedVizRoute = NULL;
8051
8052 // There is at least one routepoint, so get the whole list
8053 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8054 SelectableItemList SelList =
8055 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTEPOINT);
8056 for (SelectItem *pFindSel : SelList) {
8057 RoutePoint *prp = (RoutePoint *)pFindSel->m_pData1; // candidate
8058
8059 // Get an array of all routes using this point
8060 wxArrayPtrVoid *proute_array = g_pRouteMan->GetRouteArrayContaining(prp);
8061
8062 // Use route array (if any) to determine actual visibility for this point
8063 bool brp_viz = false;
8064 if (proute_array) {
8065 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8066 Route *pr = (Route *)proute_array->Item(ir);
8067 if (pr->IsVisible()) {
8068 brp_viz = true;
8069 break;
8070 }
8071 }
8072 if (!brp_viz && prp->IsShared()) // is not visible as part of route,
8073 // but still exists as a waypoint
8074 brp_viz = prp->IsVisible(); // so treat as isolated point
8075
8076 } else
8077 brp_viz = prp->IsVisible(); // isolated point
8078
8079 if ((NULL == pFirstVizPoint) && brp_viz) pFirstVizPoint = prp;
8080
8081 // Use route array to choose the appropriate route
8082 // Give preference to any active route, otherwise select the first visible
8083 // route in the array for this point
8084 m_pSelectedRoute = NULL;
8085 if (proute_array) {
8086 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8087 Route *pr = (Route *)proute_array->Item(ir);
8088 if (pr->m_bRtIsActive) {
8089 pSelectedActiveRoute = pr;
8090 pFoundActiveRoutePoint = prp;
8091 break;
8092 }
8093 }
8094
8095 if (NULL == pSelectedVizRoute) {
8096 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8097 Route *pr = (Route *)proute_array->Item(ir);
8098 if (pr->IsVisible()) {
8099 pSelectedVizRoute = pr;
8100 pFoundVizRoutePoint = prp;
8101 break;
8102 }
8103 }
8104 }
8105
8106 delete proute_array;
8107 }
8108 }
8109
8110 // Now choose the "best" selections
8111 if (pFoundActiveRoutePoint) {
8112 m_pFoundRoutePoint = pFoundActiveRoutePoint;
8113 m_pSelectedRoute = pSelectedActiveRoute;
8114 } else if (pFoundVizRoutePoint) {
8115 m_pFoundRoutePoint = pFoundVizRoutePoint;
8116 m_pSelectedRoute = pSelectedVizRoute;
8117 } else
8118 // default is first visible point in list
8119 m_pFoundRoutePoint = pFirstVizPoint;
8120
8121 if (m_pSelectedRoute) {
8122 if (m_pSelectedRoute->IsVisible()) seltype |= SELTYPE_ROUTEPOINT;
8123 } else if (m_pFoundRoutePoint) {
8124 seltype |= SELTYPE_MARKPOINT;
8125 }
8126
8127 // Highlight the selected point, to verify the proper right click selection
8128 if (m_pFoundRoutePoint) {
8129 m_pFoundRoutePoint->m_bPtIsSelected = true;
8130 wxRect wp_rect;
8131 RoutePointGui(*m_pFoundRoutePoint)
8132 .CalculateDCRect(m_dc_route, this, &wp_rect);
8133 RefreshRect(wp_rect, true);
8134 }
8135 }
8136
8137 // Note here that we use SELTYPE_ROUTESEGMENT to select tracks as well as
8138 // routes But call the popup handler with identifier appropriate to the type
8139 if (pFindRouteSeg) // there is at least one select item
8140 {
8141 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8142 SelectableItemList SelList =
8143 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_ROUTESEGMENT);
8144
8145 if (NULL == m_pSelectedRoute) // the case where a segment only is selected
8146 {
8147 // Choose the first visible route containing segment in the list
8148 for (SelectItem *pFindSel : SelList) {
8149 Route *pr = (Route *)pFindSel->m_pData3;
8150 if (pr->IsVisible()) {
8151 m_pSelectedRoute = pr;
8152 break;
8153 }
8154 }
8155 }
8156
8157 if (m_pSelectedRoute) {
8158 if (NULL == m_pFoundRoutePoint)
8159 m_pFoundRoutePoint = (RoutePoint *)pFindRouteSeg->m_pData1;
8160
8161 m_pSelectedRoute->m_bRtIsSelected = !(seltype & SELTYPE_ROUTEPOINT);
8162 if (m_pSelectedRoute->m_bRtIsSelected) {
8163#ifdef ocpnUSE_GL
8164 if (g_bopengl && m_glcc) {
8165 InvalidateGL();
8166 Update();
8167 } else
8168#endif
8169 RouteGui(*m_pSelectedRoute).Draw(dc, this, GetVP().GetBBox());
8170 }
8171 seltype |= SELTYPE_ROUTESEGMENT;
8172 }
8173 }
8174
8175 if (pFindTrackSeg) {
8176 m_pSelectedTrack = NULL;
8177 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8178 SelectableItemList SelList =
8179 pSelect->FindSelectionList(ctx, slat, slon, SELTYPE_TRACKSEGMENT);
8180
8181 // Choose the first visible track containing segment in the list
8182 for (SelectItem *pFindSel : SelList) {
8183 Track *pt = (Track *)pFindSel->m_pData3;
8184 if (pt->IsVisible()) {
8185 m_pSelectedTrack = pt;
8186 break;
8187 }
8188 }
8189 if (m_pSelectedTrack) seltype |= SELTYPE_TRACKSEGMENT;
8190 }
8191
8192#if 0 // disable tide and current graph on right click
8193 {
8194 if (pFindCurrent) {
8195 m_pIDXCandidate = FindBestCurrentObject(slat, slon);
8196 seltype |= SELTYPE_CURRENTPOINT;
8197 }
8198
8199 else if (pFindTide) {
8200 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8201 seltype |= SELTYPE_TIDEPOINT;
8202 }
8203 }
8204#endif
8205
8206 if (0 == seltype) seltype |= SELTYPE_UNKNOWN;
8207
8208 return seltype;
8209}
8210
8211IDX_entry *ChartCanvas::FindBestCurrentObject(double lat, double lon) {
8212 // There may be multiple current entries at the same point.
8213 // For example, there often is a current substation (with directions
8214 // specified) co-located with its master. We want to select the
8215 // substation, so that the direction will be properly indicated on the
8216 // graphic. So, we search the select list looking for IDX_type == 'c' (i.e
8217 // substation)
8218 IDX_entry *pIDX_best_candidate;
8219
8220 SelectItem *pFind = NULL;
8221 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8222 SelectableItemList SelList =
8223 pSelectTC->FindSelectionList(ctx, lat, lon, SELTYPE_CURRENTPOINT);
8224
8225 // Default is first entry
8226 pFind = *SelList.begin();
8227 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8228
8229 auto node = SelList.begin();
8230 if (SelList.size() > 1) {
8231 for (++node; node != SelList.end(); ++node) {
8232 pFind = *node;
8233 IDX_entry *pIDX_candidate = (IDX_entry *)(pFind->m_pData1);
8234 if (pIDX_candidate->IDX_type == 'c') {
8235 pIDX_best_candidate = pIDX_candidate;
8236 break;
8237 }
8238 } // while (node)
8239 } else {
8240 pFind = *SelList.begin();
8241 pIDX_best_candidate = (IDX_entry *)(pFind->m_pData1);
8242 }
8243
8244 return pIDX_best_candidate;
8245}
8246void ChartCanvas::CallPopupMenu(int x, int y) {
8247 last_drag.x = x;
8248 last_drag.y = y;
8249 if (m_routeState) { // creating route?
8250 InvokeCanvasMenu(x, y, SELTYPE_ROUTECREATE);
8251 return;
8252 }
8253
8255
8256 // If tide or current point is selected, then show the TC dialog immediately
8257 // without context menu
8258 if (SELTYPE_CURRENTPOINT == seltype) {
8259 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8260 Refresh(false);
8261 return;
8262 }
8263
8264 if (SELTYPE_TIDEPOINT == seltype) {
8265 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8266 Refresh(false);
8267 return;
8268 }
8269
8270 InvokeCanvasMenu(x, y, seltype);
8271
8272 // Clean up if not deleted in InvokeCanvasMenu
8273 if (m_pSelectedRoute && g_pRouteMan->IsRouteValid(m_pSelectedRoute)) {
8274 m_pSelectedRoute->m_bRtIsSelected = false;
8275 }
8276
8277 m_pSelectedRoute = NULL;
8278
8279 if (m_pFoundRoutePoint) {
8280 if (pSelect->IsSelectableRoutePointValid(m_pFoundRoutePoint))
8281 m_pFoundRoutePoint->m_bPtIsSelected = false;
8282 }
8283 m_pFoundRoutePoint = NULL;
8284
8285 Refresh(true);
8286 // Refresh(false); // needed for MSW, not GTK Why??
8287}
8288
8289bool ChartCanvas::MouseEventProcessObjects(wxMouseEvent &event) {
8290 // For now just bail out completely if the point clicked is not on the chart
8291 if (std::isnan(m_cursor_lat)) return false;
8292
8293 // Mouse Clicks
8294 bool ret = false; // return true if processed
8295
8296 int x, y, mx, my;
8297 event.GetPosition(&x, &y);
8298 mx = x;
8299 my = y;
8300
8301 // Calculate meaningful SelectRadius
8302 float SelectRadius;
8303 SelectRadius = g_Platform->GetSelectRadiusPix() /
8304 (m_true_scale_ppm * 1852 * 60); // Degrees, approximately
8305
8307 // We start with Double Click processing. The first left click just starts a
8308 // timer and is remembered, then we actually do something if there is a
8309 // LeftDClick. If there is, the two single clicks are ignored.
8310
8311 if (event.LeftDClick() && (cursor_region == CENTER)) {
8312 m_DoubleClickTimer->Start();
8313 singleClickEventIsValid = false;
8314
8315 double zlat, zlon;
8317 y * g_current_monitor_dip_px_ratio, zlat, zlon);
8318
8319 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8320 if (m_bShowAIS) {
8321 SelectItem *pFindAIS;
8322 pFindAIS = pSelectAIS->FindSelection(ctx, zlat, zlon, SELTYPE_AISTARGET);
8323
8324 if (pFindAIS) {
8325 m_FoundAIS_MMSI = pFindAIS->GetUserData();
8326 if (g_pAIS->Get_Target_Data_From_MMSI(m_FoundAIS_MMSI)) {
8327 ShowAISTargetQueryDialog(this, m_FoundAIS_MMSI);
8328 }
8329 return true;
8330 }
8331 }
8332
8333 SelectableItemList rpSelList =
8334 pSelect->FindSelectionList(ctx, zlat, zlon, SELTYPE_ROUTEPOINT);
8335 bool b_onRPtarget = false;
8336 for (SelectItem *pFind : rpSelList) {
8337 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8338 if (m_pRoutePointEditTarget && (frp == m_pRoutePointEditTarget)) {
8339 b_onRPtarget = true;
8340 break;
8341 }
8342 }
8343
8344 // Double tap with selected RoutePoint or Mark or Track or AISTarget
8345
8346 // Get and honor the plugin API ContextMenuMask
8347 std::unique_ptr<HostApi> host_api = GetHostApi();
8348 auto *api_121 = dynamic_cast<HostApi121 *>(host_api.get());
8349
8350 if (m_pRoutePointEditTarget) {
8351 if (b_onRPtarget) {
8352 if ((api_121->GetContextMenuMask() &
8353 api_121->kContextMenuDisableWaypoint))
8354 return true;
8355 ShowMarkPropertiesDialog(m_pRoutePointEditTarget);
8356 return true;
8357 } else {
8358 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
8359 m_pRoutePointEditTarget->m_bPtIsSelected = false;
8360 if (g_btouch)
8361 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(false);
8362 wxRect wp_rect;
8363 RoutePointGui(*m_pRoutePointEditTarget)
8364 .CalculateDCRect(m_dc_route, this, &wp_rect);
8365 m_pRoutePointEditTarget = NULL; // cancel selection
8366 RefreshRect(wp_rect, true);
8367 return true;
8368 }
8369 } else {
8370 auto node = rpSelList.begin();
8371 if (node != rpSelList.end()) {
8372 SelectItem *pFind = *node;
8373 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8374 if (frp) {
8375 wxArrayPtrVoid *proute_array =
8377
8378 // Use route array (if any) to determine actual visibility for this
8379 // point
8380 bool brp_viz = false;
8381 if (proute_array) {
8382 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8383 Route *pr = (Route *)proute_array->Item(ir);
8384 if (pr->IsVisible()) {
8385 brp_viz = true;
8386 break;
8387 }
8388 }
8389 delete proute_array;
8390 if (!brp_viz &&
8391 frp->IsShared()) // is not visible as part of route, but
8392 // still exists as a waypoint
8393 brp_viz = frp->IsVisible(); // so treat as isolated point
8394 } else
8395 brp_viz = frp->IsVisible(); // isolated point
8396
8397 if (brp_viz) {
8398 if ((api_121->GetContextMenuMask() &
8399 api_121->kContextMenuDisableWaypoint))
8400 return true;
8401
8402 ShowMarkPropertiesDialog(frp);
8403 return true;
8404 }
8405 }
8406 }
8407 }
8408
8409 SelectItem *cursorItem;
8410
8411 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_ROUTESEGMENT);
8412 if (cursorItem) {
8413 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableRoute))
8414 return true;
8415 Route *pr = (Route *)cursorItem->m_pData3;
8416 if (pr->IsVisible()) {
8417 ShowRoutePropertiesDialog(_("Route Properties"), pr);
8418 return true;
8419 }
8420 }
8421
8422 cursorItem = pSelect->FindSelection(ctx, zlat, zlon, SELTYPE_TRACKSEGMENT);
8423 if (cursorItem) {
8424 if ((api_121->GetContextMenuMask() & api_121->kContextMenuDisableTrack))
8425 return true;
8426 Track *pt = (Track *)cursorItem->m_pData3;
8427 if (pt->IsVisible()) {
8428 ShowTrackPropertiesDialog(pt);
8429 return true;
8430 }
8431 }
8432
8433 // Tide and current points
8434 SelectItem *pFindCurrent = NULL;
8435 SelectItem *pFindTide = NULL;
8436
8437 if (m_bShowCurrent) { // look for current stations
8438 pFindCurrent =
8439 pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_CURRENTPOINT);
8440 if (pFindCurrent) {
8441 m_pIDXCandidate = FindBestCurrentObject(zlat, zlon);
8442 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8443 Refresh(false);
8444 return true;
8445 }
8446 }
8447
8448 if (m_bShowTide) { // look for tide stations
8449 pFindTide = pSelectTC->FindSelection(ctx, zlat, zlon, SELTYPE_TIDEPOINT);
8450 if (pFindTide) {
8451 m_pIDXCandidate = (IDX_entry *)pFindTide->m_pData1;
8452 DrawTCWindow(x, y, (void *)m_pIDXCandidate);
8453 Refresh(false);
8454 return true;
8455 }
8456 }
8457
8458 // Found no object to act on, so show chart info.
8459 ShowObjectQueryWindow(x, y, zlat, zlon);
8460 return true;
8461 }
8462
8464 if (event.LeftDown()) {
8465 // This really should not be needed, but....
8466 // on Windows, when using wxAUIManager, sometimes the focus is lost
8467 // when clicking into another pane, e.g.the AIS target list, and then back
8468 // to this pane. Oddly, some mouse events are not lost, however. Like this
8469 // one....
8470 SetFocus();
8471
8472 last_drag.x = mx;
8473 last_drag.y = my;
8474 leftIsDown = true;
8475
8476 if (!g_btouch) {
8477 if (m_routeState) // creating route?
8478 {
8479 double rlat, rlon;
8480 bool appending = false;
8481 bool inserting = false;
8482 Route *tail = 0;
8483
8484 SetCursor(*pCursorPencil);
8485 rlat = m_cursor_lat;
8486 rlon = m_cursor_lon;
8487
8488 m_bRouteEditing = true;
8489
8490 if (m_routeState == 1) {
8491 m_pMouseRoute = new Route();
8492 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
8493 pRouteList->push_back(m_pMouseRoute);
8494 r_rband.x = x;
8495 r_rband.y = y;
8496 }
8497
8498 // Check to see if there is a nearby point which may be reused
8499 RoutePoint *pMousePoint = NULL;
8500
8501 // Calculate meaningful SelectRadius
8502 double nearby_radius_meters =
8503 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
8504
8505 RoutePoint *pNearbyPoint =
8506 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
8507 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
8508 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
8509 wxArrayPtrVoid *proute_array =
8510 g_pRouteMan->GetRouteArrayContaining(pNearbyPoint);
8511
8512 // Use route array (if any) to determine actual visibility for this
8513 // point
8514 bool brp_viz = false;
8515 if (proute_array) {
8516 for (unsigned int ir = 0; ir < proute_array->GetCount(); ir++) {
8517 Route *pr = (Route *)proute_array->Item(ir);
8518 if (pr->IsVisible()) {
8519 brp_viz = true;
8520 break;
8521 }
8522 }
8523 delete proute_array;
8524 if (!brp_viz &&
8525 pNearbyPoint->IsShared()) // is not visible as part of route,
8526 // but still exists as a waypoint
8527 brp_viz =
8528 pNearbyPoint->IsVisible(); // so treat as isolated point
8529 } else
8530 brp_viz = pNearbyPoint->IsVisible(); // isolated point
8531
8532 if (brp_viz) {
8533 wxString msg = _("Use nearby waypoint?");
8534 // Don't add a mark without name to the route. Name it if needed
8535 const bool noname(pNearbyPoint->GetName() == "");
8536 if (noname) {
8537 msg =
8538 _("Use nearby nameless waypoint and name it M with"
8539 " a unique number?");
8540 }
8541 // Avoid route finish on focus change for message dialog
8542 m_FinishRouteOnKillFocus = false;
8543 int dlg_return =
8544 OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8545 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8546 m_FinishRouteOnKillFocus = true;
8547 if (dlg_return == wxID_YES) {
8548 if (noname) {
8549 if (m_pMouseRoute) {
8550 int last_wp_num = m_pMouseRoute->GetnPoints();
8551 // AP-ECRMB will truncate to 6 characters
8552 wxString guid_short = m_pMouseRoute->GetGUID().Left(2);
8553 wxString wp_name = wxString::Format(
8554 "M%002i-%s", last_wp_num + 1, guid_short);
8555 pNearbyPoint->SetName(wp_name);
8556 } else
8557 pNearbyPoint->SetName("WPXX");
8558 }
8559 pMousePoint = pNearbyPoint;
8560
8561 // Using existing waypoint, so nothing to delete for undo.
8562 if (m_routeState > 1)
8563 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8564 Undo_HasParent, NULL);
8565
8566 tail =
8567 g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
8568 bool procede = false;
8569 if (tail) {
8570 procede = true;
8571 // if (pMousePoint == tail->GetLastPoint()) procede = false;
8572 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
8573 procede = false;
8574 }
8575
8576 if (procede) {
8577 int dlg_return;
8578 m_FinishRouteOnKillFocus = false;
8579 if (m_routeState ==
8580 1) { // first point in new route, preceeding route to be
8581 // added? Not touch case
8582
8583 wxString dmsg =
8584 _("Insert first part of this route in the new route?");
8585 if (tail->GetIndexOf(pMousePoint) ==
8586 tail->GetnPoints()) // Starting on last point of another
8587 // route?
8588 dmsg = _("Insert this route in the new route?");
8589
8590 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
8591 dlg_return = OCPNMessageBox(
8592 this, dmsg, _("OpenCPN Route Create"),
8593 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8594 m_FinishRouteOnKillFocus = true;
8595
8596 if (dlg_return == wxID_YES) {
8597 inserting = true; // part of the other route will be
8598 // preceeding the new route
8599 }
8600 }
8601 } else {
8602 wxString dmsg =
8603 _("Append last part of this route to the new route?");
8604 if (tail->GetIndexOf(pMousePoint) == 1)
8605 dmsg = _(
8606 "Append this route to the new route?"); // Picking the
8607 // first point
8608 // of another
8609 // route?
8610
8611 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
8612 dlg_return = OCPNMessageBox(
8613 this, dmsg, _("OpenCPN Route Create"),
8614 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
8615 m_FinishRouteOnKillFocus = true;
8616
8617 if (dlg_return == wxID_YES) {
8618 appending = true; // part of the other route will be
8619 // appended to the new route
8620 }
8621 }
8622 }
8623 }
8624
8625 // check all other routes to see if this point appears in any
8626 // other route If it appears in NO other route, then it should e
8627 // considered an isolated mark
8628 if (!FindRouteContainingWaypoint(pMousePoint))
8629 pMousePoint->SetShared(true);
8630 }
8631 }
8632 }
8633
8634 if (NULL == pMousePoint) { // need a new point
8635 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
8636 "", wxEmptyString);
8637 pMousePoint->SetNameShown(false);
8638
8639 // pConfig->AddNewWayPoint(pMousePoint, -1); // use auto next num
8640
8641 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
8642
8643 if (m_routeState > 1)
8644 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
8645 Undo_IsOrphanded, NULL);
8646 }
8647
8648 if (m_pMouseRoute) {
8649 if (m_routeState == 1) {
8650 // First point in the route.
8651 m_pMouseRoute->AddPoint(pMousePoint);
8652 // NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
8653 } else {
8654 if (m_pMouseRoute->m_NextLegGreatCircle) {
8655 double rhumbBearing, rhumbDist, gcBearing, gcDist;
8656 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
8657 &rhumbBearing, &rhumbDist);
8658 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon,
8659 rlat, &gcDist, &gcBearing, NULL);
8660 double gcDistNM = gcDist / 1852.0;
8661
8662 // Empirically found expression to get reasonable route segments.
8663 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
8664 pow(rhumbDist - gcDistNM - 1, 0.5);
8665
8666 wxString msg;
8667 msg << _("For this leg the Great Circle route is ")
8668 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
8669 << _(" shorter than rhumbline.\n\n")
8670 << _("Would you like include the Great Circle routing points "
8671 "for this leg?");
8672
8673 m_FinishRouteOnKillFocus = false;
8674 m_disable_edge_pan = true; // This helps on OS X if MessageBox
8675 // does not fully capture mouse
8676
8677 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
8678 wxYES_NO | wxNO_DEFAULT);
8679
8680 m_disable_edge_pan = false;
8681 m_FinishRouteOnKillFocus = true;
8682
8683 if (answer == wxID_YES) {
8684 RoutePoint *gcPoint;
8685 RoutePoint *prevGcPoint = m_prev_pMousePoint;
8686 wxRealPoint gcCoord;
8687
8688 for (int i = 1; i <= segmentCount; i++) {
8689 double fraction = (double)i * (1.0 / (double)segmentCount);
8690 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
8691 gcDist * fraction, gcBearing,
8692 &gcCoord.x, &gcCoord.y, NULL);
8693
8694 if (i < segmentCount) {
8695 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
8696 wxEmptyString);
8697 gcPoint->SetNameShown(false);
8698 // pConfig->AddNewWayPoint(gcPoint, -1);
8699 NavObj_dB::GetInstance().InsertRoutePoint(gcPoint);
8700
8701 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
8702 gcPoint);
8703 } else {
8704 gcPoint = pMousePoint; // Last point, previously exsisting!
8705 }
8706
8707 m_pMouseRoute->AddPoint(gcPoint);
8708 pSelect->AddSelectableRouteSegment(
8709 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
8710 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
8711 prevGcPoint = gcPoint;
8712 }
8713
8714 undo->CancelUndoableAction(true);
8715
8716 } else {
8717 m_pMouseRoute->AddPoint(pMousePoint);
8718 pSelect->AddSelectableRouteSegment(
8719 m_prev_rlat, m_prev_rlon, rlat, rlon, m_prev_pMousePoint,
8720 pMousePoint, m_pMouseRoute);
8721 undo->AfterUndoableAction(m_pMouseRoute);
8722 }
8723 } else {
8724 // Ordinary rhumblinesegment.
8725 m_pMouseRoute->AddPoint(pMousePoint);
8726 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
8727 rlon, m_prev_pMousePoint,
8728 pMousePoint, m_pMouseRoute);
8729 undo->AfterUndoableAction(m_pMouseRoute);
8730 }
8731 }
8732 }
8733 m_prev_rlat = rlat;
8734 m_prev_rlon = rlon;
8735 m_prev_pMousePoint = pMousePoint;
8736 if (m_pMouseRoute)
8737 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
8738
8739 m_routeState++;
8740
8741 if (appending ||
8742 inserting) { // Appending a route or making a new route
8743 int connect = tail->GetIndexOf(pMousePoint);
8744 if (connect == 1) {
8745 inserting = false; // there is nothing to insert
8746 appending = true; // so append
8747 }
8748 int length = tail->GetnPoints();
8749
8750 int i;
8751 int start, stop;
8752 if (appending) {
8753 start = connect + 1;
8754 stop = length;
8755 } else { // inserting
8756 start = 1;
8757 stop = connect;
8758 m_pMouseRoute->RemovePoint(
8759 m_pMouseRoute
8760 ->GetLastPoint()); // Remove the first and only point
8761 }
8762 for (i = start; i <= stop; i++) {
8763 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
8764 if (m_pMouseRoute)
8765 m_pMouseRoute->m_lastMousePointIndex =
8766 m_pMouseRoute->GetnPoints();
8767 m_routeState++;
8768 gFrame->RefreshAllCanvas();
8769 ret = true;
8770 }
8771 m_prev_rlat =
8772 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
8773 m_prev_rlon =
8774 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
8775 m_pMouseRoute->FinalizeForRendering();
8776 }
8777 gFrame->RefreshAllCanvas();
8778 ret = true;
8779 }
8780
8781 else if (m_bMeasure_Active && (m_nMeasureState >= 1)) // measure tool?
8782 {
8783 SetCursor(*pCursorPencil);
8784
8785 if (!m_pMeasureRoute) {
8786 m_pMeasureRoute = new Route();
8787 pRouteList->push_back(m_pMeasureRoute);
8788 }
8789
8790 if (m_nMeasureState == 1) {
8791 r_rband.x = x;
8792 r_rband.y = y;
8793 }
8794
8795 RoutePoint *pMousePoint =
8796 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
8797 wxEmptyString, wxEmptyString);
8798 pMousePoint->m_bShowName = false;
8799 pMousePoint->SetShowWaypointRangeRings(false);
8800
8801 m_pMeasureRoute->AddPoint(pMousePoint);
8802
8803 m_prev_rlat = m_cursor_lat;
8804 m_prev_rlon = m_cursor_lon;
8805 m_prev_pMousePoint = pMousePoint;
8806 m_pMeasureRoute->m_lastMousePointIndex = m_pMeasureRoute->GetnPoints();
8807
8808 m_nMeasureState++;
8809 gFrame->RefreshAllCanvas();
8810 ret = true;
8811 }
8812
8813 else {
8814 FindRoutePointsAtCursor(SelectRadius, true); // Not creating Route
8815 }
8816 } // !g_btouch
8817 else { // g_btouch
8818 m_last_touch_down_pos = event.GetPosition();
8819
8820 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
8821 // if near screen edge, pan with injection
8822 // if( CheckEdgePan( x, y, true, 5, 10 ) ) {
8823 // return;
8824 // }
8825 }
8826 }
8827
8828 if (ret) return true;
8829 }
8830
8831 if (event.Dragging()) {
8832 // in touch screen mode ensure the finger/cursor is on the selected point's
8833 // radius to allow dragging
8834 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
8835 if (g_btouch) {
8836 if (m_pRoutePointEditTarget && !m_bIsInRadius) {
8837 SelectItem *pFind = NULL;
8838 SelectableItemList SelList = pSelect->FindSelectionList(
8839 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTEPOINT);
8840 for (SelectItem *pFind : SelList) {
8841 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8842 if (m_pRoutePointEditTarget == frp) m_bIsInRadius = true;
8843 }
8844 }
8845
8846 // Check for use of dragHandle
8847 if (m_pRoutePointEditTarget &&
8848 m_pRoutePointEditTarget->IsDragHandleEnabled()) {
8849 SelectItem *pFind = NULL;
8850 SelectableItemList SelList = pSelect->FindSelectionList(
8851 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_DRAGHANDLE);
8852 for (SelectItem *pFind : SelList) {
8853 RoutePoint *frp = (RoutePoint *)pFind->m_pData1;
8854 if (m_pRoutePointEditTarget == frp) {
8855 m_bIsInRadius = true;
8856 break;
8857 }
8858 }
8859
8860 if (!m_dragoffsetSet) {
8861 RoutePointGui(*m_pRoutePointEditTarget)
8862 .PresetDragOffset(this, mouse_x, mouse_y);
8863 m_dragoffsetSet = true;
8864 }
8865 }
8866 }
8867
8868 if (m_bRouteEditing && m_pRoutePointEditTarget) {
8869 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8870
8871 if (NULL == g_pMarkInfoDialog) {
8872 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8873 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8874 DraggingAllowed = false;
8875
8876 if (m_pRoutePointEditTarget &&
8877 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8878 DraggingAllowed = false;
8879
8880 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8881
8882 if (DraggingAllowed) {
8883 if (!undo->InUndoableAction()) {
8884 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8885 Undo_NeedsCopy, m_pFoundPoint);
8886 }
8887
8888 // Get the update rectangle for the union of the un-edited routes
8889 wxRect pre_rect;
8890
8891 if (!g_bopengl && m_pEditRouteArray) {
8892 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
8893 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8894 // Need to validate route pointer
8895 // Route may be gone due to drgging close to ownship with
8896 // "Delete On Arrival" state set, as in the case of
8897 // navigating to an isolated waypoint on a temporary route
8898 if (g_pRouteMan->IsRouteValid(pr)) {
8899 wxRect route_rect;
8900 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8901 pre_rect.Union(route_rect);
8902 }
8903 }
8904 }
8905
8906 double new_cursor_lat = m_cursor_lat;
8907 double new_cursor_lon = m_cursor_lon;
8908
8909 if (CheckEdgePan(x, y, true, 5, 2))
8910 GetCanvasPixPoint(x, y, new_cursor_lat, new_cursor_lon);
8911
8912 // update the point itself
8913 if (g_btouch) {
8914 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
8915 // new_cursor_lat, new_cursor_lon);
8916 RoutePointGui(*m_pRoutePointEditTarget)
8917 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
8918 // update the Drag Handle entry in the pSelect list
8919 pSelect->ModifySelectablePoint(new_cursor_lat, new_cursor_lon,
8920 m_pRoutePointEditTarget,
8921 SELTYPE_DRAGHANDLE);
8922 m_pFoundPoint->m_slat =
8923 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
8924 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
8925 } else {
8926 m_pRoutePointEditTarget->m_lat =
8927 new_cursor_lat; // update the RoutePoint entry
8928 m_pRoutePointEditTarget->m_lon = new_cursor_lon;
8929 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
8930 m_pFoundPoint->m_slat =
8931 new_cursor_lat; // update the SelectList entry
8932 m_pFoundPoint->m_slon = new_cursor_lon;
8933 }
8934
8935 // Update the MarkProperties Dialog, if currently shown
8936 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
8937 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
8938 g_pMarkInfoDialog->UpdateProperties(true);
8939 }
8940
8941 if (g_bopengl) {
8942 // InvalidateGL();
8943 Refresh(false);
8944 } else {
8945 // Get the update rectangle for the edited route
8946 wxRect post_rect;
8947
8948 if (m_pEditRouteArray) {
8949 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
8950 ir++) {
8951 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
8952 if (g_pRouteMan->IsRouteValid(pr)) {
8953 wxRect route_rect;
8954 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
8955 post_rect.Union(route_rect);
8956 }
8957 }
8958 }
8959
8960 // Invalidate the union region
8961 pre_rect.Union(post_rect);
8962 RefreshRect(pre_rect, false);
8963 }
8964 gFrame->RefreshCanvasOther(this);
8965 m_bRoutePoinDragging = true;
8966 }
8967 ret = true;
8968 } // if Route Editing
8969
8970 else if (m_bMarkEditing && m_pRoutePointEditTarget) {
8971 bool DraggingAllowed = g_btouch ? m_bIsInRadius : true;
8972
8973 if (NULL == g_pMarkInfoDialog) {
8974 if (g_bWayPointPreventDragging) DraggingAllowed = false;
8975 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
8976 DraggingAllowed = false;
8977
8978 if (m_pRoutePointEditTarget &&
8979 (m_pRoutePointEditTarget->GetIconName() == "mob"))
8980 DraggingAllowed = false;
8981
8982 if (m_pRoutePointEditTarget->m_bIsInLayer) DraggingAllowed = false;
8983
8984 if (DraggingAllowed) {
8985 if (!undo->InUndoableAction()) {
8986 undo->BeforeUndoableAction(Undo_MoveWaypoint, m_pRoutePointEditTarget,
8987 Undo_NeedsCopy, m_pFoundPoint);
8988 }
8989
8990 // The mark may be an anchorwatch
8991 double lpp1 = 0.;
8992 double lpp2 = 0.;
8993 double lppmax;
8994
8995 if (pAnchorWatchPoint1 == m_pRoutePointEditTarget) {
8996 lpp1 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint1));
8997 }
8998 if (pAnchorWatchPoint2 == m_pRoutePointEditTarget) {
8999 lpp2 = fabs(GetAnchorWatchRadiusPixels(pAnchorWatchPoint2));
9000 }
9001 lppmax = wxMax(lpp1 + 10, lpp2 + 10); // allow for cruft
9002
9003 // Get the update rectangle for the un-edited mark
9004 wxRect pre_rect;
9005 if (!g_bopengl) {
9006 RoutePointGui(*m_pRoutePointEditTarget)
9007 .CalculateDCRect(m_dc_route, this, &pre_rect);
9008 if ((lppmax > pre_rect.width / 2) || (lppmax > pre_rect.height / 2))
9009 pre_rect.Inflate((int)(lppmax - (pre_rect.width / 2)),
9010 (int)(lppmax - (pre_rect.height / 2)));
9011 }
9012
9013 // update the point itself
9014 if (g_btouch) {
9015 // m_pRoutePointEditTarget->SetPointFromDraghandlePoint(VPoint,
9016 // m_cursor_lat, m_cursor_lon);
9017 RoutePointGui(*m_pRoutePointEditTarget)
9018 .SetPointFromDraghandlePoint(this, mouse_x, mouse_y);
9019 // update the Drag Handle entry in the pSelect list
9020 pSelect->ModifySelectablePoint(m_cursor_lat, m_cursor_lon,
9021 m_pRoutePointEditTarget,
9022 SELTYPE_DRAGHANDLE);
9023 m_pFoundPoint->m_slat =
9024 m_pRoutePointEditTarget->m_lat; // update the SelectList entry
9025 m_pFoundPoint->m_slon = m_pRoutePointEditTarget->m_lon;
9026 } else {
9027 m_pRoutePointEditTarget->m_lat =
9028 m_cursor_lat; // update the RoutePoint entry
9029 m_pRoutePointEditTarget->m_lon = m_cursor_lon;
9030 m_pRoutePointEditTarget->m_wpBBox.Invalidate();
9031 m_pFoundPoint->m_slat = m_cursor_lat; // update the SelectList entry
9032 m_pFoundPoint->m_slon = m_cursor_lon;
9033 }
9034
9035 // Update the MarkProperties Dialog, if currently shown
9036 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown())) {
9037 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9038 g_pMarkInfoDialog->UpdateProperties(true);
9039 }
9040
9041 // Invalidate the union region
9042 if (g_bopengl) {
9043 if (!g_btouch) InvalidateGL();
9044 Refresh(false);
9045 } else {
9046 // Get the update rectangle for the edited mark
9047 wxRect post_rect;
9048 RoutePointGui(*m_pRoutePointEditTarget)
9049 .CalculateDCRect(m_dc_route, this, &post_rect);
9050 if ((lppmax > post_rect.width / 2) || (lppmax > post_rect.height / 2))
9051 post_rect.Inflate((int)(lppmax - (post_rect.width / 2)),
9052 (int)(lppmax - (post_rect.height / 2)));
9053
9054 // Invalidate the union region
9055 pre_rect.Union(post_rect);
9056 RefreshRect(pre_rect, false);
9057 }
9058 gFrame->RefreshCanvasOther(this);
9059 m_bRoutePoinDragging = true;
9060 }
9061 ret = g_btouch ? m_bRoutePoinDragging : true;
9062 }
9063
9064 if (ret) return true;
9065 } // dragging
9066
9067 if (event.LeftUp()) {
9068 bool b_startedit_route = false;
9069 m_dragoffsetSet = false;
9070
9071 if (g_btouch) {
9072 m_bChartDragging = false;
9073 m_bIsInRadius = false;
9074
9075 if (m_routeState) // creating route?
9076 {
9077 if (m_ignore_next_leftup) {
9078 m_ignore_next_leftup = false;
9079 return false;
9080 }
9081
9082 if (m_bedge_pan) {
9083 m_bedge_pan = false;
9084 return false;
9085 }
9086
9087 double rlat, rlon;
9088 bool appending = false;
9089 bool inserting = false;
9090 Route *tail = 0;
9091
9092 rlat = m_cursor_lat;
9093 rlon = m_cursor_lon;
9094
9095 if (m_pRoutePointEditTarget) {
9096 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
9097 m_pRoutePointEditTarget->m_bPtIsSelected = false;
9098 if (!g_bopengl) {
9099 wxRect wp_rect;
9100 RoutePointGui(*m_pRoutePointEditTarget)
9101 .CalculateDCRect(m_dc_route, this, &wp_rect);
9102 RefreshRect(wp_rect, true);
9103 }
9104 m_pRoutePointEditTarget = NULL;
9105 }
9106 m_bRouteEditing = true;
9107
9108 if (m_routeState == 1) {
9109 m_pMouseRoute = new Route();
9110 m_pMouseRoute->SetHiLite(50);
9111 pRouteList->push_back(m_pMouseRoute);
9112 r_rband.x = x;
9113 r_rband.y = y;
9114 NavObj_dB::GetInstance().InsertRoute(m_pMouseRoute);
9115 }
9116
9117 // Check to see if there is a nearby point which may be reused
9118 RoutePoint *pMousePoint = NULL;
9119
9120 // Calculate meaningful SelectRadius
9121 double nearby_radius_meters =
9122 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9123
9124 RoutePoint *pNearbyPoint =
9125 pWayPointMan->GetNearbyWaypoint(rlat, rlon, nearby_radius_meters);
9126 if (pNearbyPoint && (pNearbyPoint != m_prev_pMousePoint) &&
9127 !pNearbyPoint->m_bIsInLayer && pNearbyPoint->IsVisible()) {
9128 int dlg_return;
9129#ifndef __WXOSX__
9130 m_FinishRouteOnKillFocus =
9131 false; // Avoid route finish on focus change for message dialog
9132 dlg_return = OCPNMessageBox(
9133 this, _("Use nearby waypoint?"), _("OpenCPN Route Create"),
9134 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9135 m_FinishRouteOnKillFocus = true;
9136#else
9137 dlg_return = wxID_YES;
9138#endif
9139 if (dlg_return == wxID_YES) {
9140 pMousePoint = pNearbyPoint;
9141
9142 // Using existing waypoint, so nothing to delete for undo.
9143 if (m_routeState > 1)
9144 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9145 Undo_HasParent, NULL);
9146 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(pMousePoint);
9147
9148 bool procede = false;
9149 if (tail) {
9150 procede = true;
9151 // if (pMousePoint == tail->GetLastPoint()) procede = false;
9152 if (m_routeState > 1 && m_pMouseRoute && tail == m_pMouseRoute)
9153 procede = false;
9154 }
9155
9156 if (procede) {
9157 int dlg_return;
9158 m_FinishRouteOnKillFocus = false;
9159 if (m_routeState == 1) { // first point in new route, preceeding
9160 // route to be added? touch case
9161
9162 wxString dmsg =
9163 _("Insert first part of this route in the new route?");
9164 if (tail->GetIndexOf(pMousePoint) ==
9165 tail->GetnPoints()) // Starting on last point of another
9166 // route?
9167 dmsg = _("Insert this route in the new route?");
9168
9169 if (tail->GetIndexOf(pMousePoint) != 1) { // Anything to do?
9170 dlg_return =
9171 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9172 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9173 m_FinishRouteOnKillFocus = true;
9174
9175 if (dlg_return == wxID_YES) {
9176 inserting = true; // part of the other route will be
9177 // preceeding the new route
9178 }
9179 }
9180 } else {
9181 wxString dmsg =
9182 _("Append last part of this route to the new route?");
9183 if (tail->GetIndexOf(pMousePoint) == 1)
9184 dmsg = _(
9185 "Append this route to the new route?"); // Picking the
9186 // first point of
9187 // another route?
9188
9189 if (tail->GetLastPoint() != pMousePoint) { // Anything to do?
9190 dlg_return =
9191 OCPNMessageBox(this, dmsg, _("OpenCPN Route Create"),
9192 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9193 m_FinishRouteOnKillFocus = true;
9194
9195 if (dlg_return == wxID_YES) {
9196 appending = true; // part of the other route will be
9197 // appended to the new route
9198 }
9199 }
9200 }
9201 }
9202
9203 // check all other routes to see if this point appears in any other
9204 // route If it appears in NO other route, then it should e
9205 // considered an isolated mark
9206 if (!FindRouteContainingWaypoint(pMousePoint))
9207 pMousePoint->SetShared(true);
9208 }
9209 }
9210
9211 if (NULL == pMousePoint) { // need a new point
9212 pMousePoint = new RoutePoint(rlat, rlon, g_default_routepoint_icon,
9213 "", wxEmptyString);
9214 pMousePoint->SetNameShown(false);
9215
9216 pSelect->AddSelectableRoutePoint(rlat, rlon, pMousePoint);
9217
9218 if (m_routeState > 1)
9219 undo->BeforeUndoableAction(Undo_AppendWaypoint, pMousePoint,
9220 Undo_IsOrphanded, NULL);
9221 }
9222
9223 if (m_routeState == 1) {
9224 // First point in the route.
9225 m_pMouseRoute->AddPoint(pMousePoint);
9226 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9227
9228 } else {
9229 if (m_pMouseRoute->m_NextLegGreatCircle) {
9230 double rhumbBearing, rhumbDist, gcBearing, gcDist;
9231 DistanceBearingMercator(rlat, rlon, m_prev_rlat, m_prev_rlon,
9232 &rhumbBearing, &rhumbDist);
9233 Geodesic::GreatCircleDistBear(m_prev_rlon, m_prev_rlat, rlon, rlat,
9234 &gcDist, &gcBearing, NULL);
9235 double gcDistNM = gcDist / 1852.0;
9236
9237 // Empirically found expression to get reasonable route segments.
9238 int segmentCount = (3.0 + (rhumbDist - gcDistNM)) /
9239 pow(rhumbDist - gcDistNM - 1, 0.5);
9240
9241 wxString msg;
9242 msg << _("For this leg the Great Circle route is ")
9243 << FormatDistanceAdaptive(rhumbDist - gcDistNM)
9244 << _(" shorter than rhumbline.\n\n")
9245 << _("Would you like include the Great Circle routing points "
9246 "for this leg?");
9247
9248#ifndef __WXOSX__
9249 m_FinishRouteOnKillFocus = false;
9250 int answer = OCPNMessageBox(this, msg, _("OpenCPN Route Create"),
9251 wxYES_NO | wxNO_DEFAULT);
9252 m_FinishRouteOnKillFocus = true;
9253#else
9254 int answer = wxID_NO;
9255#endif
9256
9257 if (answer == wxID_YES) {
9258 RoutePoint *gcPoint;
9259 RoutePoint *prevGcPoint = m_prev_pMousePoint;
9260 wxRealPoint gcCoord;
9261
9262 for (int i = 1; i <= segmentCount; i++) {
9263 double fraction = (double)i * (1.0 / (double)segmentCount);
9264 Geodesic::GreatCircleTravel(m_prev_rlon, m_prev_rlat,
9265 gcDist * fraction, gcBearing,
9266 &gcCoord.x, &gcCoord.y, NULL);
9267
9268 if (i < segmentCount) {
9269 gcPoint = new RoutePoint(gcCoord.y, gcCoord.x, "xmblue", "",
9270 wxEmptyString);
9271 gcPoint->SetNameShown(false);
9272 pSelect->AddSelectableRoutePoint(gcCoord.y, gcCoord.x,
9273 gcPoint);
9274 } else {
9275 gcPoint = pMousePoint; // Last point, previously exsisting!
9276 }
9277
9278 m_pMouseRoute->AddPoint(gcPoint);
9279 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9280
9281 pSelect->AddSelectableRouteSegment(
9282 prevGcPoint->m_lat, prevGcPoint->m_lon, gcPoint->m_lat,
9283 gcPoint->m_lon, prevGcPoint, gcPoint, m_pMouseRoute);
9284 prevGcPoint = gcPoint;
9285 }
9286
9287 undo->CancelUndoableAction(true);
9288
9289 } else {
9290 m_pMouseRoute->AddPoint(pMousePoint);
9291 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9292 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9293 rlon, m_prev_pMousePoint,
9294 pMousePoint, m_pMouseRoute);
9295 undo->AfterUndoableAction(m_pMouseRoute);
9296 }
9297 } else {
9298 // Ordinary rhumblinesegment.
9299 m_pMouseRoute->AddPoint(pMousePoint);
9300 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
9301
9302 pSelect->AddSelectableRouteSegment(m_prev_rlat, m_prev_rlon, rlat,
9303 rlon, m_prev_pMousePoint,
9304 pMousePoint, m_pMouseRoute);
9305 undo->AfterUndoableAction(m_pMouseRoute);
9306 }
9307 }
9308
9309 m_prev_rlat = rlat;
9310 m_prev_rlon = rlon;
9311 m_prev_pMousePoint = pMousePoint;
9312 m_pMouseRoute->m_lastMousePointIndex = m_pMouseRoute->GetnPoints();
9313
9314 m_routeState++;
9315
9316 if (appending ||
9317 inserting) { // Appending a route or making a new route
9318 int connect = tail->GetIndexOf(pMousePoint);
9319 if (connect == 1) {
9320 inserting = false; // there is nothing to insert
9321 appending = true; // so append
9322 }
9323 int length = tail->GetnPoints();
9324
9325 int i;
9326 int start, stop;
9327 if (appending) {
9328 start = connect + 1;
9329 stop = length;
9330 } else { // inserting
9331 start = 1;
9332 stop = connect;
9333 m_pMouseRoute->RemovePoint(
9334 m_pMouseRoute
9335 ->GetLastPoint()); // Remove the first and only point
9336 }
9337 for (i = start; i <= stop; i++) {
9338 m_pMouseRoute->AddPointAndSegment(tail->GetPoint(i), false);
9339 if (m_pMouseRoute)
9340 m_pMouseRoute->m_lastMousePointIndex =
9341 m_pMouseRoute->GetnPoints();
9342 m_routeState++;
9343 gFrame->RefreshAllCanvas();
9344 ret = true;
9345 }
9346 m_prev_rlat =
9347 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lat;
9348 m_prev_rlon =
9349 m_pMouseRoute->GetPoint(m_pMouseRoute->GetnPoints())->m_lon;
9350 m_pMouseRoute->FinalizeForRendering();
9351 }
9352
9353 Refresh(true);
9354 ret = true;
9355 } else if (m_bMeasure_Active && m_nMeasureState) // measure tool?
9356 {
9357 if (m_bedge_pan) {
9358 m_bedge_pan = false;
9359 return false;
9360 }
9361
9362 if (m_ignore_next_leftup) {
9363 m_ignore_next_leftup = false;
9364 return false;
9365 }
9366
9367 if (m_nMeasureState == 1) {
9368 m_pMeasureRoute = new Route();
9369 pRouteList->push_back(m_pMeasureRoute);
9370 r_rband.x = x;
9371 r_rband.y = y;
9372 }
9373
9374 if (m_pMeasureRoute) {
9375 RoutePoint *pMousePoint =
9376 new RoutePoint(m_cursor_lat, m_cursor_lon, wxString("circle"),
9377 wxEmptyString, wxEmptyString);
9378 pMousePoint->m_bShowName = false;
9379
9380 m_pMeasureRoute->AddPoint(pMousePoint);
9381
9382 m_prev_rlat = m_cursor_lat;
9383 m_prev_rlon = m_cursor_lon;
9384 m_prev_pMousePoint = pMousePoint;
9385 m_pMeasureRoute->m_lastMousePointIndex =
9386 m_pMeasureRoute->GetnPoints();
9387
9388 m_nMeasureState++;
9389 } else {
9390 CancelMeasureRoute();
9391 }
9392
9393 Refresh(true);
9394 ret = true;
9395 } else {
9396 bool bSelectAllowed = true;
9397 if (NULL == g_pMarkInfoDialog) {
9398 if (g_bWayPointPreventDragging) bSelectAllowed = false;
9399 } else if (!g_pMarkInfoDialog->IsShown() && g_bWayPointPreventDragging)
9400 bSelectAllowed = false;
9401
9402 // Avoid accidental selection of routepoint if last touchdown started
9403 // a significant chart drag operation
9404 int significant_drag = g_Platform->GetSelectRadiusPix() * 2;
9405 if ((abs(m_last_touch_down_pos.x - event.GetPosition().x) >
9406 significant_drag) ||
9407 (abs(m_last_touch_down_pos.y - event.GetPosition().y) >
9408 significant_drag)) {
9409 bSelectAllowed = false;
9410 }
9411
9412 /*if this left up happens at the end of a route point dragging and if
9413 the cursor/thumb is on the draghandle icon, not on the point iself a new
9414 selection will select nothing and the drag will never be ended, so the
9415 legs around this point never selectable. At this step we don't need a
9416 new selection, just keep the previoulsly selected and dragged point */
9417 if (m_bRoutePoinDragging) bSelectAllowed = false;
9418
9419 if (bSelectAllowed) {
9420 bool b_was_editing_mark = m_bMarkEditing;
9421 bool b_was_editing_route = m_bRouteEditing;
9422 FindRoutePointsAtCursor(SelectRadius,
9423 true); // Possibly selecting a point in a
9424 // route for later dragging
9425
9426 /*route and a mark points in layer can't be dragged so should't be
9427 * selected and no draghandle icon*/
9428 if (m_pRoutePointEditTarget && m_pRoutePointEditTarget->m_bIsInLayer)
9429 m_pRoutePointEditTarget = NULL;
9430
9431 if (!b_was_editing_route) {
9432 if (m_pEditRouteArray) {
9433 b_startedit_route = true;
9434
9435 // Hide the track and route rollover during route point edit, not
9436 // needed, and may be confusing
9437 if (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) {
9438 m_pTrackRolloverWin->IsActive(false);
9439 }
9440 if (m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) {
9441 m_pRouteRolloverWin->IsActive(false);
9442 }
9443
9444 wxRect pre_rect;
9445 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9446 ir++) {
9447 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9448 // Need to validate route pointer
9449 // Route may be gone due to drgging close to ownship with
9450 // "Delete On Arrival" state set, as in the case of
9451 // navigating to an isolated waypoint on a temporary route
9452 if (g_pRouteMan->IsRouteValid(pr)) {
9453 // pr->SetHiLite(50);
9454 wxRect route_rect;
9455 RouteGui(*pr).CalculateDCRect(m_dc_route, this, &route_rect);
9456 pre_rect.Union(route_rect);
9457 }
9458 }
9459 RefreshRect(pre_rect, true);
9460 }
9461 } else {
9462 b_startedit_route = false;
9463 }
9464
9465 // Mark editing in touch mode, left-up event.
9466 if (m_pRoutePointEditTarget) {
9467 if (b_was_editing_mark ||
9468 b_was_editing_route) { // kill previous hilight
9469 if (m_lastRoutePointEditTarget) {
9470 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9471 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9472 RoutePointGui(*m_lastRoutePointEditTarget)
9473 .EnableDragHandle(false);
9474 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9475 SELTYPE_DRAGHANDLE);
9476 }
9477 }
9478
9479 if (m_pRoutePointEditTarget) {
9480 m_pRoutePointEditTarget->m_bRPIsBeingEdited = true;
9481 m_pRoutePointEditTarget->m_bPtIsSelected = true;
9482 RoutePointGui(*m_pRoutePointEditTarget).EnableDragHandle(true);
9483 wxPoint2DDouble dragHandlePoint =
9484 RoutePointGui(*m_pRoutePointEditTarget)
9485 .GetDragHandlePoint(this);
9486 pSelect->AddSelectablePoint(
9487 dragHandlePoint.m_y, dragHandlePoint.m_x,
9488 m_pRoutePointEditTarget, SELTYPE_DRAGHANDLE);
9489 }
9490 } else { // Deselect everything
9491 if (m_lastRoutePointEditTarget) {
9492 m_lastRoutePointEditTarget->m_bRPIsBeingEdited = false;
9493 m_lastRoutePointEditTarget->m_bPtIsSelected = false;
9494 RoutePointGui(*m_lastRoutePointEditTarget)
9495 .EnableDragHandle(false);
9496 pSelect->DeleteSelectablePoint(m_lastRoutePointEditTarget,
9497 SELTYPE_DRAGHANDLE);
9498
9499 // Clear any routes being edited, probably orphans
9500 wxArrayPtrVoid *lastEditRouteArray =
9502 m_lastRoutePointEditTarget);
9503 if (lastEditRouteArray) {
9504 for (unsigned int ir = 0; ir < lastEditRouteArray->GetCount();
9505 ir++) {
9506 Route *pr = (Route *)lastEditRouteArray->Item(ir);
9507 if (g_pRouteMan->IsRouteValid(pr)) {
9508 pr->m_bIsBeingEdited = false;
9509 }
9510 }
9511 delete lastEditRouteArray;
9512 }
9513 }
9514 }
9515
9516 // Do the refresh
9517
9518 if (g_bopengl) {
9519 InvalidateGL();
9520 Refresh(false);
9521 } else {
9522 if (m_lastRoutePointEditTarget) {
9523 wxRect wp_rect;
9524 RoutePointGui(*m_lastRoutePointEditTarget)
9525 .CalculateDCRect(m_dc_route, this, &wp_rect);
9526 RefreshRect(wp_rect, true);
9527 }
9528
9529 if (m_pRoutePointEditTarget) {
9530 wxRect wp_rect;
9531 RoutePointGui(*m_pRoutePointEditTarget)
9532 .CalculateDCRect(m_dc_route, this, &wp_rect);
9533 RefreshRect(wp_rect, true);
9534 }
9535 }
9536 }
9537 } // bSelectAllowed
9538
9539 // Check to see if there is a route or AIS target under the cursor
9540 // If so, start the rollover timer which creates the popup
9541 SelectCtx ctx(m_bShowNavobjects, GetCanvasTrueScale(), GetScaleValue());
9542 bool b_start_rollover = false;
9543 if (g_pAIS && g_pAIS->GetNumTargets() && m_bShowAIS) {
9544 SelectItem *pFind = pSelectAIS->FindSelection(
9545 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_AISTARGET);
9546 if (pFind) b_start_rollover = true;
9547 }
9548
9549 if (!b_start_rollover && !b_startedit_route) {
9550 SelectableItemList SelList = pSelect->FindSelectionList(
9551 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_ROUTESEGMENT);
9552 for (SelectItem *pFindSel : SelList) {
9553 Route *pr = (Route *)pFindSel->m_pData3; // candidate
9554 if (pr && pr->IsVisible()) {
9555 b_start_rollover = true;
9556 break;
9557 }
9558 } // while
9559 }
9560
9561 if (!b_start_rollover && !b_startedit_route) {
9562 SelectableItemList SelList = pSelect->FindSelectionList(
9563 ctx, m_cursor_lat, m_cursor_lon, SELTYPE_TRACKSEGMENT);
9564 for (SelectItem *pFindSel : SelList) {
9565 Track *tr = (Track *)pFindSel->m_pData3; // candidate
9566 if (tr && tr->IsVisible()) {
9567 b_start_rollover = true;
9568 break;
9569 }
9570 } // while
9571 }
9572
9573 if (b_start_rollover)
9574 m_RolloverPopupTimer.Start(m_rollover_popup_timer_msec,
9575 wxTIMER_ONE_SHOT);
9576 Route *tail = 0;
9577 Route *current = 0;
9578 bool appending = false;
9579 bool inserting = false;
9580 int connect = 0;
9581 if (m_bRouteEditing /* && !b_startedit_route*/) { // End of RoutePoint
9582 // drag
9583 if (m_pRoutePointEditTarget) {
9584 // Check to see if there is a nearby point which may replace the
9585 // dragged one
9586 RoutePoint *pMousePoint = NULL;
9587
9588 int index_last;
9589 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9590 double nearby_radius_meters =
9591 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9592 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9593 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9594 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9595 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9596 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9597 bool duplicate =
9598 false; // ensure we won't create duplicate point in routes
9599 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9600 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9601 ir++) {
9602 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9603 if (pr && pr->pRoutePointList) {
9604 auto *list = pr->pRoutePointList;
9605 auto pos =
9606 std::find(list->begin(), list->end(), pNearbyPoint);
9607 if (pos != list->end()) {
9608 duplicate = true;
9609 break;
9610 }
9611 }
9612 }
9613 }
9614
9615 // Special case:
9616 // Allow "re-use" of a route's waypoints iff it is a simple
9617 // isolated route. This allows, for instance, creation of a closed
9618 // polygon route
9619 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9620
9621 if (!duplicate) {
9622 int dlg_return;
9623 dlg_return =
9624 OCPNMessageBox(this,
9625 _("Replace this RoutePoint by the nearby "
9626 "Waypoint?"),
9627 _("OpenCPN RoutePoint change"),
9628 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9629 if (dlg_return == wxID_YES) {
9630 /*double confirmation if the dragged point has been manually
9631 * created which can be important and could be deleted
9632 * unintentionally*/
9633
9634 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9635 pNearbyPoint);
9636 current =
9637 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9638
9639 if (tail && current && (tail != current)) {
9640 int dlg_return1;
9641 connect = tail->GetIndexOf(pNearbyPoint);
9642 int index_current_route =
9643 current->GetIndexOf(m_pRoutePointEditTarget);
9644 index_last = current->GetIndexOf(current->GetLastPoint());
9645 dlg_return1 = wxID_NO;
9646 if (index_last ==
9647 index_current_route) { // we are dragging the last
9648 // point of the route
9649 if (connect != tail->GetnPoints()) { // anything to do?
9650
9651 wxString dmsg(
9652 _("Last part of route to be appended to dragged "
9653 "route?"));
9654 if (connect == 1)
9655 dmsg =
9656 _("Full route to be appended to dragged route?");
9657
9658 dlg_return1 = OCPNMessageBox(
9659 this, dmsg, _("OpenCPN Route Create"),
9660 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9661 if (dlg_return1 == wxID_YES) {
9662 appending = true;
9663 }
9664 }
9665 } else if (index_current_route ==
9666 1) { // dragging the first point of the route
9667 if (connect != 1) { // anything to do?
9668
9669 wxString dmsg(
9670 _("First part of route to be inserted into dragged "
9671 "route?"));
9672 if (connect == tail->GetnPoints())
9673 dmsg = _(
9674 "Full route to be inserted into dragged route?");
9675
9676 dlg_return1 = OCPNMessageBox(
9677 this, dmsg, _("OpenCPN Route Create"),
9678 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9679 if (dlg_return1 == wxID_YES) {
9680 inserting = true;
9681 }
9682 }
9683 }
9684 }
9685
9686 if (m_pRoutePointEditTarget->IsShared()) {
9687 // dlg_return = wxID_NO;
9688 dlg_return = OCPNMessageBox(
9689 this,
9690 _("Do you really want to delete and replace this "
9691 "WayPoint") +
9692 "\n" + _("which has been created manually?"),
9693 ("OpenCPN RoutePoint warning"),
9694 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9695 }
9696 }
9697 if (dlg_return == wxID_YES) {
9698 pMousePoint = pNearbyPoint;
9699 if (pMousePoint->m_bIsolatedMark) {
9700 pMousePoint->SetShared(true);
9701 }
9702 pMousePoint->m_bIsolatedMark =
9703 false; // definitely no longer isolated
9704 pMousePoint->m_bIsInRoute = true;
9705 }
9706 }
9707 }
9708 }
9709 if (!pMousePoint)
9710 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9711
9712 if (m_pEditRouteArray) {
9713 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9714 ir++) {
9715 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9716 if (g_pRouteMan->IsRouteValid(pr)) {
9717 if (pMousePoint) { // remove the dragged point and insert the
9718 // nearby
9719 auto *list = pr->pRoutePointList;
9720 auto pos = std::find(list->begin(), list->end(),
9721 m_pRoutePointEditTarget);
9722
9723 pSelect->DeleteAllSelectableRoutePoints(pr);
9724 pSelect->DeleteAllSelectableRouteSegments(pr);
9725
9726 pr->pRoutePointList->insert(pos, pMousePoint);
9727 pos = std::find(list->begin(), list->end(),
9728 m_pRoutePointEditTarget);
9729 pr->pRoutePointList->erase(pos);
9730
9731 pSelect->AddAllSelectableRouteSegments(pr);
9732 pSelect->AddAllSelectableRoutePoints(pr);
9733 }
9734 pr->FinalizeForRendering();
9735 pr->UpdateSegmentDistances();
9736 if (m_bRoutePoinDragging) {
9737 // pConfig->UpdateRoute(pr);
9738 NavObj_dB::GetInstance().UpdateRoute(pr);
9739 }
9740 }
9741 }
9742 }
9743
9744 // Update the RouteProperties Dialog, if currently shown
9745 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9746 if (m_pEditRouteArray) {
9747 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9748 ir++) {
9749 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9750 if (g_pRouteMan->IsRouteValid(pr)) {
9751 if (pRoutePropDialog->GetRoute() == pr) {
9752 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9753 }
9754 /* cannot edit track points anyway
9755 else if ( ( NULL !=
9756 pTrackPropDialog ) && ( pTrackPropDialog->IsShown() ) &&
9757 pTrackPropDialog->m_pTrack == pr ) {
9758 pTrackPropDialog->SetTrackAndUpdate(
9759 pr );
9760 }
9761 */
9762 }
9763 }
9764 }
9765 }
9766 if (pMousePoint) { // clear all about the dragged point
9767 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
9768 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
9769 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
9770 // Hide mark properties dialog if open on the replaced point
9771 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
9772 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
9773 g_pMarkInfoDialog->Hide();
9774
9775 delete m_pRoutePointEditTarget;
9776 m_lastRoutePointEditTarget = NULL;
9777 m_pRoutePointEditTarget = NULL;
9778 undo->AfterUndoableAction(pMousePoint);
9779 undo->InvalidateUndo();
9780 }
9781 }
9782 }
9783
9784 else if (m_bMarkEditing) { // End of way point drag
9785 if (m_pRoutePointEditTarget)
9786 if (m_bRoutePoinDragging) {
9787 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
9788 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
9789 }
9790 }
9791
9792 if (m_pRoutePointEditTarget)
9793 undo->AfterUndoableAction(m_pRoutePointEditTarget);
9794
9795 if (!m_pRoutePointEditTarget) {
9796 delete m_pEditRouteArray;
9797 m_pEditRouteArray = NULL;
9798 m_bRouteEditing = false;
9799 }
9800 m_bRoutePoinDragging = false;
9801
9802 if (appending) { // Appending to the route of which the last point is
9803 // dragged onto another route
9804
9805 // copy tail from connect until length to end of current after dragging
9806
9807 int length = tail->GetnPoints();
9808 for (int i = connect + 1; i <= length; i++) {
9809 current->AddPointAndSegment(tail->GetPoint(i), false);
9810 if (current) current->m_lastMousePointIndex = current->GetnPoints();
9811 m_routeState++;
9812 gFrame->RefreshAllCanvas();
9813 ret = true;
9814 }
9815 current->FinalizeForRendering();
9816 current->m_bIsBeingEdited = false;
9817 FinishRoute();
9818 g_pRouteMan->DeleteRoute(tail);
9819 }
9820 if (inserting) {
9821 pSelect->DeleteAllSelectableRoutePoints(current);
9822 pSelect->DeleteAllSelectableRouteSegments(current);
9823 for (int i = 1; i < connect; i++) { // numbering in the tail route
9824 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
9825 }
9826 pSelect->AddAllSelectableRouteSegments(current);
9827 pSelect->AddAllSelectableRoutePoints(current);
9828 current->FinalizeForRendering();
9829 current->m_bIsBeingEdited = false;
9830 g_pRouteMan->DeleteRoute(tail);
9831 }
9832
9833 // Update the RouteProperties Dialog, if currently shown
9834 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
9835 if (m_pEditRouteArray) {
9836 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount(); ir++) {
9837 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9838 if (g_pRouteMan->IsRouteValid(pr)) {
9839 if (pRoutePropDialog->GetRoute() == pr) {
9840 pRoutePropDialog->SetRouteAndUpdate(pr, true);
9841 }
9842 }
9843 }
9844 }
9845 }
9846
9847 } // g_btouch
9848
9849 else { // !g_btouch
9850 if (m_bRouteEditing) { // End of RoutePoint drag
9851 Route *tail = 0;
9852 Route *current = 0;
9853 bool appending = false;
9854 bool inserting = false;
9855 int connect = 0;
9856 int index_last;
9857 if (m_pRoutePointEditTarget) {
9858 m_pRoutePointEditTarget->m_bBlink = false;
9859 // Check to see if there is a nearby point which may replace the
9860 // dragged one
9861 RoutePoint *pMousePoint = NULL;
9862 if (m_bRoutePoinDragging && !m_pRoutePointEditTarget->m_bIsActive) {
9863 double nearby_radius_meters =
9864 g_Platform->GetSelectRadiusPix() / m_true_scale_ppm;
9865 RoutePoint *pNearbyPoint = pWayPointMan->GetOtherNearbyWaypoint(
9866 m_pRoutePointEditTarget->m_lat, m_pRoutePointEditTarget->m_lon,
9867 nearby_radius_meters, m_pRoutePointEditTarget->m_GUID);
9868 if (pNearbyPoint && !pNearbyPoint->m_bIsInLayer &&
9869 pWayPointMan->IsReallyVisible(pNearbyPoint)) {
9870 bool duplicate = false; // don't create duplicate point in routes
9871 if (m_pEditRouteArray && !pNearbyPoint->m_bIsolatedMark) {
9872 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9873 ir++) {
9874 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9875 if (pr && pr->pRoutePointList) {
9876 auto *list = pr->pRoutePointList;
9877 auto pos =
9878 std::find(list->begin(), list->end(), pNearbyPoint);
9879 if (pos != list->end()) {
9880 duplicate = true;
9881 break;
9882 }
9883 }
9884 }
9885 }
9886
9887 // Special case:
9888 // Allow "re-use" of a route's waypoints iff it is a simple
9889 // isolated route. This allows, for instance, creation of a closed
9890 // polygon route
9891 if (m_pEditRouteArray->GetCount() == 1) duplicate = false;
9892
9893 if (!duplicate) {
9894 int dlg_return;
9895 dlg_return =
9896 OCPNMessageBox(this,
9897 _("Replace this RoutePoint by the nearby "
9898 "Waypoint?"),
9899 _("OpenCPN RoutePoint change"),
9900 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9901 if (dlg_return == wxID_YES) {
9902 /*double confirmation if the dragged point has been manually
9903 * created which can be important and could be deleted
9904 * unintentionally*/
9905 tail = g_pRouteMan->FindVisibleRouteContainingWaypoint(
9906 pNearbyPoint);
9907 current =
9908 FindRouteContainingWaypoint(m_pRoutePointEditTarget);
9909
9910 if (tail && current && (tail != current)) {
9911 int dlg_return1;
9912 connect = tail->GetIndexOf(pNearbyPoint);
9913 int index_current_route =
9914 current->GetIndexOf(m_pRoutePointEditTarget);
9915 index_last = current->GetIndexOf(current->GetLastPoint());
9916 dlg_return1 = wxID_NO;
9917 if (index_last ==
9918 index_current_route) { // we are dragging the last
9919 // point of the route
9920 if (connect != tail->GetnPoints()) { // anything to do?
9921
9922 wxString dmsg(
9923 _("Last part of route to be appended to dragged "
9924 "route?"));
9925 if (connect == 1)
9926 dmsg =
9927 _("Full route to be appended to dragged route?");
9928
9929 dlg_return1 = OCPNMessageBox(
9930 this, dmsg, _("OpenCPN Route Create"),
9931 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9932 if (dlg_return1 == wxID_YES) {
9933 appending = true;
9934 }
9935 }
9936 } else if (index_current_route ==
9937 1) { // dragging the first point of the route
9938 if (connect != 1) { // anything to do?
9939
9940 wxString dmsg(
9941 _("First part of route to be inserted into dragged "
9942 "route?"));
9943 if (connect == tail->GetnPoints())
9944 dmsg = _(
9945 "Full route to be inserted into dragged route?");
9946
9947 dlg_return1 = OCPNMessageBox(
9948 this, dmsg, _("OpenCPN Route Create"),
9949 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9950 if (dlg_return1 == wxID_YES) {
9951 inserting = true;
9952 }
9953 }
9954 }
9955 }
9956
9957 if (m_pRoutePointEditTarget->IsShared()) {
9958 dlg_return = wxID_NO;
9959 dlg_return = OCPNMessageBox(
9960 this,
9961 _("Do you really want to delete and replace this "
9962 "WayPoint") +
9963 "\n" + _("which has been created manually?"),
9964 ("OpenCPN RoutePoint warning"),
9965 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
9966 }
9967 }
9968 if (dlg_return == wxID_YES) {
9969 pMousePoint = pNearbyPoint;
9970 if (pMousePoint->m_bIsolatedMark) {
9971 pMousePoint->SetShared(true);
9972 }
9973 pMousePoint->m_bIsolatedMark =
9974 false; // definitely no longer isolated
9975 pMousePoint->m_bIsInRoute = true;
9976 }
9977 }
9978 }
9979 }
9980 if (!pMousePoint)
9981 pSelect->UpdateSelectableRouteSegments(m_pRoutePointEditTarget);
9982
9983 if (m_pEditRouteArray) {
9984 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
9985 ir++) {
9986 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
9987 if (g_pRouteMan->IsRouteValid(pr)) {
9988 if (pMousePoint) { // replace dragged point by nearby one
9989 auto *list = pr->pRoutePointList;
9990 auto pos = std::find(list->begin(), list->end(),
9991 m_pRoutePointEditTarget);
9992
9993 pSelect->DeleteAllSelectableRoutePoints(pr);
9994 pSelect->DeleteAllSelectableRouteSegments(pr);
9995
9996 pr->pRoutePointList->insert(pos, pMousePoint);
9997 pos = std::find(list->begin(), list->end(),
9998 m_pRoutePointEditTarget);
9999 if (pos != list->end()) list->erase(pos);
10000 // pr->pRoutePointList->erase(pos + 1);
10001
10002 pSelect->AddAllSelectableRouteSegments(pr);
10003 pSelect->AddAllSelectableRoutePoints(pr);
10004 }
10005 pr->FinalizeForRendering();
10006 pr->UpdateSegmentDistances();
10007 pr->m_bIsBeingEdited = false;
10008
10009 if (m_bRoutePoinDragging) {
10010 // Special case optimization.
10011 // Dragging a single point of a route
10012 // without any point additions or re-ordering
10013 if (!pMousePoint)
10014 NavObj_dB::GetInstance().UpdateRoutePoint(
10015 m_pRoutePointEditTarget);
10016 else
10017 NavObj_dB::GetInstance().UpdateRoute(pr);
10018 }
10019 pr->SetHiLite(0);
10020 }
10021 }
10022 Refresh(false);
10023 }
10024
10025 if (appending) {
10026 // copy tail from connect until length to end of current after
10027 // dragging
10028
10029 int length = tail->GetnPoints();
10030 for (int i = connect + 1; i <= length; i++) {
10031 current->AddPointAndSegment(tail->GetPoint(i), false);
10032 if (current)
10033 current->m_lastMousePointIndex = current->GetnPoints();
10034 m_routeState++;
10035 gFrame->RefreshAllCanvas();
10036 ret = true;
10037 }
10038 current->FinalizeForRendering();
10039 current->m_bIsBeingEdited = false;
10040 FinishRoute();
10041 g_pRouteMan->DeleteRoute(tail);
10042 }
10043 if (inserting) {
10044 pSelect->DeleteAllSelectableRoutePoints(current);
10045 pSelect->DeleteAllSelectableRouteSegments(current);
10046 for (int i = 1; i < connect; i++) { // numbering in the tail route
10047 current->InsertPointAndSegment(tail->GetPoint(i), i - 1, false);
10048 }
10049 pSelect->AddAllSelectableRouteSegments(current);
10050 pSelect->AddAllSelectableRoutePoints(current);
10051 current->FinalizeForRendering();
10052 current->m_bIsBeingEdited = false;
10053 g_pRouteMan->DeleteRoute(tail);
10054 }
10055
10056 // Update the RouteProperties Dialog, if currently shown
10057 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10058 if (m_pEditRouteArray) {
10059 for (unsigned int ir = 0; ir < m_pEditRouteArray->GetCount();
10060 ir++) {
10061 Route *pr = (Route *)m_pEditRouteArray->Item(ir);
10062 if (g_pRouteMan->IsRouteValid(pr)) {
10063 if (pRoutePropDialog->GetRoute() == pr) {
10064 pRoutePropDialog->SetRouteAndUpdate(pr, true);
10065 }
10066 }
10067 }
10068 }
10069 }
10070
10071 if (pMousePoint) {
10072 // pConfig->DeleteWayPoint(m_pRoutePointEditTarget);
10073 NavObj_dB::GetInstance().DeleteRoutePoint(m_pRoutePointEditTarget);
10074 pWayPointMan->RemoveRoutePoint(m_pRoutePointEditTarget);
10075 // Hide mark properties dialog if open on the replaced point
10076 if ((NULL != g_pMarkInfoDialog) && (g_pMarkInfoDialog->IsShown()))
10077 if (m_pRoutePointEditTarget == g_pMarkInfoDialog->GetRoutePoint())
10078 g_pMarkInfoDialog->Hide();
10079
10080 delete m_pRoutePointEditTarget;
10081 m_lastRoutePointEditTarget = NULL;
10082 undo->AfterUndoableAction(pMousePoint);
10083 undo->InvalidateUndo();
10084 } else {
10085 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10086 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10087
10088 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10089 }
10090
10091 delete m_pEditRouteArray;
10092 m_pEditRouteArray = NULL;
10093 }
10094
10095 InvalidateGL();
10096 m_bRouteEditing = false;
10097 m_pRoutePointEditTarget = NULL;
10098
10099 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10100 ret = true;
10101 }
10102
10103 else if (m_bMarkEditing) { // end of Waypoint drag
10104 if (m_pRoutePointEditTarget) {
10105 if (m_bRoutePoinDragging) {
10106 // pConfig->UpdateWayPoint(m_pRoutePointEditTarget);
10107 NavObj_dB::GetInstance().UpdateRoutePoint(m_pRoutePointEditTarget);
10108 }
10109 undo->AfterUndoableAction(m_pRoutePointEditTarget);
10110 m_pRoutePointEditTarget->m_bRPIsBeingEdited = false;
10111 if (!g_bopengl) {
10112 wxRect wp_rect;
10113 RoutePointGui(*m_pRoutePointEditTarget)
10114 .CalculateDCRect(m_dc_route, this, &wp_rect);
10115 m_pRoutePointEditTarget->m_bPtIsSelected = false;
10116 RefreshRect(wp_rect, true);
10117 }
10118 }
10119 m_pRoutePointEditTarget = NULL;
10120 m_bMarkEditing = false;
10121 // if (m_toolBar && !m_toolBar->IsToolbarShown()) SurfaceToolbar();
10122 ret = true;
10123 }
10124
10125 else if (leftIsDown) { // left click for chart center
10126 leftIsDown = false;
10127 ret = false;
10128
10129 if (!g_btouch) {
10130 if (!m_bChartDragging && !m_bMeasure_Active) {
10131 } else {
10132 m_bChartDragging = false;
10133 }
10134 }
10135 }
10136 m_bRoutePoinDragging = false;
10137 } // !btouch
10138
10139 if (ret) return true;
10140 } // left up
10141
10142 if (event.RightDown()) {
10143 SetFocus(); // This is to let a plugin know which canvas is right-clicked
10144 last_drag.x = mx;
10145 last_drag.y = my;
10146
10147 if (g_btouch) {
10148 // if( m_pRoutePointEditTarget )
10149 // return false;
10150 }
10151
10152 ret = true;
10153 m_FinishRouteOnKillFocus = false;
10154 CallPopupMenu(mx, my);
10155 m_FinishRouteOnKillFocus = true;
10156 } // Right down
10157
10158 return ret;
10159}
10160
10161bool panleftIsDown;
10162bool ChartCanvas::MouseEventProcessCanvas(wxMouseEvent &event) {
10163 // Skip all mouse processing if shift is held.
10164 // This allows plugins to implement shift+drag behaviors.
10165 if (event.ShiftDown()) {
10166 return false;
10167 }
10168 int x, y;
10169 event.GetPosition(&x, &y);
10170
10171 x *= m_displayScale;
10172 y *= m_displayScale;
10173
10174 // Check for wheel rotation
10175 // ideally, should be just longer than the time between
10176 // processing accumulated mouse events from the event queue
10177 // as would happen during screen redraws.
10178 int wheel_dir = event.GetWheelRotation();
10179
10180 if (wheel_dir) {
10181 int mouse_wheel_oneshot = abs(wheel_dir) * 4; // msec
10182 wheel_dir = wheel_dir > 0 ? 1 : -1; // normalize
10183
10184 double factor = g_mouse_zoom_sensitivity;
10185 if (wheel_dir < 0) factor = 1 / factor;
10186
10187 if (g_bsmoothpanzoom) {
10188 if ((m_wheelstopwatch.Time() < m_wheelzoom_stop_oneshot)) {
10189 if (wheel_dir == m_last_wheel_dir) {
10190 m_wheelzoom_stop_oneshot += mouse_wheel_oneshot;
10191 // m_zoom_target /= factor;
10192 } else
10193 StopMovement();
10194 } else {
10195 m_wheelzoom_stop_oneshot = mouse_wheel_oneshot;
10196 m_wheelstopwatch.Start(0);
10197 // m_zoom_target = VPoint.chart_scale / factor;
10198 }
10199 }
10200
10201 m_last_wheel_dir = wheel_dir;
10202
10203 ZoomCanvas(factor, true, false);
10204 }
10205
10206 if (event.LeftDown()) {
10207 // Skip the first left click if it will cause a canvas focus shift
10208 if ((GetCanvasCount() > 1) && (this != g_focusCanvas)) {
10209 return false;
10210 }
10211
10212 last_drag.x = x, last_drag.y = y;
10213 panleftIsDown = true;
10214 }
10215
10216 if (event.LeftUp()) {
10217 if (panleftIsDown) { // leftUp for chart center, but only after a leftDown
10218 // seen here.
10219 panleftIsDown = false;
10220
10221 if (!g_btouch) {
10222 if (!m_bChartDragging && !m_bMeasure_Active) {
10223 switch (cursor_region) {
10224 case MID_RIGHT: {
10225 PanCanvas(100, 0);
10226 break;
10227 }
10228
10229 case MID_LEFT: {
10230 PanCanvas(-100, 0);
10231 break;
10232 }
10233
10234 case MID_TOP: {
10235 PanCanvas(0, 100);
10236 break;
10237 }
10238
10239 case MID_BOT: {
10240 PanCanvas(0, -100);
10241 break;
10242 }
10243
10244 case CENTER: {
10245 PanCanvas(x - GetVP().pix_width / 2, y - GetVP().pix_height / 2);
10246 break;
10247 }
10248 }
10249 } else {
10250 m_bChartDragging = false;
10251 }
10252 }
10253 }
10254 }
10255
10256 if (event.Dragging() && event.LeftIsDown()) {
10257 /*
10258 * fixed dragging.
10259 * On my Surface Pro 3 running Arch Linux there is no mouse down event
10260 * before the drag event. Hence, as there is no mouse down event, last_drag
10261 * is not reset before the drag. And that results in one single drag
10262 * session, meaning you cannot drag the map a few miles north, lift your
10263 * finger, and the go even further north. Instead, the map resets itself
10264 * always to the very first drag start (since there is not reset of
10265 * last_drag).
10266 *
10267 * Besides, should not left down and dragging be enough of a situation to
10268 * start a drag procedure?
10269 *
10270 * Anyways, guarded it to be active in touch situations only.
10271 */
10272 if (g_btouch && !m_inPinch) {
10273 struct timespec now;
10274 clock_gettime(CLOCK_MONOTONIC, &now);
10275 uint64_t tnow = (1e9 * now.tv_sec) + now.tv_nsec;
10276
10277 bool trigger_hold = false;
10278 if (false == m_bChartDragging) {
10279 if (m_DragTrigger < 0) {
10280 // printf("\ntrigger1\n");
10281 m_DragTrigger = 0;
10282 m_DragTriggerStartTime = tnow;
10283 trigger_hold = true;
10284 } else {
10285 if (((tnow - m_DragTriggerStartTime) / 1e6) > 20) { // m sec
10286 m_DragTrigger = -1; // Reset trigger
10287 // printf("trigger fired\n");
10288 }
10289 }
10290 }
10291 if (trigger_hold) return true;
10292
10293 if (false == m_bChartDragging) {
10294 // printf("starting drag\n");
10295 // Reset drag calculation members
10296 last_drag.x = x - 1, last_drag.y = y - 1;
10297 m_bChartDragging = true;
10298 m_chart_drag_total_time = 0;
10299 m_chart_drag_total_x = 0;
10300 m_chart_drag_total_y = 0;
10301 m_inertia_last_drag_x = x;
10302 m_inertia_last_drag_y = y;
10303 m_drag_vec_x.clear();
10304 m_drag_vec_y.clear();
10305 m_drag_vec_t.clear();
10306 m_last_drag_time = tnow;
10307 }
10308
10309 // Calculate and store drag dynamics.
10310 uint64_t delta_t = tnow - m_last_drag_time;
10311 double delta_tf = delta_t / 1e9;
10312
10313 m_chart_drag_total_time += delta_tf;
10314 m_chart_drag_total_x += m_inertia_last_drag_x - x;
10315 m_chart_drag_total_y += m_inertia_last_drag_y - y;
10316
10317 m_drag_vec_x.push_back(m_inertia_last_drag_x - x);
10318 m_drag_vec_y.push_back(m_inertia_last_drag_y - y);
10319 m_drag_vec_t.push_back(delta_tf);
10320
10321 m_inertia_last_drag_x = x;
10322 m_inertia_last_drag_y = y;
10323 m_last_drag_time = tnow;
10324
10325 if ((abs(last_drag.x - x) > 2) || (abs(last_drag.y - y) > 2)) {
10326 m_bChartDragging = true;
10327 StartTimedMovement();
10328 m_pan_drag.x += last_drag.x - x;
10329 m_pan_drag.y += last_drag.y - y;
10330 last_drag.x = x, last_drag.y = y;
10331 }
10332 } else if (!g_btouch) {
10333 if ((last_drag.x != x) || (last_drag.y != y)) {
10334 if (!m_routeState) { // Correct fault on wx32/gtk3, uncommanded
10335 // dragging on route create.
10336 // github #2994
10337 m_bChartDragging = true;
10338 StartTimedMovement();
10339 m_pan_drag.x += last_drag.x - x;
10340 m_pan_drag.y += last_drag.y - y;
10341 last_drag.x = x, last_drag.y = y;
10342 }
10343 }
10344 }
10345
10346 // Handle some special cases
10347 if (g_btouch) {
10348 if ((m_bMeasure_Active && m_nMeasureState) || (m_routeState)) {
10349 // deactivate next LeftUp to ovoid creating an unexpected point
10350 m_ignore_next_leftup = true;
10351 m_DoubleClickTimer->Start();
10352 singleClickEventIsValid = false;
10353 }
10354 }
10355 }
10356
10357 return true;
10358}
10359
10360void ChartCanvas::MouseEvent(wxMouseEvent &event) {
10361 if (MouseEventOverlayWindows(event)) return;
10362
10363 if (MouseEventSetup(event)) return; // handled, no further action required
10364
10365 bool nm = MouseEventProcessObjects(event);
10366 if (!nm) MouseEventProcessCanvas(event);
10367}
10368
10369void ChartCanvas::SetCanvasCursor(wxMouseEvent &event) {
10370 // Switch to the appropriate cursor on mouse movement
10371
10372 wxCursor *ptarget_cursor = pCursorArrow;
10373 if (!pPlugIn_Cursor) {
10374 ptarget_cursor = pCursorArrow;
10375 if ((!m_routeState) &&
10376 (!m_bMeasure_Active) /*&& ( !m_bCM93MeasureOffset_Active )*/) {
10377 if (cursor_region == MID_RIGHT) {
10378 ptarget_cursor = pCursorRight;
10379 } else if (cursor_region == MID_LEFT) {
10380 ptarget_cursor = pCursorLeft;
10381 } else if (cursor_region == MID_TOP) {
10382 ptarget_cursor = pCursorDown;
10383 } else if (cursor_region == MID_BOT) {
10384 ptarget_cursor = pCursorUp;
10385 } else {
10386 ptarget_cursor = pCursorArrow;
10387 }
10388 } else if (m_bMeasure_Active ||
10389 m_routeState) // If Measure tool use Pencil Cursor
10390 ptarget_cursor = pCursorPencil;
10391 } else {
10392 ptarget_cursor = pPlugIn_Cursor;
10393 }
10394
10395 SetCursor(*ptarget_cursor);
10396}
10397
10398void ChartCanvas::LostMouseCapture(wxMouseCaptureLostEvent &event) {
10399 SetCursor(*pCursorArrow);
10400}
10401
10402void ChartCanvas::ShowObjectQueryWindow(int x, int y, float zlat, float zlon) {
10403 ChartPlugInWrapper *target_plugin_chart = NULL;
10404 s57chart *Chs57 = NULL;
10405 wxFileName file;
10406 wxArrayString files;
10407
10408 ChartBase *target_chart = GetChartAtCursor();
10409 if (target_chart) {
10410 file.Assign(target_chart->GetFullPath());
10411 if ((target_chart->GetChartType() == CHART_TYPE_PLUGIN) &&
10412 (target_chart->GetChartFamily() == CHART_FAMILY_VECTOR))
10413 target_plugin_chart = dynamic_cast<ChartPlugInWrapper *>(target_chart);
10414 else
10415 Chs57 = dynamic_cast<s57chart *>(target_chart);
10416 } else { // target_chart = null, might be mbtiles
10417 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
10418 unsigned int im = stackIndexArray.size();
10419 int scale = 2147483647; // max 32b integer
10420 if (VPoint.b_quilt && im > 0) {
10421 for (unsigned int is = 0; is < im; is++) {
10422 if (ChartData->GetDBChartType(stackIndexArray[is]) ==
10423 CHART_TYPE_MBTILES) {
10424 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) continue;
10425 double lat, lon;
10426 VPoint.GetLLFromPix(wxPoint(mouse_x, mouse_y), &lat, &lon);
10427 if (ChartData->GetChartTableEntry(stackIndexArray[is])
10428 .GetBBox()
10429 .Contains(lat, lon)) {
10430 if (ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale() <
10431 scale) {
10432 scale =
10433 ChartData->GetChartTableEntry(stackIndexArray[is]).GetScale();
10434 file.Assign(ChartData->GetDBChartFileName(stackIndexArray[is]));
10435 }
10436 }
10437 }
10438 }
10439 }
10440 }
10441
10442 std::vector<Ais8_001_22 *> area_notices;
10443
10444 if (g_pAIS && m_bShowAIS && g_bShowAreaNotices) {
10445 float vp_scale = GetVPScale();
10446
10447 for (const auto &target : g_pAIS->GetAreaNoticeSourcesList()) {
10448 auto target_data = target.second;
10449 if (!target_data->area_notices.empty()) {
10450 for (auto &ani : target_data->area_notices) {
10451 Ais8_001_22 &area_notice = ani.second;
10452
10453 BoundingBox bbox;
10454
10455 for (Ais8_001_22_SubAreaList::iterator sa =
10456 area_notice.sub_areas.begin();
10457 sa != area_notice.sub_areas.end(); ++sa) {
10458 switch (sa->shape) {
10459 case AIS8_001_22_SHAPE_CIRCLE: {
10460 wxPoint target_point;
10461 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10462 bbox.Expand(target_point);
10463 if (sa->radius_m > 0.0) bbox.EnLarge(sa->radius_m * vp_scale);
10464 break;
10465 }
10466 case AIS8_001_22_SHAPE_RECT: {
10467 wxPoint target_point;
10468 GetCanvasPointPix(sa->latitude, sa->longitude, &target_point);
10469 bbox.Expand(target_point);
10470 if (sa->e_dim_m > sa->n_dim_m)
10471 bbox.EnLarge(sa->e_dim_m * vp_scale);
10472 else
10473 bbox.EnLarge(sa->n_dim_m * vp_scale);
10474 break;
10475 }
10476 case AIS8_001_22_SHAPE_POLYGON:
10477 case AIS8_001_22_SHAPE_POLYLINE: {
10478 for (int i = 0; i < 4; ++i) {
10479 double lat = sa->latitude;
10480 double lon = sa->longitude;
10481 ll_gc_ll(lat, lon, sa->angles[i], sa->dists_m[i] / 1852.0,
10482 &lat, &lon);
10483 wxPoint target_point;
10484 GetCanvasPointPix(lat, lon, &target_point);
10485 bbox.Expand(target_point);
10486 }
10487 break;
10488 }
10489 case AIS8_001_22_SHAPE_SECTOR: {
10490 double lat1 = sa->latitude;
10491 double lon1 = sa->longitude;
10492 double lat, lon;
10493 wxPoint target_point;
10494 GetCanvasPointPix(lat1, lon1, &target_point);
10495 bbox.Expand(target_point);
10496 for (int i = 0; i < 18; ++i) {
10497 ll_gc_ll(
10498 lat1, lon1,
10499 sa->left_bound_deg +
10500 i * (sa->right_bound_deg - sa->left_bound_deg) / 18,
10501 sa->radius_m / 1852.0, &lat, &lon);
10502 GetCanvasPointPix(lat, lon, &target_point);
10503 bbox.Expand(target_point);
10504 }
10505 ll_gc_ll(lat1, lon1, sa->right_bound_deg, sa->radius_m / 1852.0,
10506 &lat, &lon);
10507 GetCanvasPointPix(lat, lon, &target_point);
10508 bbox.Expand(target_point);
10509 break;
10510 }
10511 }
10512 }
10513
10514 if (bbox.GetValid() && bbox.PointInBox(x, y)) {
10515 area_notices.push_back(&area_notice);
10516 }
10517 }
10518 }
10519 }
10520 }
10521
10522 if (target_chart || !area_notices.empty() || file.HasName()) {
10523 // Go get the array of all objects at the cursor lat/lon
10524 int sel_rad_pix = 5;
10525 float SelectRadius = sel_rad_pix / (GetVP().view_scale_ppm * 1852 * 60);
10526
10527 // Make sure we always get the lights from an object, even if we are
10528 // currently not displaying lights on the chart.
10529
10530 SetCursor(wxCURSOR_WAIT);
10531 bool lightsVis = m_encShowLights; // gFrame->ToggleLights( false );
10532 if (!lightsVis) SetShowENCLights(true);
10533 ;
10534
10535 ListOfObjRazRules *rule_list = NULL;
10536 ListOfPI_S57Obj *pi_rule_list = NULL;
10537 if (Chs57)
10538 rule_list =
10539 Chs57->GetObjRuleListAtLatLon(zlat, zlon, SelectRadius, &GetVP());
10540 else if (target_plugin_chart)
10541 pi_rule_list = g_pi_manager->GetPlugInObjRuleListAtLatLon(
10542 target_plugin_chart, zlat, zlon, SelectRadius, GetVP());
10543
10544 ListOfObjRazRules *overlay_rule_list = NULL;
10545 ChartBase *overlay_chart = GetOverlayChartAtCursor();
10546 s57chart *CHs57_Overlay = dynamic_cast<s57chart *>(overlay_chart);
10547
10548 if (CHs57_Overlay) {
10549 overlay_rule_list = CHs57_Overlay->GetObjRuleListAtLatLon(
10550 zlat, zlon, SelectRadius, &GetVP());
10551 }
10552
10553 if (!lightsVis) SetShowENCLights(false);
10554
10555 wxString objText;
10556 wxFont *dFont = FontMgr::Get().GetFont(_("ObjectQuery"));
10557 wxString face = dFont->GetFaceName();
10558
10559 if (NULL == g_pObjectQueryDialog) {
10561 new S57QueryDialog(this, -1, _("Object Query"), wxDefaultPosition,
10562 wxSize(g_S57_dialog_sx, g_S57_dialog_sy));
10563 }
10564
10565 wxColor bg = g_pObjectQueryDialog->GetBackgroundColour();
10566 wxColor fg = FontMgr::Get().GetFontColor(_("ObjectQuery"));
10567
10568#ifdef __WXOSX__
10569 // Auto Adjustment for dark mode
10570 fg = g_pObjectQueryDialog->GetForegroundColour();
10571#endif
10572
10573 objText.Printf(
10574 "<html><body bgcolor=#%02x%02x%02x><font color=#%02x%02x%02x>",
10575 bg.Red(), bg.Green(), bg.Blue(), fg.Red(), fg.Green(), fg.Blue());
10576
10577#ifdef __WXOSX__
10578 int points = dFont->GetPointSize();
10579#else
10580 int points = dFont->GetPointSize() + 1;
10581#endif
10582
10583 int sizes[7];
10584 for (int i = -2; i < 5; i++) {
10585 sizes[i + 2] = points + i + (i > 0 ? i : 0);
10586 }
10587 g_pObjectQueryDialog->m_phtml->SetFonts(face, face, sizes);
10588
10589 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText += "<i>";
10590
10591 if (overlay_rule_list && CHs57_Overlay) {
10592 objText << CHs57_Overlay->CreateObjDescriptions(overlay_rule_list);
10593 objText << "<hr noshade>";
10594 }
10595
10596 for (std::vector<Ais8_001_22 *>::iterator an = area_notices.begin();
10597 an != area_notices.end(); ++an) {
10598 objText << "<b>AIS Area Notice:</b> ";
10599 objText << ais8_001_22_notice_names[(*an)->notice_type];
10600 for (std::vector<Ais8_001_22_SubArea>::iterator sa =
10601 (*an)->sub_areas.begin();
10602 sa != (*an)->sub_areas.end(); ++sa)
10603 if (!sa->text.empty()) objText << sa->text;
10604 objText << "<br>expires: " << (*an)->expiry_time.Format();
10605 objText << "<hr noshade>";
10606 }
10607
10608 if (Chs57)
10609 objText << Chs57->CreateObjDescriptions(rule_list);
10610 else if (target_plugin_chart)
10611 objText << g_pi_manager->CreateObjDescriptions(target_plugin_chart,
10612 pi_rule_list);
10613
10614 if (wxFONTSTYLE_ITALIC == dFont->GetStyle()) objText << "</i>";
10615
10616 // Add the additional info files
10617 wxString AddFiles, filenameOK;
10618 int filecount = 0;
10619 if (!target_plugin_chart) { // plugincharts shoud take care of this in the
10620 // plugin
10621
10622 AddFiles = wxString::Format(
10623 "<hr noshade><br><b>Additional info files attached to: </b> "
10624 "<font "
10625 "size=-2>%s</font><br><table border=0 cellspacing=0 "
10626 "cellpadding=3>",
10627 file.GetFullName());
10628 file.Normalize();
10629 file.Assign(file.GetPath(), "");
10630 wxDir dir(file.GetFullPath());
10631 wxString filename;
10632 bool cont = dir.GetFirst(&filename, "", wxDIR_FILES);
10633 while (cont) {
10634 file.Assign(dir.GetNameWithSep().append(filename));
10635 wxString FormatString =
10636 "<td valign=top><font size=-2><a "
10637 "href=\"%s\">%s</a></font></td>";
10638 if (g_ObjQFileExt.Find(file.GetExt().Lower()) != wxNOT_FOUND) {
10639 filenameOK = file.GetFullPath(); // remember last valid name
10640 // we are making a 3 columns table. New row only every third file
10641 if (3 * ((int)filecount / 3) == filecount)
10642 FormatString.Prepend("<tr>"); // new row
10643 else
10644 FormatString.Prepend(
10645 "<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp</td>"); // an empty
10646 // spacer column
10647
10648 AddFiles << wxString::Format(FormatString, file.GetFullPath(),
10649 file.GetFullName());
10650 filecount++;
10651 }
10652 cont = dir.GetNext(&filename);
10653 }
10654 objText << AddFiles << "</table>";
10655 }
10656 objText << "</font>";
10657 objText << "</body></html>";
10658
10659 if (Chs57 || target_plugin_chart || (filecount > 1)) {
10660 g_pObjectQueryDialog->SetHTMLPage(objText);
10661 g_pObjectQueryDialog->Show();
10662 }
10663 if ((!Chs57 && filecount == 1)) { // only one file?, show direktly
10664 // generate an event to avoid double code
10665 wxHtmlLinkInfo hli(filenameOK);
10666 wxHtmlLinkEvent hle(1, hli);
10667 g_pObjectQueryDialog->OnHtmlLinkClicked(hle);
10668 }
10669
10670 if (rule_list) rule_list->Clear();
10671 delete rule_list;
10672
10673 if (overlay_rule_list) overlay_rule_list->Clear();
10674 delete overlay_rule_list;
10675
10676 if (pi_rule_list) pi_rule_list->Clear();
10677 delete pi_rule_list;
10678
10679 SetCursor(wxCURSOR_ARROW);
10680 }
10681}
10682
10683void ChartCanvas::ShowMarkPropertiesDialog(RoutePoint *markPoint) {
10684 bool bNew = false;
10685 if (!g_pMarkInfoDialog) { // There is one global instance of the MarkProp
10686 // Dialog
10687 g_pMarkInfoDialog = new MarkInfoDlg(this);
10688 bNew = true;
10689 }
10690
10691 if (1 /*g_bresponsive*/) {
10692 wxSize canvas_size = GetSize();
10693
10694 int best_size_y = wxMin(400 / OCPN_GetWinDIPScaleFactor(), canvas_size.y);
10695 g_pMarkInfoDialog->SetMinSize(wxSize(-1, best_size_y));
10696
10697 g_pMarkInfoDialog->Layout();
10698
10699 wxPoint canvas_pos = GetPosition();
10700 wxSize fitted_size = g_pMarkInfoDialog->GetSize();
10701
10702 bool newFit = false;
10703 if (canvas_size.x < fitted_size.x) {
10704 fitted_size.x = canvas_size.x - 40;
10705 if (canvas_size.y < fitted_size.y)
10706 fitted_size.y -= 40; // scrollbar added
10707 }
10708 if (canvas_size.y < fitted_size.y) {
10709 fitted_size.y = canvas_size.y - 40;
10710 if (canvas_size.x < fitted_size.x)
10711 fitted_size.x -= 40; // scrollbar added
10712 }
10713
10714 if (newFit) {
10715 g_pMarkInfoDialog->SetSize(fitted_size);
10716 g_pMarkInfoDialog->Centre();
10717 }
10718 }
10719
10720 markPoint->m_bRPIsBeingEdited = false;
10721
10722 wxString title_base = _("Mark Properties");
10723 if (markPoint->m_bIsInRoute) {
10724 title_base = _("Waypoint Properties");
10725 }
10726 g_pMarkInfoDialog->SetRoutePoint(markPoint);
10727 g_pMarkInfoDialog->UpdateProperties();
10728 if (markPoint->m_bIsInLayer) {
10729 wxString caption(wxString::Format("%s, %s: %s", title_base, _("Layer"),
10730 GetLayerName(markPoint->m_LayerID)));
10731 g_pMarkInfoDialog->SetDialogTitle(caption);
10732 } else
10733 g_pMarkInfoDialog->SetDialogTitle(title_base);
10734
10735 g_pMarkInfoDialog->Show();
10736 g_pMarkInfoDialog->Raise();
10737 g_pMarkInfoDialog->InitialFocus();
10738 if (bNew) g_pMarkInfoDialog->CenterOnScreen();
10739}
10740
10741void ChartCanvas::ShowRoutePropertiesDialog(wxString title, Route *selected) {
10742 pRoutePropDialog = RoutePropDlgImpl::getInstance(this);
10743 pRoutePropDialog->SetRouteAndUpdate(selected);
10744 // pNew->UpdateProperties();
10745 pRoutePropDialog->Show();
10746 pRoutePropDialog->Raise();
10747 return;
10748 pRoutePropDialog = RoutePropDlgImpl::getInstance(
10749 this); // There is one global instance of the RouteProp Dialog
10750
10751 if (g_bresponsive) {
10752 wxSize canvas_size = GetSize();
10753 wxPoint canvas_pos = GetPosition();
10754 wxSize fitted_size = pRoutePropDialog->GetSize();
10755 ;
10756
10757 if (canvas_size.x < fitted_size.x) {
10758 fitted_size.x = canvas_size.x;
10759 if (canvas_size.y < fitted_size.y)
10760 fitted_size.y -= 20; // scrollbar added
10761 }
10762 if (canvas_size.y < fitted_size.y) {
10763 fitted_size.y = canvas_size.y;
10764 if (canvas_size.x < fitted_size.x)
10765 fitted_size.x -= 20; // scrollbar added
10766 }
10767
10768 pRoutePropDialog->SetSize(fitted_size);
10769 pRoutePropDialog->Centre();
10770
10771 // int xp = (canvas_size.x - fitted_size.x)/2;
10772 // int yp = (canvas_size.y - fitted_size.y)/2;
10773
10774 wxPoint xxp = ClientToScreen(canvas_pos);
10775 // pRoutePropDialog->Move(xxp.x + xp, xxp.y + yp);
10776 }
10777
10778 pRoutePropDialog->SetRouteAndUpdate(selected);
10779
10780 pRoutePropDialog->Show();
10781
10782 Refresh(false);
10783}
10784
10785void ChartCanvas::ShowTrackPropertiesDialog(Track *selected) {
10786 pTrackPropDialog = TrackPropDlg::getInstance(
10787 this); // There is one global instance of the RouteProp Dialog
10788
10789 pTrackPropDialog->SetTrackAndUpdate(selected);
10791
10792 pTrackPropDialog->Show();
10793
10794 Refresh(false);
10795}
10796
10797void pupHandler_PasteWaypoint() {
10798 Kml kml;
10799
10800 int pasteBuffer = kml.ParsePasteBuffer();
10801 RoutePoint *pasted = kml.GetParsedRoutePoint();
10802 if (!pasted) return;
10803
10804 double nearby_radius_meters =
10805 g_Platform->GetSelectRadiusPix() /
10806 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10807
10808 RoutePoint *nearPoint = pWayPointMan->GetNearbyWaypoint(
10809 pasted->m_lat, pasted->m_lon, nearby_radius_meters);
10810
10811 int answer = wxID_NO;
10812 if (nearPoint && !nearPoint->m_bIsInLayer) {
10813 wxString msg;
10814 msg << _(
10815 "There is an existing waypoint at the same location as the one you are "
10816 "pasting. Would you like to merge the pasted data with it?\n\n");
10817 msg << _("Answering 'No' will create a new waypoint at the same location.");
10818 answer = OCPNMessageBox(NULL, msg, _("Merge waypoint?"),
10819 (long)wxYES_NO | wxCANCEL | wxNO_DEFAULT);
10820 }
10821
10822 if (answer == wxID_YES) {
10823 nearPoint->SetName(pasted->GetName());
10824 nearPoint->m_MarkDescription = pasted->m_MarkDescription;
10825 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10826 pRouteManagerDialog->UpdateWptListCtrl();
10827 }
10828
10829 if (answer == wxID_NO) {
10830 RoutePoint *newPoint = new RoutePoint(pasted);
10831 newPoint->m_bIsolatedMark = true;
10832 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10833 newPoint);
10834 // pConfig->AddNewWayPoint(newPoint, -1);
10835 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10836
10837 pWayPointMan->AddRoutePoint(newPoint);
10838 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
10839 pRouteManagerDialog->UpdateWptListCtrl();
10840 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10841 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10842 }
10843
10844 gFrame->InvalidateAllGL();
10845 gFrame->RefreshAllCanvas(false);
10846}
10847
10848void pupHandler_PasteRoute() {
10849 Kml kml;
10850
10851 int pasteBuffer = kml.ParsePasteBuffer();
10852 Route *pasted = kml.GetParsedRoute();
10853 if (!pasted) return;
10854
10855 double nearby_radius_meters =
10856 g_Platform->GetSelectRadiusPix() /
10857 gFrame->GetPrimaryCanvas()->GetCanvasTrueScale();
10858
10859 RoutePoint *curPoint;
10860 RoutePoint *nearPoint;
10861 RoutePoint *prevPoint = NULL;
10862
10863 bool mergepoints = false;
10864 bool createNewRoute = true;
10865 int existingWaypointCounter = 0;
10866
10867 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10868 curPoint = pasted->GetPoint(i); // NB! n starts at 1 !
10869 nearPoint = pWayPointMan->GetNearbyWaypoint(
10870 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10871 if (nearPoint) {
10872 mergepoints = true;
10873 existingWaypointCounter++;
10874 // Small hack here to avoid both extending RoutePoint and repeating all
10875 // the GetNearbyWaypoint calculations. Use existin data field in
10876 // RoutePoint as temporary storage.
10877 curPoint->m_bPtIsSelected = true;
10878 }
10879 }
10880
10881 int answer = wxID_NO;
10882 if (mergepoints) {
10883 wxString msg;
10884 msg << _(
10885 "There are existing waypoints at the same location as some of the ones "
10886 "you are pasting. Would you like to just merge the pasted data into "
10887 "them?\n\n");
10888 msg << _("Answering 'No' will create all new waypoints for this route.");
10889 answer = OCPNMessageBox(NULL, msg, _("Merge waypoints?"),
10890 (long)wxYES_NO | wxCANCEL | wxYES_DEFAULT);
10891
10892 if (answer == wxID_CANCEL) {
10893 return;
10894 }
10895 }
10896
10897 // If all waypoints exist since before, and a route with the same name, we
10898 // don't create a new route.
10899 if (mergepoints && answer == wxID_YES &&
10900 existingWaypointCounter == pasted->GetnPoints()) {
10901 for (Route *proute : *pRouteList) {
10902 if (pasted->m_RouteNameString == proute->m_RouteNameString) {
10903 createNewRoute = false;
10904 break;
10905 }
10906 }
10907 }
10908
10909 Route *newRoute = 0;
10910 RoutePoint *newPoint = 0;
10911
10912 if (createNewRoute) {
10913 newRoute = new Route();
10914 newRoute->m_RouteNameString = pasted->m_RouteNameString;
10915 }
10916
10917 for (int i = 1; i <= pasted->GetnPoints(); i++) {
10918 curPoint = pasted->GetPoint(i);
10919 if (answer == wxID_YES && curPoint->m_bPtIsSelected) {
10920 curPoint->m_bPtIsSelected = false;
10921 newPoint = pWayPointMan->GetNearbyWaypoint(
10922 curPoint->m_lat, curPoint->m_lon, nearby_radius_meters);
10923 newPoint->SetName(curPoint->GetName());
10924 newPoint->m_MarkDescription = curPoint->m_MarkDescription;
10925
10926 if (createNewRoute) newRoute->AddPoint(newPoint);
10927 } else {
10928 curPoint->m_bPtIsSelected = false;
10929
10930 newPoint = new RoutePoint(curPoint);
10931 newPoint->m_bIsolatedMark = false;
10932 newPoint->SetIconName("circle");
10933 newPoint->m_bIsVisible = true;
10934 newPoint->m_bShowName = false;
10935 newPoint->SetShared(false);
10936
10937 newRoute->AddPoint(newPoint);
10938 pSelect->AddSelectableRoutePoint(newPoint->m_lat, newPoint->m_lon,
10939 newPoint);
10940 // pConfig->AddNewWayPoint(newPoint, -1);
10941 NavObj_dB::GetInstance().InsertRoutePoint(newPoint);
10942 pWayPointMan->AddRoutePoint(newPoint);
10943 }
10944 if (i > 1 && createNewRoute)
10945 pSelect->AddSelectableRouteSegment(prevPoint->m_lat, prevPoint->m_lon,
10946 curPoint->m_lat, curPoint->m_lon,
10947 prevPoint, newPoint, newRoute);
10948 prevPoint = newPoint;
10949 }
10950
10951 if (createNewRoute) {
10952 pRouteList->push_back(newRoute);
10953 // pConfig->AddNewRoute(newRoute); // use auto next num
10954 NavObj_dB::GetInstance().InsertRoute(newRoute);
10955
10956 if (pRoutePropDialog && pRoutePropDialog->IsShown()) {
10957 pRoutePropDialog->SetRouteAndUpdate(newRoute);
10958 }
10959
10960 if (pRouteManagerDialog && pRouteManagerDialog->IsShown()) {
10961 pRouteManagerDialog->UpdateRouteListCtrl();
10962 pRouteManagerDialog->UpdateWptListCtrl();
10963 }
10964 gFrame->InvalidateAllGL();
10965 gFrame->RefreshAllCanvas(false);
10966 }
10967 if (RoutePointGui(*newPoint).IsVisibleSelectable(g_focusCanvas))
10968 RoutePointGui(*newPoint).ShowScaleWarningMessage(g_focusCanvas);
10969}
10970
10971void pupHandler_PasteTrack() {
10972 Kml kml;
10973
10974 int pasteBuffer = kml.ParsePasteBuffer();
10975 Track *pasted = kml.GetParsedTrack();
10976 if (!pasted) return;
10977
10978 TrackPoint *curPoint;
10979
10980 Track *newTrack = new Track();
10981 TrackPoint *newPoint;
10982 TrackPoint *prevPoint = NULL;
10983
10984 newTrack->SetName(pasted->GetName());
10985
10986 for (int i = 0; i < pasted->GetnPoints(); i++) {
10987 curPoint = pasted->GetPoint(i);
10988
10989 newPoint = new TrackPoint(curPoint);
10990
10991 wxDateTime now = wxDateTime::Now();
10992 newPoint->SetCreateTime(curPoint->GetCreateTime());
10993
10994 newTrack->AddPoint(newPoint);
10995
10996 if (prevPoint)
10997 pSelect->AddSelectableTrackSegment(prevPoint->m_lat, prevPoint->m_lon,
10998 newPoint->m_lat, newPoint->m_lon,
10999 prevPoint, newPoint, newTrack);
11000
11001 prevPoint = newPoint;
11002 }
11003
11004 g_TrackList.push_back(newTrack);
11005 // pConfig->AddNewTrack(newTrack);
11006 NavObj_dB::GetInstance().InsertTrack(newTrack);
11007
11008 gFrame->InvalidateAllGL();
11009 gFrame->RefreshAllCanvas(false);
11010}
11011
11012bool ChartCanvas::InvokeCanvasMenu(int x, int y, int seltype) {
11013 wxJSONValue v;
11014 v["CanvasIndex"] = GetCanvasIndexUnderMouse();
11015 v["CursorPosition_x"] = x;
11016 v["CursorPosition_y"] = y;
11017 // Send a limited set of selection types depending on what is
11018 // found under the mouse point.
11019 if (seltype & SELTYPE_UNKNOWN) v["SelectionType"] = "Canvas";
11020 if (seltype & SELTYPE_ROUTEPOINT) v["SelectionType"] = "RoutePoint";
11021 if (seltype & SELTYPE_AISTARGET) v["SelectionType"] = "AISTarget";
11022
11023 wxJSONWriter w;
11024 wxString out;
11025 w.Write(v, out);
11026 SendMessageToAllPlugins("OCPN_CONTEXT_CLICK", out);
11027
11028 json_msg.Notify(std::make_shared<wxJSONValue>(v), "OCPN_CONTEXT_CLICK");
11029
11030#if 0
11031#define SELTYPE_UNKNOWN 0x0001
11032#define SELTYPE_ROUTEPOINT 0x0002
11033#define SELTYPE_ROUTESEGMENT 0x0004
11034#define SELTYPE_TIDEPOINT 0x0008
11035#define SELTYPE_CURRENTPOINT 0x0010
11036#define SELTYPE_ROUTECREATE 0x0020
11037#define SELTYPE_AISTARGET 0x0040
11038#define SELTYPE_MARKPOINT 0x0080
11039#define SELTYPE_TRACKSEGMENT 0x0100
11040#define SELTYPE_DRAGHANDLE 0x0200
11041#endif
11042
11043 if (g_bhide_context_menus) return true;
11044 m_canvasMenu = new CanvasMenuHandler(this, m_pSelectedRoute, m_pSelectedTrack,
11045 m_pFoundRoutePoint, m_FoundAIS_MMSI,
11046 m_pIDXCandidate, m_nmea_log);
11047
11048 Connect(
11049 wxEVT_COMMAND_MENU_SELECTED,
11050 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11051
11052#ifdef __WXGTK__
11053 // Funny requirement here for gtk, to clear the menu trigger event
11054 // TODO
11055 // Causes a slight "flasH" of the menu,
11056 if (m_inLongPress) {
11057 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11058 m_inLongPress = false;
11059 }
11060#endif
11061
11062 m_canvasMenu->CanvasPopupMenu(x, y, seltype);
11063
11064 Disconnect(
11065 wxEVT_COMMAND_MENU_SELECTED,
11066 (wxObjectEventFunction)(wxEventFunction)&ChartCanvas::PopupMenuHandler);
11067
11068 delete m_canvasMenu;
11069 m_canvasMenu = NULL;
11070
11071#ifdef __WXQT__
11072 // gFrame->SurfaceToolbar();
11073 // g_MainToolbar->Raise();
11074#endif
11075
11076 return true;
11077}
11078
11079void ChartCanvas::PopupMenuHandler(wxCommandEvent &event) {
11080 // Pass menu events from the canvas to the menu handler
11081 // This is necessarily in ChartCanvas since that is the menu's parent.
11082 if (m_canvasMenu) {
11083 m_canvasMenu->PopupMenuHandler(event);
11084 }
11085 return;
11086}
11087
11088void ChartCanvas::StartRoute() {
11089 // Do not allow more than one canvas to create a route at one time.
11090 if (g_brouteCreating) return;
11091
11092 if (g_MainToolbar) g_MainToolbar->DisableTooltips();
11093
11094 g_brouteCreating = true;
11095 m_routeState = 1;
11096 m_bDrawingRoute = false;
11097 SetCursor(*pCursorPencil);
11098 // SetCanvasToolbarItemState(ID_ROUTE, true);
11099 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, true);
11100
11101 HideGlobalToolbar();
11102
11103#ifdef __ANDROID__
11104 androidSetRouteAnnunciator(true);
11105#endif
11106}
11107
11108wxString ChartCanvas::FinishRoute() {
11109 m_routeState = 0;
11110 m_prev_pMousePoint = NULL;
11111 m_bDrawingRoute = false;
11112 wxString rv = "";
11113 if (m_pMouseRoute) rv = m_pMouseRoute->m_GUID;
11114
11115 // SetCanvasToolbarItemState(ID_ROUTE, false);
11116 gFrame->SetMasterToolbarItemState(ID_MENU_ROUTE_NEW, false);
11117#ifdef __ANDROID__
11118 androidSetRouteAnnunciator(false);
11119#endif
11120
11121 SetCursor(*pCursorArrow);
11122
11123 if (m_pMouseRoute) {
11124 if (m_bAppendingRoute) {
11125 // pConfig->UpdateRoute(m_pMouseRoute);
11126 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11127 } else {
11128 if (m_pMouseRoute->GetnPoints() > 1) {
11129 // pConfig->AddNewRoute(m_pMouseRoute);
11130 NavObj_dB::GetInstance().UpdateRoute(m_pMouseRoute);
11131 } else {
11132 g_pRouteMan->DeleteRoute(m_pMouseRoute);
11133 m_pMouseRoute = NULL;
11134 }
11135 }
11136 if (m_pMouseRoute) m_pMouseRoute->SetHiLite(0);
11137
11138 if (RoutePropDlgImpl::getInstanceFlag() && pRoutePropDialog &&
11139 (pRoutePropDialog->IsShown())) {
11140 pRoutePropDialog->SetRouteAndUpdate(m_pMouseRoute, true);
11141 }
11142
11143 if (RouteManagerDialog::getInstanceFlag() && pRouteManagerDialog) {
11144 if (pRouteManagerDialog && pRouteManagerDialog->IsShown())
11145 pRouteManagerDialog->UpdateRouteListCtrl();
11146 }
11147 }
11148 m_bAppendingRoute = false;
11149 m_pMouseRoute = NULL;
11150
11151 m_pSelectedRoute = NULL;
11152
11153 undo->InvalidateUndo();
11154 gFrame->RefreshAllCanvas(true);
11155
11156 if (g_MainToolbar) g_MainToolbar->EnableTooltips();
11157
11158 ShowGlobalToolbar();
11159
11160 g_brouteCreating = false;
11161
11162 return rv;
11163}
11164
11165void ChartCanvas::HideGlobalToolbar() {
11166 if (m_canvasIndex == 0) {
11167 m_last_TBviz = gFrame->SetGlobalToolbarViz(false);
11168 }
11169}
11170
11171void ChartCanvas::ShowGlobalToolbar() {
11172 if (m_canvasIndex == 0) {
11173 if (m_last_TBviz) gFrame->SetGlobalToolbarViz(true);
11174 }
11175}
11176
11177void ChartCanvas::ShowAISTargetList() {
11178 if (NULL == g_pAISTargetList) { // There is one global instance of the Dialog
11179 g_pAISTargetList = new AISTargetListDialog(parent_frame, g_pauimgr, g_pAIS);
11180 }
11181
11182 g_pAISTargetList->UpdateAISTargetList();
11183}
11184
11185void ChartCanvas::RenderAllChartOutlines(ocpnDC &dc, ViewPort &vp) {
11186 if (!m_bShowOutlines) return;
11187
11188 if (!ChartData) return;
11189
11190 int nEntry = ChartData->GetChartTableEntries();
11191
11192 for (int i = 0; i < nEntry; i++) {
11193 ChartTableEntry *pt = (ChartTableEntry *)&ChartData->GetChartTableEntry(i);
11194
11195 // Check to see if the candidate chart is in the currently active group
11196 bool b_group_draw = false;
11197 if (m_groupIndex > 0) {
11198 for (unsigned int ig = 0; ig < pt->GetGroupArray().size(); ig++) {
11199 int index = pt->GetGroupArray()[ig];
11200 if (m_groupIndex == index) {
11201 b_group_draw = true;
11202 break;
11203 }
11204 }
11205 } else
11206 b_group_draw = true;
11207
11208 if (b_group_draw) RenderChartOutline(dc, i, vp);
11209 }
11210
11211 // On CM93 Composite Charts, draw the outlines of the next smaller
11212 // scale cell
11213 cm93compchart *pcm93 = NULL;
11214 if (VPoint.b_quilt) {
11215 for (ChartBase *pch = GetFirstQuiltChart(); pch; pch = GetNextQuiltChart())
11216 if (pch->GetChartType() == CHART_TYPE_CM93COMP) {
11217 pcm93 = (cm93compchart *)pch;
11218 break;
11219 }
11220 } else if (m_singleChart &&
11221 (m_singleChart->GetChartType() == CHART_TYPE_CM93COMP))
11222 pcm93 = (cm93compchart *)m_singleChart;
11223
11224 if (pcm93) {
11225 double chart_native_ppm = m_canvas_scale_factor / pcm93->GetNativeScale();
11226 double zoom_factor = GetVP().view_scale_ppm / chart_native_ppm;
11227
11228 if (zoom_factor > 8.0) {
11229 wxPen mPen(GetGlobalColor("UINFM"), 2, wxPENSTYLE_SHORT_DASH);
11230 dc.SetPen(mPen);
11231 } else {
11232 wxPen mPen(GetGlobalColor("UINFM"), 1, wxPENSTYLE_SOLID);
11233 dc.SetPen(mPen);
11234 }
11235
11236 pcm93->RenderNextSmallerCellOutlines(dc, vp, this);
11237 }
11238}
11239
11240void ChartCanvas::RenderChartOutline(ocpnDC &dc, int dbIndex, ViewPort &vp) {
11241#ifdef ocpnUSE_GL
11242 if (g_bopengl && m_glcc) {
11243 /* opengl version specially optimized */
11244 m_glcc->RenderChartOutline(dc, dbIndex, vp);
11245 return;
11246 }
11247#endif
11248
11249 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_PLUGIN) {
11250 if (!ChartData->IsChartAvailable(dbIndex)) return;
11251 }
11252
11253 float plylat, plylon;
11254 float plylat1, plylon1;
11255
11256 int pixx, pixy, pixx1, pixy1;
11257
11258 LLBBox box;
11259 ChartData->GetDBBoundingBox(dbIndex, box);
11260
11261 // Don't draw an outline in the case where the chart covers the entire world
11262 // */
11263 if (box.GetLonRange() == 360) return;
11264
11265 double lon_bias = 0;
11266 // chart is outside of viewport lat/lon bounding box
11267 if (box.IntersectOutGetBias(vp.GetBBox(), lon_bias)) return;
11268
11269 int nPly = ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11270
11271 if (ChartData->GetDBChartType(dbIndex) == CHART_TYPE_CM93)
11272 dc.SetPen(wxPen(GetGlobalColor("YELO1"), 1, wxPENSTYLE_SOLID));
11273
11274 else if (ChartData->GetDBChartFamily(dbIndex) == CHART_FAMILY_VECTOR)
11275 dc.SetPen(wxPen(GetGlobalColor("UINFG"), 1, wxPENSTYLE_SOLID));
11276
11277 else
11278 dc.SetPen(wxPen(GetGlobalColor("UINFR"), 1, wxPENSTYLE_SOLID));
11279
11280 // Are there any aux ply entries?
11281 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11282 if (0 == nAuxPlyEntries) // There are no aux Ply Point entries
11283 {
11284 wxPoint r, r1;
11285
11286 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat, &plylon);
11287 plylon += lon_bias;
11288
11289 GetCanvasPointPix(plylat, plylon, &r);
11290 pixx = r.x;
11291 pixy = r.y;
11292
11293 for (int i = 0; i < nPly - 1; i++) {
11294 ChartData->GetDBPlyPoint(dbIndex, i + 1, &plylat1, &plylon1);
11295 plylon1 += lon_bias;
11296
11297 GetCanvasPointPix(plylat1, plylon1, &r1);
11298 pixx1 = r1.x;
11299 pixy1 = r1.y;
11300
11301 int pixxs1 = pixx1;
11302 int pixys1 = pixy1;
11303
11304 bool b_skip = false;
11305
11306 if (vp.chart_scale > 5e7) {
11307 // calculate projected distance between these two points in meters
11308 double dist = sqrt(pow((double)(pixx1 - pixx), 2) +
11309 pow((double)(pixy1 - pixy), 2)) /
11310 vp.view_scale_ppm;
11311
11312 if (dist > 0.0) {
11313 // calculate GC distance between these two points in meters
11314 double distgc =
11315 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11316
11317 // If the distances are nonsense, it means that the scale is very
11318 // small and the segment wrapped the world So skip it....
11319 // TODO improve this to draw two segments
11320 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11321 b_skip = true;
11322 } else
11323 b_skip = true;
11324 }
11325
11326 ClipResult res = cohen_sutherland_line_clip_i(
11327 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11328 if (res != Invisible && !b_skip)
11329 dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11330
11331 plylat = plylat1;
11332 plylon = plylon1;
11333 pixx = pixxs1;
11334 pixy = pixys1;
11335 }
11336
11337 ChartData->GetDBPlyPoint(dbIndex, 0, &plylat1, &plylon1);
11338 plylon1 += lon_bias;
11339
11340 GetCanvasPointPix(plylat1, plylon1, &r1);
11341 pixx1 = r1.x;
11342 pixy1 = r1.y;
11343
11344 ClipResult res = cohen_sutherland_line_clip_i(
11345 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11346 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11347 }
11348
11349 else // Use Aux PlyPoints
11350 {
11351 wxPoint r, r1;
11352
11353 int nAuxPlyEntries = ChartData->GetnAuxPlyEntries(dbIndex);
11354 for (int j = 0; j < nAuxPlyEntries; j++) {
11355 int nAuxPly =
11356 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat, &plylon);
11357 GetCanvasPointPix(plylat, plylon, &r);
11358 pixx = r.x;
11359 pixy = r.y;
11360
11361 for (int i = 0; i < nAuxPly - 1; i++) {
11362 ChartData->GetDBAuxPlyPoint(dbIndex, i + 1, j, &plylat1, &plylon1);
11363
11364 GetCanvasPointPix(plylat1, plylon1, &r1);
11365 pixx1 = r1.x;
11366 pixy1 = r1.y;
11367
11368 int pixxs1 = pixx1;
11369 int pixys1 = pixy1;
11370
11371 bool b_skip = false;
11372
11373 if (vp.chart_scale > 5e7) {
11374 // calculate projected distance between these two points in meters
11375 double dist = sqrt((double)((pixx1 - pixx) * (pixx1 - pixx)) +
11376 ((pixy1 - pixy) * (pixy1 - pixy))) /
11377 vp.view_scale_ppm;
11378 if (dist > 0.0) {
11379 // calculate GC distance between these two points in meters
11380 double distgc =
11381 DistGreatCircle(plylat, plylon, plylat1, plylon1) * 1852.;
11382
11383 // If the distances are nonsense, it means that the scale is very
11384 // small and the segment wrapped the world So skip it....
11385 // TODO improve this to draw two segments
11386 if (fabs(dist - distgc) > 10000. * 1852.) // lotsa miles
11387 b_skip = true;
11388 } else
11389 b_skip = true;
11390 }
11391
11392 ClipResult res = cohen_sutherland_line_clip_i(
11393 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11394 if (res != Invisible && !b_skip) dc.DrawLine(pixx, pixy, pixx1, pixy1);
11395
11396 plylat = plylat1;
11397 plylon = plylon1;
11398 pixx = pixxs1;
11399 pixy = pixys1;
11400 }
11401
11402 ChartData->GetDBAuxPlyPoint(dbIndex, 0, j, &plylat1, &plylon1);
11403 GetCanvasPointPix(plylat1, plylon1, &r1);
11404 pixx1 = r1.x;
11405 pixy1 = r1.y;
11406
11407 ClipResult res = cohen_sutherland_line_clip_i(
11408 &pixx, &pixy, &pixx1, &pixy1, 0, vp.pix_width, 0, vp.pix_height);
11409 if (res != Invisible) dc.DrawLine(pixx, pixy, pixx1, pixy1, false);
11410 }
11411 }
11412}
11413
11414static void RouteLegInfo(ocpnDC &dc, wxPoint ref_point,
11415 const wxArrayString &legend) {
11416 wxFont *dFont = FontMgr::Get().GetFont(_("RouteLegInfoRollover"));
11417
11418 int pointsize = dFont->GetPointSize();
11419 pointsize /= OCPN_GetWinDIPScaleFactor();
11420
11421 wxFont *psRLI_font = FontMgr::Get().FindOrCreateFont(
11422 pointsize, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
11423 false, dFont->GetFaceName());
11424
11425 dc.SetFont(*psRLI_font);
11426
11427 int h = 0;
11428 int w = 0;
11429 int hl, wl;
11430
11431 int xp, yp;
11432 int hilite_offset = 3;
11433
11434 for (wxString line : legend) {
11435#ifdef __WXMAC__
11436 wxScreenDC sdc;
11437 sdc.GetTextExtent(line, &wl, &hl, NULL, NULL, psRLI_font);
11438#else
11439 dc.GetTextExtent(line, &wl, &hl);
11440 hl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11441 wl *= (OCPN_GetWinDIPScaleFactor() * 100.) / 100;
11442#endif
11443 h += hl;
11444 w = wxMax(w, wl);
11445 }
11446 w += (hl / 2); // Add a little right pad
11447
11448 xp = ref_point.x - w;
11449 yp = ref_point.y;
11450 yp += hilite_offset;
11451
11452 AlphaBlending(dc, xp, yp, w, h, 0.0, GetGlobalColor("YELO1"), 172);
11453
11454 dc.SetPen(wxPen(GetGlobalColor("UBLCK")));
11455 dc.SetTextForeground(GetGlobalColor("UBLCK"));
11456
11457 for (wxString line : legend) {
11458 dc.DrawText(line, xp, yp);
11459 yp += hl;
11460 }
11461}
11462
11463void ChartCanvas::RenderShipToActive(ocpnDC &dc, bool Use_Opengl) {
11464 if (!g_bAllowShipToActive) return;
11465
11466 Route *rt = g_pRouteMan->GetpActiveRoute();
11467 if (!rt) return;
11468
11469 if (RoutePoint *rp = g_pRouteMan->GetpActivePoint()) {
11470 wxPoint2DDouble pa, pb;
11472 GetDoubleCanvasPointPix(rp->m_lat, rp->m_lon, &pb);
11473
11474 // set pen
11475 int width =
11476 g_pRouteMan->GetRoutePen()->GetWidth(); // get default route pen with
11477 if (rt->m_width != wxPENSTYLE_INVALID)
11478 width = rt->m_width; // set route pen style if any
11479 wxPenStyle style = (wxPenStyle)::StyleValues[wxMin(
11480 g_shipToActiveStyle, 5)]; // get setting pen style
11481 if (style == wxPENSTYLE_INVALID) style = wxPENSTYLE_SOLID; // default style
11482 wxColour color =
11483 g_shipToActiveColor > 0 ? GpxxColors[wxMin(g_shipToActiveColor - 1, 15)]
11484 : // set setting route pen color
11485 g_pRouteMan->GetActiveRoutePen()->GetColour(); // default color
11486 wxPen *mypen = wxThePenList->FindOrCreatePen(color, width, style);
11487
11488 dc.SetPen(*mypen);
11489 dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
11490
11491 if (!Use_Opengl)
11492 RouteGui(*rt).RenderSegment(dc, (int)pa.m_x, (int)pa.m_y, (int)pb.m_x,
11493 (int)pb.m_y, GetVP(), true);
11494
11495#ifdef ocpnUSE_GL
11496 else {
11497#ifdef USE_ANDROID_GLES2
11498 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11499#else
11500 if (style != wxPENSTYLE_SOLID) {
11501 if (glChartCanvas::dash_map.find(style) !=
11502 glChartCanvas::dash_map.end()) {
11503 mypen->SetDashes(2, &glChartCanvas::dash_map[style][0]);
11504 dc.SetPen(*mypen);
11505 }
11506 }
11507 dc.DrawLine(pa.m_x, pa.m_y, pb.m_x, pb.m_y);
11508#endif
11509
11510 RouteGui(*rt).RenderSegmentArrowsGL(dc, (int)pa.m_x, (int)pa.m_y,
11511 (int)pb.m_x, (int)pb.m_y, GetVP());
11512 }
11513#endif
11514 }
11515}
11516
11517void ChartCanvas::RenderRouteLegs(ocpnDC &dc) {
11518 Route *route = 0;
11519 if (m_routeState >= 2) route = m_pMouseRoute;
11520 if (m_pMeasureRoute && m_bMeasure_Active && (m_nMeasureState >= 2))
11521 route = m_pMeasureRoute;
11522
11523 if (!route) return;
11524
11525 // Validate route pointer
11526 if (!g_pRouteMan->IsRouteValid(route)) return;
11527
11528 double render_lat = m_cursor_lat;
11529 double render_lon = m_cursor_lon;
11530
11531 int np = route->GetnPoints();
11532 if (np) {
11533 if (g_btouch && (np > 1)) np--;
11534 RoutePoint rp = route->GetPoint(np);
11535 render_lat = rp.m_lat;
11536 render_lon = rp.m_lon;
11537 }
11538
11539 double rhumbBearing, rhumbDist;
11540 DistanceBearingMercator(m_cursor_lat, m_cursor_lon, render_lat, render_lon,
11541 &rhumbBearing, &rhumbDist);
11542 double brg = rhumbBearing;
11543 double dist = rhumbDist;
11544
11545 // Skip GreatCircle rubberbanding on touch devices.
11546 if (!g_btouch) {
11547 double gcBearing, gcBearing2, gcDist;
11548 Geodesic::GreatCircleDistBear(render_lon, render_lat, m_cursor_lon,
11549 m_cursor_lat, &gcDist, &gcBearing,
11550 &gcBearing2);
11551 double gcDistm = gcDist / 1852.0;
11552
11553 if ((render_lat == m_cursor_lat) && (render_lon == m_cursor_lon))
11554 rhumbBearing = 90.;
11555
11556 wxPoint destPoint, lastPoint;
11557
11558 route->m_NextLegGreatCircle = false;
11559 int milesDiff = rhumbDist - gcDistm;
11560 if (milesDiff > 1) {
11561 brg = gcBearing;
11562 dist = gcDistm;
11563 route->m_NextLegGreatCircle = true;
11564 }
11565
11566 // FIXME (MacOS, the first segment is rendered wrong)
11567 RouteGui(*route).DrawPointWhich(dc, this, route->m_lastMousePointIndex,
11568 &lastPoint);
11569
11570 if (route->m_NextLegGreatCircle) {
11571 for (int i = 1; i <= milesDiff; i++) {
11572 double p = (double)i * (1.0 / (double)milesDiff);
11573 double pLat, pLon;
11574 Geodesic::GreatCircleTravel(render_lon, render_lat, gcDist * p, brg,
11575 &pLon, &pLat, &gcBearing2);
11576 destPoint = VPoint.GetPixFromLL(pLat, pLon);
11577 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &destPoint, GetVP(),
11578 false);
11579 lastPoint = destPoint;
11580 }
11581 } else {
11582 if (r_rband.x && r_rband.y) { // RubberBand disabled?
11583 RouteGui(*route).DrawSegment(dc, this, &lastPoint, &r_rband, GetVP(),
11584 false);
11585 if (m_bMeasure_DistCircle) {
11586 double distanceRad = sqrtf(powf((float)(r_rband.x - lastPoint.x), 2) +
11587 powf((float)(r_rband.y - lastPoint.y), 2));
11588
11589 dc.SetPen(*g_pRouteMan->GetRoutePen());
11590 dc.SetBrush(*wxTRANSPARENT_BRUSH);
11591 dc.StrokeCircle(lastPoint.x, lastPoint.y, distanceRad);
11592 }
11593 }
11594 }
11595 }
11596
11597 wxString routeInfo;
11598 wxArrayString infoArray;
11599 double varBrg = 0;
11600 if (g_bShowTrue)
11601 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8), (int)brg,
11602 0x00B0);
11603
11604 if (g_bShowMag) {
11605 double latAverage = (m_cursor_lat + render_lat) / 2;
11606 double lonAverage = (m_cursor_lon + render_lon) / 2;
11607 varBrg = gFrame->GetMag(brg, latAverage, lonAverage);
11608
11609 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11610 (int)varBrg, 0x00B0);
11611 }
11612 routeInfo << " " << FormatDistanceAdaptive(dist);
11613 infoArray.Add(routeInfo);
11614 routeInfo.Clear();
11615
11616 // To make it easier to use a route as a bearing on a charted object add for
11617 // the first leg also the reverse bearing.
11618 if (np == 1) {
11619 routeInfo << "Reverse: ";
11620 if (g_bShowTrue)
11621 routeInfo << wxString::Format(wxString("%03d%c(T) ", wxConvUTF8),
11622 (int)(brg + 180.) % 360, 0x00B0);
11623 if (g_bShowMag)
11624 routeInfo << wxString::Format(wxString("%03d%c(M) ", wxConvUTF8),
11625 (int)(varBrg + 180.) % 360, 0x00B0);
11626 infoArray.Add(routeInfo);
11627 routeInfo.Clear();
11628 }
11629
11630 wxString s0;
11631 if (!route->m_bIsInLayer)
11632 s0.Append(_("Route") + ": ");
11633 else
11634 s0.Append(_("Layer Route: "));
11635
11636 double disp_length = route->m_route_length;
11637 if (!g_btouch) disp_length += dist; // Add in the to-be-created leg.
11638 s0 += FormatDistanceAdaptive(disp_length);
11639
11640 infoArray.Add(s0);
11641 routeInfo.Clear();
11642
11643 RouteLegInfo(dc, r_rband, infoArray);
11644
11645 m_brepaint_piano = true;
11646}
11647
11648void ChartCanvas::RenderVisibleSectorLights(ocpnDC &dc) {
11649 if (!m_bShowVisibleSectors) return;
11650
11651 if (g_bDeferredInitDone) {
11652 // need to re-evaluate sectors?
11653 double rhumbBearing, rhumbDist;
11654 DistanceBearingMercator(gLat, gLon, m_sector_glat, m_sector_glon,
11655 &rhumbBearing, &rhumbDist);
11656
11657 if (rhumbDist > 0.05) // miles
11658 {
11659 s57_GetVisibleLightSectors(this, gLat, gLon, GetVP(),
11660 m_sectorlegsVisible);
11661 m_sector_glat = gLat;
11662 m_sector_glon = gLon;
11663 }
11664 s57_DrawExtendedLightSectors(dc, VPoint, m_sectorlegsVisible);
11665 }
11666}
11667
11668void ChartCanvas::WarpPointerDeferred(int x, int y) {
11669 warp_x = x;
11670 warp_y = y;
11671 warp_flag = true;
11672}
11673
11674int s_msg;
11675
11676void ChartCanvas::UpdateCanvasS52PLIBConfig() {
11677 if (!ps52plib) return;
11678
11679 if (VPoint.b_quilt) { // quilted
11680 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11681
11682 if (m_pQuilt->IsQuiltVector()) {
11683 if (ps52plib->GetStateHash() != m_s52StateHash) {
11684 UpdateS52State();
11685 m_s52StateHash = ps52plib->GetStateHash();
11686 }
11687 }
11688 } else {
11689 if (ps52plib->GetStateHash() != m_s52StateHash) {
11690 UpdateS52State();
11691 m_s52StateHash = ps52plib->GetStateHash();
11692 }
11693 }
11694
11695 // Plugin charts
11696 bool bSendPlibState = true;
11697 if (VPoint.b_quilt) { // quilted
11698 if (!m_pQuilt->DoesQuiltContainPlugins()) bSendPlibState = false;
11699 }
11700
11701 if (bSendPlibState) {
11702 wxJSONValue v;
11703 v["OpenCPN Version Major"] = VERSION_MAJOR;
11704 v["OpenCPN Version Minor"] = VERSION_MINOR;
11705 v["OpenCPN Version Patch"] = VERSION_PATCH;
11706 v["OpenCPN Version Date"] = VERSION_DATE;
11707 v["OpenCPN Version Full"] = VERSION_FULL;
11708
11709 // S52PLIB state
11710 v["OpenCPN S52PLIB ShowText"] = GetShowENCText();
11711 v["OpenCPN S52PLIB ShowSoundings"] = GetShowENCDepth();
11712 v["OpenCPN S52PLIB ShowLights"] = GetShowENCLights();
11713 v["OpenCPN S52PLIB ShowAnchorConditions"] = m_encShowAnchor;
11714 v["OpenCPN S52PLIB ShowQualityOfData"] = GetShowENCDataQual();
11715 v["OpenCPN S52PLIB ShowATONLabel"] = GetShowENCBuoyLabels();
11716 v["OpenCPN S52PLIB ShowLightDescription"] = GetShowENCLightDesc();
11717
11718 v["OpenCPN S52PLIB DisplayCategory"] = GetENCDisplayCategory();
11719
11720 v["OpenCPN S52PLIB SoundingsFactor"] = g_ENCSoundingScaleFactor;
11721 v["OpenCPN S52PLIB TextFactor"] = g_ENCTextScaleFactor;
11722
11723 // Global S52 options
11724
11725 v["OpenCPN S52PLIB MetaDisplay"] = ps52plib->m_bShowMeta;
11726 v["OpenCPN S52PLIB DeclutterText"] = ps52plib->m_bDeClutterText;
11727 v["OpenCPN S52PLIB ShowNationalText"] = ps52plib->m_bShowNationalTexts;
11728 v["OpenCPN S52PLIB ShowImportantTextOnly"] =
11729 ps52plib->m_bShowS57ImportantTextOnly;
11730 v["OpenCPN S52PLIB UseSCAMIN"] = ps52plib->m_bUseSCAMIN;
11731 v["OpenCPN S52PLIB UseSUPER_SCAMIN"] = ps52plib->m_bUseSUPER_SCAMIN;
11732 v["OpenCPN S52PLIB SymbolStyle"] = ps52plib->m_nSymbolStyle;
11733 v["OpenCPN S52PLIB BoundaryStyle"] = ps52plib->m_nBoundaryStyle;
11734 v["OpenCPN S52PLIB ColorShades"] = S52_getMarinerParam(S52_MAR_TWO_SHADES);
11735
11736 // Some global GUI parameters, for completeness
11737 v["OpenCPN Zoom Mod Vector"] = g_chart_zoom_modifier_vector;
11738 v["OpenCPN Zoom Mod Other"] = g_chart_zoom_modifier_raster;
11739 v["OpenCPN Scale Factor Exp"] =
11740 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
11741 v["OpenCPN Display Width"] = (int)g_display_size_mm;
11742
11743 wxJSONWriter w;
11744 wxString out;
11745 w.Write(v, out);
11746
11747 if (!g_lastS52PLIBPluginMessage.IsSameAs(out)) {
11748 SendMessageToAllPlugins(wxString("OpenCPN Config"), out);
11749 g_lastS52PLIBPluginMessage = out;
11750 }
11751 }
11752}
11753int spaint;
11754int s_in_update;
11755void ChartCanvas::OnPaint(wxPaintEvent &event) {
11756 wxPaintDC dc(this);
11757
11758 // GetToolbar()->Show( m_bToolbarEnable );
11759
11760 // Paint updates may have been externally disabled (temporarily, to avoid
11761 // Yield() recursion performance loss) It is important that the wxPaintDC is
11762 // built, even if we elect to not process this paint message. Otherwise, the
11763 // paint message may not be removed from the message queue, esp on Windows.
11764 // (FS#1213) This would lead to a deadlock condition in ::wxYield()
11765
11766 if (!m_b_paint_enable) {
11767 return;
11768 }
11769
11770 // If necessary, reconfigure the S52 PLIB
11772
11773#ifdef ocpnUSE_GL
11774 if (!g_bdisable_opengl && m_glcc) m_glcc->Show(g_bopengl);
11775
11776 if (m_glcc && g_bopengl) {
11777 if (!s_in_update) { // no recursion allowed, seen on lo-spec Mac
11778 s_in_update++;
11779 m_glcc->Update();
11780 s_in_update--;
11781 }
11782
11783 return;
11784 }
11785#endif
11786
11787 if ((GetVP().pix_width == 0) || (GetVP().pix_height == 0)) return;
11788
11789 wxRegion ru = GetUpdateRegion();
11790
11791 int rx, ry, rwidth, rheight;
11792 ru.GetBox(rx, ry, rwidth, rheight);
11793
11794#ifdef ocpnUSE_DIBSECTION
11795 ocpnMemDC temp_dc;
11796#else
11797 wxMemoryDC temp_dc;
11798#endif
11799
11800 long height = GetVP().pix_height;
11801
11802#ifdef __WXMAC__
11803 // On OS X we have to explicitly extend the region for the piano area
11804 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11805 if (!style->chartStatusWindowTransparent && g_bShowChartBar)
11806 height += m_Piano->GetHeight();
11807#endif // __WXMAC__
11808 wxRegion rgn_chart(0, 0, GetVP().pix_width, height);
11809
11810 // In case Thumbnail is shown, set up dc clipper and blt iterator regions
11811 if (pthumbwin) {
11812 int thumbx, thumby, thumbsx, thumbsy;
11813 pthumbwin->GetPosition(&thumbx, &thumby);
11814 pthumbwin->GetSize(&thumbsx, &thumbsy);
11815 wxRegion rgn_thumbwin(thumbx, thumby, thumbsx - 1, thumbsy - 1);
11816
11817 if (pthumbwin->IsShown()) {
11818 rgn_chart.Subtract(rgn_thumbwin);
11819 ru.Subtract(rgn_thumbwin);
11820 }
11821 }
11822
11823 // subtract the chart bar if it isn't transparent, and determine if we need to
11824 // paint it
11825 wxRegion rgn_blit = ru;
11826 if (g_bShowChartBar) {
11827 wxRect chart_bar_rect(0, GetClientSize().y - m_Piano->GetHeight(),
11828 GetClientSize().x, m_Piano->GetHeight());
11829
11830 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
11831 if (ru.Contains(chart_bar_rect) != wxOutRegion) {
11832 if (style->chartStatusWindowTransparent)
11833 m_brepaint_piano = true;
11834 else
11835 ru.Subtract(chart_bar_rect);
11836 }
11837 }
11838
11839 if (m_Compass && m_Compass->IsShown()) {
11840 wxRect compassRect = m_Compass->GetRect();
11841 if (ru.Contains(compassRect) != wxOutRegion) {
11842 ru.Subtract(compassRect);
11843 }
11844 }
11845
11846 if (m_notification_button) {
11847 wxRect noteRect = m_notification_button->GetRect();
11848 if (ru.Contains(noteRect) != wxOutRegion) {
11849 ru.Subtract(noteRect);
11850 }
11851 }
11852
11853 // Is this viewpoint the same as the previously painted one?
11854 bool b_newview = true;
11855
11856 if ((m_cache_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
11857 (m_cache_vp.rotation == VPoint.rotation) &&
11858 (m_cache_vp.clat == VPoint.clat) && (m_cache_vp.clon == VPoint.clon) &&
11859 m_cache_vp.IsValid()) {
11860 b_newview = false;
11861 }
11862
11863 // If the ViewPort is skewed or rotated, we may be able to use the cached
11864 // rotated bitmap.
11865 bool b_rcache_ok = false;
11866 if (fabs(VPoint.skew) > 0.01 || fabs(VPoint.rotation) > 0.01)
11867 b_rcache_ok = !b_newview;
11868
11869 // Make a special VP
11870 if (VPoint.b_MercatorProjectionOverride)
11871 VPoint.SetProjectionType(PROJECTION_MERCATOR);
11872 ViewPort svp = VPoint;
11873
11874 svp.pix_width = svp.rv_rect.width;
11875 svp.pix_height = svp.rv_rect.height;
11876
11877 // printf("Onpaint pix %d %d\n", VPoint.pix_width, VPoint.pix_height);
11878 // printf("OnPaint rv_rect %d %d\n", VPoint.rv_rect.width,
11879 // VPoint.rv_rect.height);
11880
11881 OCPNRegion chart_get_region(wxRect(0, 0, svp.pix_width, svp.pix_height));
11882
11883 // If we are going to use the cached rotated image, there is no need to fetch
11884 // any chart data and this will do it...
11885 if (b_rcache_ok) chart_get_region.Clear();
11886
11887 // Blit pan acceleration
11888 if (VPoint.b_quilt) // quilted
11889 {
11890 if (!m_pQuilt || !m_pQuilt->IsComposed()) return; // not ready
11891
11892 bool bvectorQuilt = m_pQuilt->IsQuiltVector();
11893
11894 bool busy = false;
11895 if (bvectorQuilt && (m_cache_vp.view_scale_ppm != VPoint.view_scale_ppm ||
11896 m_cache_vp.rotation != VPoint.rotation)) {
11897 AbstractPlatform::ShowBusySpinner();
11898 busy = true;
11899 }
11900
11901 if ((m_working_bm.GetWidth() != svp.pix_width) ||
11902 (m_working_bm.GetHeight() != svp.pix_height))
11903 m_working_bm.Create(svp.pix_width, svp.pix_height,
11904 -1); // make sure the target is big enoug
11905
11906 if (fabs(VPoint.rotation) < 0.01) {
11907 bool b_save = true;
11908
11909 if (g_SencThreadManager) {
11910 if (g_SencThreadManager->GetJobCount()) {
11911 b_save = false;
11912 m_cache_vp.Invalidate();
11913 }
11914 }
11915
11916 // If the saved wxBitmap from last OnPaint is useable
11917 // calculate the blit parameters
11918
11919 // We can only do screen blit painting if subsequent ViewPorts differ by
11920 // whole pixels So, in small scale bFollow mode, force the full screen
11921 // render. This seems a hack....There may be better logic here.....
11922
11923 // if(m_bFollow)
11924 // b_save = false;
11925
11926 if (m_bm_cache_vp.IsValid() && m_cache_vp.IsValid() /*&& !m_bFollow*/) {
11927 if (b_newview) {
11928 wxPoint c_old = VPoint.GetPixFromLL(VPoint.clat, VPoint.clon);
11929 wxPoint c_new = m_bm_cache_vp.GetPixFromLL(VPoint.clat, VPoint.clon);
11930
11931 int dy = c_new.y - c_old.y;
11932 int dx = c_new.x - c_old.x;
11933
11934 // printf("In OnPaint Trying Blit dx: %d
11935 // dy:%d\n\n", dx, dy);
11936
11937 if (m_pQuilt->IsVPBlittable(VPoint, dx, dy, true)) {
11938 if (dx || dy) {
11939 // Blit the reuseable portion of the cached wxBitmap to a working
11940 // bitmap
11941 temp_dc.SelectObject(m_working_bm);
11942
11943 wxMemoryDC cache_dc;
11944 cache_dc.SelectObject(m_cached_chart_bm);
11945
11946 if (dy > 0) {
11947 if (dx > 0) {
11948 temp_dc.Blit(0, 0, VPoint.pix_width - dx,
11949 VPoint.pix_height - dy, &cache_dc, dx, dy);
11950 } else {
11951 temp_dc.Blit(-dx, 0, VPoint.pix_width + dx,
11952 VPoint.pix_height - dy, &cache_dc, 0, dy);
11953 }
11954
11955 } else {
11956 if (dx > 0) {
11957 temp_dc.Blit(0, -dy, VPoint.pix_width - dx,
11958 VPoint.pix_height + dy, &cache_dc, dx, 0);
11959 } else {
11960 temp_dc.Blit(-dx, -dy, VPoint.pix_width + dx,
11961 VPoint.pix_height + dy, &cache_dc, 0, 0);
11962 }
11963 }
11964
11965 OCPNRegion update_region;
11966 if (dy) {
11967 if (dy > 0)
11968 update_region.Union(
11969 wxRect(0, VPoint.pix_height - dy, VPoint.pix_width, dy));
11970 else
11971 update_region.Union(wxRect(0, 0, VPoint.pix_width, -dy));
11972 }
11973
11974 if (dx) {
11975 if (dx > 0)
11976 update_region.Union(
11977 wxRect(VPoint.pix_width - dx, 0, dx, VPoint.pix_height));
11978 else
11979 update_region.Union(wxRect(0, 0, -dx, VPoint.pix_height));
11980 }
11981
11982 // Render the new region
11983 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11984 update_region);
11985 cache_dc.SelectObject(wxNullBitmap);
11986 } else {
11987 // No sensible (dx, dy) change in the view, so use the cached
11988 // member bitmap
11989 temp_dc.SelectObject(m_cached_chart_bm);
11990 b_save = false;
11991 }
11992 m_pQuilt->ComputeRenderRegion(svp, chart_get_region);
11993
11994 } else // not blitable
11995 {
11996 temp_dc.SelectObject(m_working_bm);
11997 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
11998 chart_get_region);
11999 }
12000 } else {
12001 // No change in the view, so use the cached member bitmap2
12002 temp_dc.SelectObject(m_cached_chart_bm);
12003 b_save = false;
12004 }
12005 } else // cached bitmap is not yet valid
12006 {
12007 temp_dc.SelectObject(m_working_bm);
12008 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12009 chart_get_region);
12010 }
12011
12012 // Save the fully rendered quilt image as a wxBitmap member of this class
12013 if (b_save) {
12014 // if((m_cached_chart_bm.GetWidth() !=
12015 // svp.pix_width) ||
12016 // (m_cached_chart_bm.GetHeight() !=
12017 // svp.pix_height))
12018 // m_cached_chart_bm.Create(svp.pix_width,
12019 // svp.pix_height, -1); // target wxBitmap
12020 // is big enough
12021 wxMemoryDC scratch_dc_0;
12022 scratch_dc_0.SelectObject(m_cached_chart_bm);
12023 scratch_dc_0.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12024
12025 scratch_dc_0.SelectObject(wxNullBitmap);
12026
12027 m_bm_cache_vp =
12028 VPoint; // save the ViewPort associated with the cached wxBitmap
12029 }
12030 }
12031
12032 else // quilted, rotated
12033 {
12034 temp_dc.SelectObject(m_working_bm);
12035 OCPNRegion chart_get_all_region(
12036 wxRect(0, 0, svp.pix_width, svp.pix_height));
12037 m_pQuilt->RenderQuiltRegionViewOnDCNoText(temp_dc, svp,
12038 chart_get_all_region);
12039 }
12040
12041 AbstractPlatform::HideBusySpinner();
12042
12043 }
12044
12045 else // not quilted
12046 {
12047 if (!m_singleChart) {
12048 dc.SetBackground(wxBrush(*wxLIGHT_GREY));
12049 dc.Clear();
12050 return;
12051 }
12052
12053 if (!chart_get_region.IsEmpty()) {
12054 m_singleChart->RenderRegionViewOnDC(temp_dc, svp, chart_get_region);
12055 }
12056 }
12057
12058 if (temp_dc.IsOk()) {
12059 // Arrange to render the World Chart vector data behind the rendered
12060 // current chart so that uncovered canvas areas show at least the world
12061 // chart.
12062 OCPNRegion chartValidRegion;
12063 if (!VPoint.b_quilt) {
12064 // Make a region covering the current chart on the canvas
12065
12066 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12067 m_singleChart->GetValidCanvasRegion(svp, &chartValidRegion);
12068 else {
12069 // The raster calculations in ChartBaseBSB::ComputeSourceRectangle
12070 // require that the viewport passed here have pix_width and pix_height
12071 // set to the actual display, not the virtual (rv_rect) sizes
12072 // (the vector calculations require the virtual sizes in svp)
12073
12074 m_singleChart->GetValidCanvasRegion(VPoint, &chartValidRegion);
12075 chartValidRegion.Offset(-VPoint.rv_rect.x, -VPoint.rv_rect.y);
12076 }
12077 } else
12078 chartValidRegion = m_pQuilt->GetFullQuiltRenderedRegion();
12079
12080 temp_dc.DestroyClippingRegion();
12081
12082 // Copy current chart region
12083 OCPNRegion backgroundRegion(wxRect(0, 0, svp.pix_width, svp.pix_height));
12084
12085 if (chartValidRegion.IsOk()) backgroundRegion.Subtract(chartValidRegion);
12086
12087 if (!backgroundRegion.IsEmpty()) {
12088 // Draw the Background Chart only in the areas NOT covered by the
12089 // current chart view
12090
12091 /* unfortunately wxDC::DrawRectangle and wxDC::Clear do not respect
12092 clipping regions with more than 1 rectangle so... */
12093 wxColour water = pWorldBackgroundChart->water;
12094 if (water.IsOk()) {
12095 temp_dc.SetPen(*wxTRANSPARENT_PEN);
12096 temp_dc.SetBrush(wxBrush(water));
12097 OCPNRegionIterator upd(backgroundRegion); // get the update rect list
12098 while (upd.HaveRects()) {
12099 wxRect rect = upd.GetRect();
12100 temp_dc.DrawRectangle(rect);
12101 upd.NextRect();
12102 }
12103 }
12104 // Associate with temp_dc
12105 wxRegion *clip_region = backgroundRegion.GetNew_wxRegion();
12106 temp_dc.SetDeviceClippingRegion(*clip_region);
12107 delete clip_region;
12108
12109 ocpnDC bgdc(temp_dc);
12110 double r = VPoint.rotation;
12111 SetVPRotation(VPoint.skew);
12112
12113 // pWorldBackgroundChart->RenderViewOnDC(bgdc, VPoint);
12114 gShapeBasemap.RenderViewOnDC(bgdc, VPoint);
12115
12116 SetVPRotation(r);
12117 }
12118 } // temp_dc.IsOk();
12119
12120 wxMemoryDC *pChartDC = &temp_dc;
12121 wxMemoryDC rotd_dc;
12122
12123 if (((fabs(GetVP().rotation) > 0.01)) || (fabs(GetVP().skew) > 0.01)) {
12124 // Can we use the current rotated image cache?
12125 if (!b_rcache_ok) {
12126#ifdef __WXMSW__
12127 wxMemoryDC tbase_dc;
12128 wxBitmap bm_base(svp.pix_width, svp.pix_height, -1);
12129 tbase_dc.SelectObject(bm_base);
12130 tbase_dc.Blit(0, 0, svp.pix_width, svp.pix_height, &temp_dc, 0, 0);
12131 tbase_dc.SelectObject(wxNullBitmap);
12132#else
12133 const wxBitmap &bm_base = temp_dc.GetSelectedBitmap();
12134#endif
12135
12136 wxImage base_image;
12137 if (bm_base.IsOk()) base_image = bm_base.ConvertToImage();
12138
12139 // Use a local static image rotator to improve wxWidgets code profile
12140 // Especially, on GTK the wxRound and wxRealPoint functions are very
12141 // expensive.....
12142
12143 double angle = GetVP().skew - GetVP().rotation;
12144 wxImage ri;
12145 bool b_rot_ok = false;
12146 if (base_image.IsOk()) {
12147 ViewPort rot_vp = GetVP();
12148
12149 m_b_rot_hidef = false;
12150
12151 ri = Image_Rotate(
12152 base_image, angle,
12153 wxPoint(GetVP().rv_rect.width / 2, GetVP().rv_rect.height / 2),
12154 m_b_rot_hidef, &m_roffset);
12155
12156 if ((rot_vp.view_scale_ppm == VPoint.view_scale_ppm) &&
12157 (rot_vp.rotation == VPoint.rotation) &&
12158 (rot_vp.clat == VPoint.clat) && (rot_vp.clon == VPoint.clon) &&
12159 rot_vp.IsValid() && (ri.IsOk())) {
12160 b_rot_ok = true;
12161 }
12162 }
12163
12164 if (b_rot_ok) {
12165 delete m_prot_bm;
12166 m_prot_bm = new wxBitmap(ri);
12167 }
12168
12169 m_roffset.x += VPoint.rv_rect.x;
12170 m_roffset.y += VPoint.rv_rect.y;
12171 }
12172
12173 if (m_prot_bm && m_prot_bm->IsOk()) {
12174 rotd_dc.SelectObject(*m_prot_bm);
12175 pChartDC = &rotd_dc;
12176 } else {
12177 pChartDC = &temp_dc;
12178 m_roffset = wxPoint(0, 0);
12179 }
12180 } else { // unrotated
12181 pChartDC = &temp_dc;
12182 m_roffset = wxPoint(0, 0);
12183 }
12184
12185 wxPoint offset = m_roffset;
12186
12187 // Save the PixelCache viewpoint for next time
12188 m_cache_vp = VPoint;
12189
12190 // Set up a scratch DC for overlay objects
12191 wxMemoryDC mscratch_dc;
12192 mscratch_dc.SelectObject(*pscratch_bm);
12193
12194 mscratch_dc.ResetBoundingBox();
12195 mscratch_dc.DestroyClippingRegion();
12196 mscratch_dc.SetDeviceClippingRegion(rgn_chart);
12197
12198 // Blit the externally invalidated areas of the chart onto the scratch dc
12199 wxRegionIterator upd(rgn_blit); // get the update rect list
12200 while (upd) {
12201 wxRect rect = upd.GetRect();
12202
12203 mscratch_dc.Blit(rect.x, rect.y, rect.width, rect.height, pChartDC,
12204 rect.x - offset.x, rect.y - offset.y);
12205 upd++;
12206 }
12207
12208 // If multi-canvas, indicate which canvas has keyboard focus
12209 // by drawing a simple blue bar at the top.
12210 if (m_show_focus_bar && (g_canvasConfig != 0)) { // multi-canvas?
12211 if (this == wxWindow::FindFocus()) {
12212 g_focusCanvas = this;
12213
12214 wxColour colour = GetGlobalColor("BLUE4");
12215 mscratch_dc.SetPen(wxPen(colour));
12216 mscratch_dc.SetBrush(wxBrush(colour));
12217
12218 wxRect activeRect(0, 0, GetClientSize().x, m_focus_indicator_pix);
12219 mscratch_dc.DrawRectangle(activeRect);
12220 }
12221 }
12222
12223 // Any MBtiles?
12224 std::vector<int> stackIndexArray = m_pQuilt->GetExtendedStackIndexArray();
12225 unsigned int im = stackIndexArray.size();
12226 if (VPoint.b_quilt && im > 0) {
12227 std::vector<int> tiles_to_show;
12228 for (unsigned int is = 0; is < im; is++) {
12229 const ChartTableEntry &cte =
12230 ChartData->GetChartTableEntry(stackIndexArray[is]);
12231 if (IsTileOverlayIndexInNoShow(stackIndexArray[is])) {
12232 continue;
12233 }
12234 if (cte.GetChartType() == CHART_TYPE_MBTILES) {
12235 tiles_to_show.push_back(stackIndexArray[is]);
12236 }
12237 }
12238
12239 if (tiles_to_show.size())
12240 SetAlertString(_("MBTile requires OpenGL to be enabled"));
12241 }
12242
12243 // May get an unexpected OnPaint call while switching display modes
12244 // Guard for that.
12245 if (!g_bopengl) {
12246 ocpnDC scratch_dc(mscratch_dc);
12247 RenderAlertMessage(mscratch_dc, GetVP());
12248 }
12249
12250#if 0
12251 // quiting?
12252 if (g_bquiting) {
12253#ifdef ocpnUSE_DIBSECTION
12254 ocpnMemDC q_dc;
12255#else
12256 wxMemoryDC q_dc;
12257#endif
12258 wxBitmap qbm(GetVP().pix_width, GetVP().pix_height);
12259 q_dc.SelectObject(qbm);
12260
12261 // Get a copy of the screen
12262 q_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &mscratch_dc, 0, 0);
12263
12264 // Draw a rectangle over the screen with a stipple brush
12265 wxBrush qbr(*wxBLACK, wxBRUSHSTYLE_FDIAGONAL_HATCH);
12266 q_dc.SetBrush(qbr);
12267 q_dc.DrawRectangle(0, 0, GetVP().pix_width, GetVP().pix_height);
12268
12269 // Blit back into source
12270 mscratch_dc.Blit(0, 0, GetVP().pix_width, GetVP().pix_height, &q_dc, 0, 0,
12271 wxCOPY);
12272
12273 q_dc.SelectObject(wxNullBitmap);
12274 }
12275#endif
12276
12277#if 0
12278 // It is possible that this two-step method may be reuired for some platforms.
12279 // So, retain in the code base to aid recovery if necessary
12280
12281 // Create and Render the Vector quilt decluttered text overlay, omitting CM93 composite
12282 if( VPoint.b_quilt ) {
12283 if(m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()){
12284 ChartBase *chart = m_pQuilt->GetRefChart();
12285 if( chart && chart->GetChartType() != CHART_TYPE_CM93COMP){
12286
12287 // Clear the text Global declutter list
12288 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper*>( chart );
12289 if(ChPI)
12290 ChPI->ClearPLIBTextList();
12291 else{
12292 if(ps52plib)
12293 ps52plib->ClearTextList();
12294 }
12295
12296 wxMemoryDC t_dc;
12297 wxBitmap qbm( GetVP().pix_width, GetVP().pix_height );
12298
12299 wxColor maskBackground = wxColour(1,0,0);
12300 t_dc.SelectObject( qbm );
12301 t_dc.SetBackground(wxBrush(maskBackground));
12302 t_dc.Clear();
12303
12304 // Copy the scratch DC into the new bitmap
12305 t_dc.Blit( 0, 0, GetVP().pix_width, GetVP().pix_height, scratch_dc.GetDC(), 0, 0, wxCOPY );
12306
12307 // Render the text to the new bitmap
12308 OCPNRegion chart_all_text_region( wxRect( 0, 0, GetVP().pix_width, GetVP().pix_height ) );
12309 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly( t_dc, svp, chart_all_text_region );
12310
12311 // Copy the new bitmap back to the scratch dc
12312 wxRegionIterator upd_final( ru );
12313 while( upd_final ) {
12314 wxRect rect = upd_final.GetRect();
12315 scratch_dc.GetDC()->Blit( rect.x, rect.y, rect.width, rect.height, &t_dc, rect.x, rect.y, wxCOPY, true );
12316 upd_final++;
12317 }
12318
12319 t_dc.SelectObject( wxNullBitmap );
12320 }
12321 }
12322 }
12323#endif
12324 // Direct rendering model...
12325 if (VPoint.b_quilt) {
12326 if (m_pQuilt->IsQuiltVector() && ps52plib && ps52plib->GetShowS57Text()) {
12327 ChartBase *chart = m_pQuilt->GetRefChart();
12328 if (chart && chart->GetChartType() != CHART_TYPE_CM93COMP) {
12329 // Clear the text Global declutter list
12330 ChartPlugInWrapper *ChPI = dynamic_cast<ChartPlugInWrapper *>(chart);
12331 if (ChPI)
12332 ChPI->ClearPLIBTextList();
12333 else {
12334 if (ps52plib) ps52plib->ClearTextList();
12335 }
12336
12337 // Render the text directly to the scratch bitmap
12338 OCPNRegion chart_all_text_region(
12339 wxRect(0, 0, GetVP().pix_width, GetVP().pix_height));
12340
12341 if (g_bShowChartBar && m_Piano) {
12342 wxRect chart_bar_rect(0, GetVP().pix_height - m_Piano->GetHeight(),
12343 GetVP().pix_width, m_Piano->GetHeight());
12344
12345 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12346 if (!style->chartStatusWindowTransparent)
12347 chart_all_text_region.Subtract(chart_bar_rect);
12348 }
12349
12350 if (m_Compass && m_Compass->IsShown()) {
12351 wxRect compassRect = m_Compass->GetRect();
12352 if (chart_all_text_region.Contains(compassRect) != wxOutRegion) {
12353 chart_all_text_region.Subtract(compassRect);
12354 }
12355 }
12356
12357 mscratch_dc.DestroyClippingRegion();
12358
12359 m_pQuilt->RenderQuiltRegionViewOnDCTextOnly(mscratch_dc, svp,
12360 chart_all_text_region);
12361 }
12362 }
12363 }
12364
12365 // Now that charts are fully rendered, apply the overlay objects as decals.
12366 ocpnDC scratch_dc(mscratch_dc);
12367 DrawOverlayObjects(scratch_dc, ru);
12368
12369 // And finally, blit the scratch dc onto the physical dc
12370 wxRegionIterator upd_final(rgn_blit);
12371 while (upd_final) {
12372 wxRect rect = upd_final.GetRect();
12373 dc.Blit(rect.x, rect.y, rect.width, rect.height, &mscratch_dc, rect.x,
12374 rect.y);
12375 upd_final++;
12376 }
12377
12378 // Deselect the chart bitmap from the temp_dc, so that it will not be
12379 // destroyed in the temp_dc dtor
12380 temp_dc.SelectObject(wxNullBitmap);
12381 // And for the scratch bitmap
12382 mscratch_dc.SelectObject(wxNullBitmap);
12383
12384 dc.DestroyClippingRegion();
12385
12386 PaintCleanup();
12387}
12388
12389void ChartCanvas::PaintCleanup() {
12390 // Handle the current graphic window, if present
12391 if (m_inPinch) return;
12392
12393 if (pCwin) {
12394 pCwin->Show();
12395 if (m_bTCupdate) {
12396 pCwin->Refresh();
12397 pCwin->Update();
12398 }
12399 }
12400
12401 // And set flags for next time
12402 m_bTCupdate = false;
12403
12404 // Handle deferred WarpPointer
12405 if (warp_flag) {
12406 WarpPointer(warp_x, warp_y);
12407 warp_flag = false;
12408 }
12409
12410 // Start movement timers, this runs nearly immediately.
12411 // the reason we cannot simply call it directly is the
12412 // refresh events it emits may be blocked from this paint event
12413 pMovementTimer->Start(1, wxTIMER_ONE_SHOT);
12414 m_VPMovementTimer.Start(1, wxTIMER_ONE_SHOT);
12415}
12416
12417#if 0
12418wxColour GetErrorGraphicColor(double val)
12419{
12420 /*
12421 double valm = wxMin(val_max, val);
12422
12423 unsigned char green = (unsigned char)(255 * (1 - (valm/val_max)));
12424 unsigned char red = (unsigned char)(255 * (valm/val_max));
12425
12426 wxImage::HSVValue hv = wxImage::RGBtoHSV(wxImage::RGBValue(red, green, 0));
12427
12428 hv.saturation = 1.0;
12429 hv.value = 1.0;
12430
12431 wxImage::RGBValue rv = wxImage::HSVtoRGB(hv);
12432 return wxColour(rv.red, rv.green, rv.blue);
12433 */
12434
12435 // HTML colors taken from NOAA WW3 Web representation
12436 wxColour c;
12437 if((val > 0) && (val < 1)) c.Set("#002ad9");
12438 else if((val >= 1) && (val < 2)) c.Set("#006ed9");
12439 else if((val >= 2) && (val < 3)) c.Set("#00b2d9");
12440 else if((val >= 3) && (val < 4)) c.Set("#00d4d4");
12441 else if((val >= 4) && (val < 5)) c.Set("#00d9a6");
12442 else if((val >= 5) && (val < 7)) c.Set("#00d900");
12443 else if((val >= 7) && (val < 9)) c.Set("#95d900");
12444 else if((val >= 9) && (val < 12)) c.Set("#d9d900");
12445 else if((val >= 12) && (val < 15)) c.Set("#d9ae00");
12446 else if((val >= 15) && (val < 18)) c.Set("#d98300");
12447 else if((val >= 18) && (val < 21)) c.Set("#d95700");
12448 else if((val >= 21) && (val < 24)) c.Set("#d90000");
12449 else if((val >= 24) && (val < 27)) c.Set("#ae0000");
12450 else if((val >= 27) && (val < 30)) c.Set("#8c0000");
12451 else if((val >= 30) && (val < 36)) c.Set("#870000");
12452 else if((val >= 36) && (val < 42)) c.Set("#690000");
12453 else if((val >= 42) && (val < 48)) c.Set("#550000");
12454 else if( val >= 48) c.Set("#410000");
12455
12456 return c;
12457}
12458
12459void ChartCanvas::RenderGeorefErrorMap( wxMemoryDC *pmdc, ViewPort *vp)
12460{
12461 wxImage gr_image(vp->pix_width, vp->pix_height);
12462 gr_image.InitAlpha();
12463
12464 double maxval = -10000;
12465 double minval = 10000;
12466
12467 double rlat, rlon;
12468 double glat, glon;
12469
12470 GetCanvasPixPoint(0, 0, rlat, rlon);
12471
12472 for(int i=1; i < vp->pix_height-1; i++)
12473 {
12474 for(int j=0; j < vp->pix_width; j++)
12475 {
12476 // Reference mercator value
12477// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12478
12479 // Georef value
12480 GetCanvasPixPoint(j, i, glat, glon);
12481
12482 maxval = wxMax(maxval, (glat - rlat));
12483 minval = wxMin(minval, (glat - rlat));
12484
12485 }
12486 rlat = glat;
12487 }
12488
12489 GetCanvasPixPoint(0, 0, rlat, rlon);
12490 for(int i=1; i < vp->pix_height-1; i++)
12491 {
12492 for(int j=0; j < vp->pix_width; j++)
12493 {
12494 // Reference mercator value
12495// vp->GetMercatorLLFromPix(wxPoint(j, i), &rlat, &rlon);
12496
12497 // Georef value
12498 GetCanvasPixPoint(j, i, glat, glon);
12499
12500 double f = ((glat - rlat)-minval)/(maxval - minval);
12501
12502 double dy = (f * 40);
12503
12504 wxColour c = GetErrorGraphicColor(dy);
12505 unsigned char r = c.Red();
12506 unsigned char g = c.Green();
12507 unsigned char b = c.Blue();
12508
12509 gr_image.SetRGB(j, i, r,g,b);
12510 if((glat - rlat )!= 0)
12511 gr_image.SetAlpha(j, i, 128);
12512 else
12513 gr_image.SetAlpha(j, i, 255);
12514
12515 }
12516 rlat = glat;
12517 }
12518
12519 // Create a Bitmap
12520 wxBitmap *pbm = new wxBitmap(gr_image);
12521 wxMask *gr_mask = new wxMask(*pbm, wxColour(0,0,0));
12522 pbm->SetMask(gr_mask);
12523
12524 pmdc->DrawBitmap(*pbm, 0,0);
12525
12526 delete pbm;
12527
12528}
12529
12530#endif
12531
12532void ChartCanvas::CancelMouseRoute() {
12533 m_routeState = 0;
12534 m_pMouseRoute = NULL;
12535 m_bDrawingRoute = false;
12536}
12537
12538int ChartCanvas::GetNextContextMenuId() {
12539 return CanvasMenuHandler::GetNextContextMenuId();
12540}
12541
12542bool ChartCanvas::SetCursor(const wxCursor &c) {
12543#ifdef ocpnUSE_GL
12544 if (g_bopengl && m_glcc)
12545 return m_glcc->SetCursor(c);
12546 else
12547#endif
12548 return wxWindow::SetCursor(c);
12549}
12550
12551void ChartCanvas::Refresh(bool eraseBackground, const wxRect *rect) {
12552 if (g_bquiting) return;
12553 // Keep the mouse position members up to date
12554 GetCanvasPixPoint(mouse_x, mouse_y, m_cursor_lat, m_cursor_lon);
12555
12556 // Retrigger the route leg popup timer
12557 // This handles the case when the chart is moving in auto-follow mode,
12558 // but no user mouse input is made. The timer handler may Hide() the
12559 // popup if the chart moved enough n.b. We use slightly longer oneshot
12560 // value to allow this method's Refresh() to complete before potentially
12561 // getting another Refresh() in the popup timer handler.
12562 if (!m_RolloverPopupTimer.IsRunning() &&
12563 ((m_pRouteRolloverWin && m_pRouteRolloverWin->IsActive()) ||
12564 (m_pTrackRolloverWin && m_pTrackRolloverWin->IsActive()) ||
12565 (m_pAISRolloverWin && m_pAISRolloverWin->IsActive())))
12566 m_RolloverPopupTimer.Start(500, wxTIMER_ONE_SHOT);
12567
12568#ifdef ocpnUSE_GL
12569 if (m_glcc && g_bopengl) {
12570 // We need to invalidate the FBO cache to ensure repaint of "grounded"
12571 // overlay objects.
12572 if (eraseBackground && m_glcc->UsingFBO()) m_glcc->Invalidate();
12573
12574 m_glcc->Refresh(eraseBackground,
12575 NULL); // We always are going to render the entire screen
12576 // anyway, so make
12577 // sure that the window managers understand the invalid area
12578 // is actually the entire client area.
12579
12580 // We need to selectively Refresh some child windows, if they are visible.
12581 // Note that some children are refreshed elsewhere on timer ticks, so don't
12582 // need attention here.
12583
12584 // Thumbnail chart
12585 if (pthumbwin && pthumbwin->IsShown()) {
12586 pthumbwin->Raise();
12587 pthumbwin->Refresh(false);
12588 }
12589
12590 // ChartInfo window
12591 if (m_pCIWin && m_pCIWin->IsShown()) {
12592 m_pCIWin->Raise();
12593 m_pCIWin->Refresh(false);
12594 }
12595
12596 // if(g_MainToolbar)
12597 // g_MainToolbar->UpdateRecoveryWindow(g_bshowToolbar);
12598
12599 } else
12600#endif
12601 wxWindow::Refresh(eraseBackground, rect);
12602}
12603
12604void ChartCanvas::Update() {
12605 if (m_glcc && g_bopengl) {
12606#ifdef ocpnUSE_GL
12607 m_glcc->Update();
12608#endif
12609 } else
12610 wxWindow::Update();
12611}
12612
12613void ChartCanvas::DrawEmboss(ocpnDC &dc, emboss_data *pemboss) {
12614 if (!pemboss) return;
12615 int x = pemboss->x, y = pemboss->y;
12616 const double factor = 200;
12617
12618 wxASSERT_MSG(dc.GetDC(), "DrawEmboss has no dc (opengl?)");
12619 wxMemoryDC *pmdc = dynamic_cast<wxMemoryDC *>(dc.GetDC());
12620 wxASSERT_MSG(pmdc, "dc to EmbossCanvas not a memory dc");
12621
12622 // Grab a snipped image out of the chart
12623 wxMemoryDC snip_dc;
12624 wxBitmap snip_bmp(pemboss->width, pemboss->height, -1);
12625 snip_dc.SelectObject(snip_bmp);
12626
12627 snip_dc.Blit(0, 0, pemboss->width, pemboss->height, pmdc, x, y);
12628 snip_dc.SelectObject(wxNullBitmap);
12629
12630 wxImage snip_img = snip_bmp.ConvertToImage();
12631
12632 // Apply Emboss map to the snip image
12633 unsigned char *pdata = snip_img.GetData();
12634 if (pdata) {
12635 for (int y = 0; y < pemboss->height; y++) {
12636 int map_index = (y * pemboss->width);
12637 for (int x = 0; x < pemboss->width; x++) {
12638 double val = (pemboss->pmap[map_index] * factor) / 256.;
12639
12640 int nred = (int)((*pdata) + val);
12641 nred = nred > 255 ? 255 : (nred < 0 ? 0 : nred);
12642 *pdata++ = (unsigned char)nred;
12643
12644 int ngreen = (int)((*pdata) + val);
12645 ngreen = ngreen > 255 ? 255 : (ngreen < 0 ? 0 : ngreen);
12646 *pdata++ = (unsigned char)ngreen;
12647
12648 int nblue = (int)((*pdata) + val);
12649 nblue = nblue > 255 ? 255 : (nblue < 0 ? 0 : nblue);
12650 *pdata++ = (unsigned char)nblue;
12651
12652 map_index++;
12653 }
12654 }
12655 }
12656
12657 // Convert embossed snip to a bitmap
12658 wxBitmap emb_bmp(snip_img);
12659
12660 // Map to another memoryDC
12661 wxMemoryDC result_dc;
12662 result_dc.SelectObject(emb_bmp);
12663
12664 // Blit to target
12665 pmdc->Blit(x, y, pemboss->width, pemboss->height, &result_dc, 0, 0);
12666
12667 result_dc.SelectObject(wxNullBitmap);
12668}
12669
12670emboss_data *ChartCanvas::EmbossOverzoomIndicator(ocpnDC &dc) {
12671 double zoom_factor = GetVP().ref_scale / GetVP().chart_scale;
12672
12673 if (GetQuiltMode()) {
12674 // disable Overzoom indicator for MBTiles
12675 int refIndex = GetQuiltRefChartdbIndex();
12676 if (refIndex >= 0) {
12677 const ChartTableEntry &cte = ChartData->GetChartTableEntry(refIndex);
12678 ChartTypeEnum current_type = (ChartTypeEnum)cte.GetChartType();
12679 if (current_type == CHART_TYPE_MBTILES) {
12680 ChartBase *pChart = m_pQuilt->GetRefChart();
12681 ChartMbTiles *ptc = dynamic_cast<ChartMbTiles *>(pChart);
12682 if (ptc) {
12683 zoom_factor = ptc->GetZoomFactor();
12684 }
12685 }
12686 }
12687
12688 if (zoom_factor <= 3.9) return NULL;
12689 } else {
12690 if (m_singleChart) {
12691 if (zoom_factor <= 3.9) return NULL;
12692 } else
12693 return NULL;
12694 }
12695
12696 if (m_pEM_OverZoom) {
12697 m_pEM_OverZoom->x = 4;
12698 m_pEM_OverZoom->y = 0;
12699 if (g_MainToolbar && IsPrimaryCanvas()) {
12700 wxRect masterToolbarRect = g_MainToolbar->GetToolbarRect();
12701 m_pEM_OverZoom->x = masterToolbarRect.width + 4;
12702 }
12703 }
12704 return m_pEM_OverZoom;
12705}
12706
12707void ChartCanvas::DrawOverlayObjects(ocpnDC &dc, const wxRegion &ru) {
12708 GridDraw(dc);
12709
12710 // bool pluginOverlayRender = true;
12711 //
12712 // if(g_canvasConfig > 0){ // Multi canvas
12713 // if(IsPrimaryCanvas())
12714 // pluginOverlayRender = false;
12715 // }
12716
12717 g_overlayCanvas = this;
12718
12719 if (g_pi_manager) {
12720 g_pi_manager->SendViewPortToRequestingPlugIns(GetVP());
12721 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12723 }
12724
12725 AISDrawAreaNotices(dc, GetVP(), this);
12726
12727 wxDC *pdc = dc.GetDC();
12728 if (pdc) {
12729 pdc->DestroyClippingRegion();
12730 wxDCClipper(*pdc, ru);
12731 }
12732
12733 if (m_bShowNavobjects) {
12734 DrawAllTracksInBBox(dc, GetVP().GetBBox());
12735 DrawAllRoutesInBBox(dc, GetVP().GetBBox());
12736 DrawAllWaypointsInBBox(dc, GetVP().GetBBox());
12737 DrawAnchorWatchPoints(dc);
12738 } else {
12739 DrawActiveTrackInBBox(dc, GetVP().GetBBox());
12740 DrawActiveRouteInBBox(dc, GetVP().GetBBox());
12741 }
12742
12743 AISDraw(dc, GetVP(), this);
12744 ShipDraw(dc);
12745 AlertDraw(dc);
12746
12747 RenderVisibleSectorLights(dc);
12748
12749 RenderAllChartOutlines(dc, GetVP());
12750 RenderRouteLegs(dc);
12751 RenderShipToActive(dc, false);
12752 ScaleBarDraw(dc);
12753 s57_DrawExtendedLightSectors(dc, VPoint, extendedSectorLegs);
12754 if (g_pi_manager) {
12755 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12757 }
12758
12759 if (!g_bhide_depth_units) DrawEmboss(dc, EmbossDepthScale());
12760 if (!g_bhide_overzoom_flag) DrawEmboss(dc, EmbossOverzoomIndicator(dc));
12761
12762 if (g_pi_manager) {
12763 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12765 }
12766
12767 if (m_bShowTide) {
12768 RebuildTideSelectList(GetVP().GetBBox());
12769 DrawAllTidesInBBox(dc, GetVP().GetBBox());
12770 }
12771
12772 if (m_bShowCurrent) {
12773 RebuildCurrentSelectList(GetVP().GetBBox());
12774 DrawAllCurrentsInBBox(dc, GetVP().GetBBox());
12775 }
12776
12777 if (!g_PrintingInProgress) {
12778 if (IsPrimaryCanvas()) {
12779 if (g_MainToolbar) g_MainToolbar->DrawDC(dc, 1.0);
12780 }
12781
12782 if (IsPrimaryCanvas()) {
12783 if (g_iENCToolbar) g_iENCToolbar->DrawDC(dc, 1.0);
12784 }
12785
12786 if (m_muiBar) m_muiBar->DrawDC(dc, 1.0);
12787
12788 if (m_pTrackRolloverWin) {
12789 m_pTrackRolloverWin->Draw(dc);
12790 m_brepaint_piano = true;
12791 }
12792
12793 if (m_pRouteRolloverWin) {
12794 m_pRouteRolloverWin->Draw(dc);
12795 m_brepaint_piano = true;
12796 }
12797
12798 if (m_pAISRolloverWin) {
12799 m_pAISRolloverWin->Draw(dc);
12800 m_brepaint_piano = true;
12801 }
12802 if (m_brepaint_piano && g_bShowChartBar) {
12803 m_Piano->Paint(GetClientSize().y - m_Piano->GetHeight(), dc);
12804 }
12805
12806 if (m_Compass) m_Compass->Paint(dc);
12807
12808 if (!g_CanvasHideNotificationIcon) {
12809 if (IsPrimaryCanvas()) {
12810 auto &noteman = NotificationManager::GetInstance();
12811 if (noteman.GetNotificationCount()) {
12812 m_notification_button->SetIconSeverity(noteman.GetMaxSeverity());
12813 if (m_notification_button->UpdateStatus()) Refresh();
12814 m_notification_button->Show(true);
12815 m_notification_button->Paint(dc);
12816 } else {
12817 m_notification_button->Show(false);
12818 }
12819 }
12820 }
12821 }
12822 if (g_pi_manager) {
12823 g_pi_manager->RenderAllCanvasOverlayPlugIns(dc, GetVP(), m_canvasIndex,
12825 }
12826}
12827
12828emboss_data *ChartCanvas::EmbossDepthScale() {
12829 if (!m_bShowDepthUnits) return NULL;
12830
12831 int depth_unit_type = DEPTH_UNIT_UNKNOWN;
12832
12833 if (GetQuiltMode()) {
12834 wxString s = m_pQuilt->GetQuiltDepthUnit();
12835 s.MakeUpper();
12836 if (s == "FEET")
12837 depth_unit_type = DEPTH_UNIT_FEET;
12838 else if (s.StartsWith("FATHOMS"))
12839 depth_unit_type = DEPTH_UNIT_FATHOMS;
12840 else if (s.StartsWith("METERS"))
12841 depth_unit_type = DEPTH_UNIT_METERS;
12842 else if (s.StartsWith("METRES"))
12843 depth_unit_type = DEPTH_UNIT_METERS;
12844 else if (s.StartsWith("METRIC"))
12845 depth_unit_type = DEPTH_UNIT_METERS;
12846 else if (s.StartsWith("METER"))
12847 depth_unit_type = DEPTH_UNIT_METERS;
12848
12849 } else {
12850 if (m_singleChart) {
12851 depth_unit_type = m_singleChart->GetDepthUnitType();
12852 if (m_singleChart->GetChartFamily() == CHART_FAMILY_VECTOR)
12853 depth_unit_type = ps52plib->m_nDepthUnitDisplay + 1;
12854 }
12855 }
12856
12857 emboss_data *ped = NULL;
12858 switch (depth_unit_type) {
12859 case DEPTH_UNIT_FEET:
12860 ped = m_pEM_Feet;
12861 break;
12862 case DEPTH_UNIT_METERS:
12863 ped = m_pEM_Meters;
12864 break;
12865 case DEPTH_UNIT_FATHOMS:
12866 ped = m_pEM_Fathoms;
12867 break;
12868 default:
12869 return NULL;
12870 }
12871
12872 ped->x = (GetVP().pix_width - ped->width);
12873
12874 if (m_Compass && m_bShowCompassWin && g_bShowCompassWin) {
12875 wxRect r = m_Compass->GetRect();
12876 ped->y = r.y + r.height;
12877 } else {
12878 ped->y = 40;
12879 }
12880 return ped;
12881}
12882
12883void ChartCanvas::CreateDepthUnitEmbossMaps(ColorScheme cs) {
12884 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12885 wxFont font;
12886 if (style->embossFont == wxEmptyString) {
12887 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12888 font = *dFont;
12889 font.SetPointSize(60);
12890 font.SetWeight(wxFONTWEIGHT_BOLD);
12891 } else
12892 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12893 wxFONTWEIGHT_BOLD, false, style->embossFont);
12894
12895 int emboss_width = 500;
12896 int emboss_height = 200;
12897
12898 // Free any existing emboss maps
12899 delete m_pEM_Feet;
12900 delete m_pEM_Meters;
12901 delete m_pEM_Fathoms;
12902
12903 // Create the 3 DepthUnit emboss map structures
12904 m_pEM_Feet =
12905 CreateEmbossMapData(font, emboss_width, emboss_height, _("Feet"), cs);
12906 m_pEM_Meters =
12907 CreateEmbossMapData(font, emboss_width, emboss_height, _("Meters"), cs);
12908 m_pEM_Fathoms =
12909 CreateEmbossMapData(font, emboss_width, emboss_height, _("Fathoms"), cs);
12910}
12911
12912#define OVERZOOM_TEXT _("OverZoom")
12913
12914void ChartCanvas::SetOverzoomFont() {
12915 ocpnStyle::Style *style = g_StyleManager->GetCurrentStyle();
12916 int w, h;
12917
12918 wxFont font;
12919 if (style->embossFont == wxEmptyString) {
12920 wxFont *dFont = FontMgr::Get().GetFont(_("Dialog"), 0);
12921 font = *dFont;
12922 font.SetPointSize(40);
12923 font.SetWeight(wxFONTWEIGHT_BOLD);
12924 } else
12925 font = wxFont(style->embossHeight, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
12926 wxFONTWEIGHT_BOLD, false, style->embossFont);
12927
12928 wxClientDC dc(this);
12929 dc.SetFont(font);
12930 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12931
12932 while (font.GetPointSize() > 10 && (w > 500 || h > 100)) {
12933 font.SetPointSize(font.GetPointSize() - 1);
12934 dc.SetFont(font);
12935 dc.GetTextExtent(OVERZOOM_TEXT, &w, &h);
12936 }
12937 m_overzoomFont = font;
12938 m_overzoomTextWidth = w;
12939 m_overzoomTextHeight = h;
12940}
12941
12942void ChartCanvas::CreateOZEmbossMapData(ColorScheme cs) {
12943 delete m_pEM_OverZoom;
12944
12945 if (m_overzoomTextWidth > 0 && m_overzoomTextHeight > 0)
12946 m_pEM_OverZoom =
12947 CreateEmbossMapData(m_overzoomFont, m_overzoomTextWidth + 10,
12948 m_overzoomTextHeight + 10, OVERZOOM_TEXT, cs);
12949}
12950
12951emboss_data *ChartCanvas::CreateEmbossMapData(wxFont &font, int width,
12952 int height, const wxString &str,
12953 ColorScheme cs) {
12954 int *pmap;
12955
12956 // Create a temporary bitmap
12957 wxBitmap bmp(width, height, -1);
12958
12959 // Create a memory DC
12960 wxMemoryDC temp_dc;
12961 temp_dc.SelectObject(bmp);
12962
12963 // Paint on it
12964 temp_dc.SetBackground(*wxWHITE_BRUSH);
12965 temp_dc.SetTextBackground(*wxWHITE);
12966 temp_dc.SetTextForeground(*wxBLACK);
12967
12968 temp_dc.Clear();
12969
12970 temp_dc.SetFont(font);
12971
12972 int str_w, str_h;
12973 temp_dc.GetTextExtent(str, &str_w, &str_h);
12974 // temp_dc.DrawText( str, width - str_w - 10, 10 );
12975 temp_dc.DrawText(str, 1, 1);
12976
12977 // Deselect the bitmap
12978 temp_dc.SelectObject(wxNullBitmap);
12979
12980 // Convert bitmap the wxImage for manipulation
12981 wxImage img = bmp.ConvertToImage();
12982
12983 int image_width = str_w * 105 / 100;
12984 int image_height = str_h * 105 / 100;
12985 wxRect r(0, 0, wxMin(image_width, img.GetWidth()),
12986 wxMin(image_height, img.GetHeight()));
12987 wxImage imgs = img.GetSubImage(r);
12988
12989 double val_factor;
12990 switch (cs) {
12991 case GLOBAL_COLOR_SCHEME_DAY:
12992 default:
12993 val_factor = 1;
12994 break;
12995 case GLOBAL_COLOR_SCHEME_DUSK:
12996 val_factor = .5;
12997 break;
12998 case GLOBAL_COLOR_SCHEME_NIGHT:
12999 val_factor = .25;
13000 break;
13001 }
13002
13003 int val;
13004 int index;
13005 const int w = imgs.GetWidth();
13006 const int h = imgs.GetHeight();
13007 pmap = (int *)calloc(w * h * sizeof(int), 1);
13008 // Create emboss map by differentiating the emboss image
13009 // and storing integer results in pmap
13010 // n.b. since the image is B/W, it is sufficient to check
13011 // one channel (i.e. red) only
13012 for (int y = 1; y < h - 1; y++) {
13013 for (int x = 1; x < w - 1; x++) {
13014 val =
13015 img.GetRed(x + 1, y + 1) - img.GetRed(x - 1, y - 1); // range +/- 256
13016 val = (int)(val * val_factor);
13017 index = (y * w) + x;
13018 pmap[index] = val;
13019 }
13020 }
13021
13022 emboss_data *pret = new emboss_data;
13023 pret->pmap = pmap;
13024 pret->width = w;
13025 pret->height = h;
13026
13027 return pret;
13028}
13029
13030void ChartCanvas::DrawAllTracksInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13031 Track *active_track = NULL;
13032 for (Track *pTrackDraw : g_TrackList) {
13033 if (g_pActiveTrack == pTrackDraw) {
13034 active_track = pTrackDraw;
13035 continue;
13036 }
13037
13038 TrackGui(*pTrackDraw).Draw(this, dc, GetVP(), BltBBox);
13039 }
13040
13041 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13042}
13043
13044void ChartCanvas::DrawActiveTrackInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13045 Track *active_track = NULL;
13046 for (Track *pTrackDraw : g_TrackList) {
13047 if (g_pActiveTrack == pTrackDraw) {
13048 active_track = pTrackDraw;
13049 break;
13050 }
13051 }
13052 if (active_track) TrackGui(*active_track).Draw(this, dc, GetVP(), BltBBox);
13053}
13054
13055void ChartCanvas::DrawAllRoutesInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13056 Route *active_route = NULL;
13057 for (Route *pRouteDraw : *pRouteList) {
13058 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13059 active_route = pRouteDraw;
13060 continue;
13061 }
13062
13063 // if(m_canvasIndex == 1)
13064 RouteGui(*pRouteDraw).Draw(dc, this, BltBBox);
13065 }
13066
13067 // Draw any active or selected route (or track) last, so that is is always on
13068 // top
13069 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13070}
13071
13072void ChartCanvas::DrawActiveRouteInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13073 Route *active_route = NULL;
13074
13075 for (Route *pRouteDraw : *pRouteList) {
13076 if (pRouteDraw->IsActive() || pRouteDraw->IsSelected()) {
13077 active_route = pRouteDraw;
13078 break;
13079 }
13080 }
13081 if (active_route) RouteGui(*active_route).Draw(dc, this, BltBBox);
13082}
13083
13084void ChartCanvas::DrawAllWaypointsInBBox(ocpnDC &dc, LLBBox &BltBBox) {
13085 if (!pWayPointMan) return;
13086
13087 auto node = pWayPointMan->GetWaypointList()->begin();
13088
13089 while (node != pWayPointMan->GetWaypointList()->end()) {
13090 RoutePoint *pWP = *node;
13091 if (pWP) {
13092 if (pWP->m_bIsInRoute) {
13093 ++node;
13094 continue;
13095 }
13096
13097 /* technically incorrect... waypoint has bounding box */
13098 if (BltBBox.Contains(pWP->m_lat, pWP->m_lon))
13099 RoutePointGui(*pWP).Draw(dc, this, NULL);
13100 else {
13101 // Are Range Rings enabled?
13102 if (pWP->GetShowWaypointRangeRings() &&
13103 (pWP->GetWaypointRangeRingsNumber() > 0)) {
13104 double factor = 1.00;
13105 if (pWP->GetWaypointRangeRingsStepUnits() ==
13106 1) // convert kilometers to NMi
13107 factor = 1 / 1.852;
13108
13109 double radius = factor * pWP->GetWaypointRangeRingsNumber() *
13110 pWP->GetWaypointRangeRingsStep() / 60.;
13111 radius *= 2; // Fudge factor
13112
13113 LLBBox radar_box;
13114 radar_box.Set(pWP->m_lat - radius, pWP->m_lon - radius,
13115 pWP->m_lat + radius, pWP->m_lon + radius);
13116 if (!BltBBox.IntersectOut(radar_box)) {
13117 RoutePointGui(*pWP).Draw(dc, this, NULL);
13118 }
13119 }
13120 }
13121 }
13122
13123 ++node;
13124 }
13125}
13126
13127void ChartCanvas::DrawBlinkObjects() {
13128 // All RoutePoints
13129 wxRect update_rect;
13130
13131 if (!pWayPointMan) return;
13132
13133 for (RoutePoint *pWP : *pWayPointMan->GetWaypointList()) {
13134 if (pWP) {
13135 if (pWP->m_bBlink) {
13136 update_rect.Union(pWP->CurrentRect_in_DC);
13137 }
13138 }
13139 }
13140 if (!update_rect.IsEmpty()) RefreshRect(update_rect);
13141}
13142
13143void ChartCanvas::DrawAnchorWatchPoints(ocpnDC &dc) {
13144 // draw anchor watch rings, if activated
13145
13147 wxPoint r1, r2;
13148 wxPoint lAnchorPoint1, lAnchorPoint2;
13149 double lpp1 = 0.0;
13150 double lpp2 = 0.0;
13151 if (pAnchorWatchPoint1) {
13152 lpp1 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint1);
13154 &lAnchorPoint1);
13155 }
13156 if (pAnchorWatchPoint2) {
13157 lpp2 = GetAnchorWatchRadiusPixels(pAnchorWatchPoint2);
13159 &lAnchorPoint2);
13160 }
13161
13162 wxPen ppPeng(GetGlobalColor("UGREN"), 2);
13163 wxPen ppPenr(GetGlobalColor("URED"), 2);
13164
13165 wxBrush *ppBrush = wxTheBrushList->FindOrCreateBrush(
13166 wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT);
13167 dc.SetBrush(*ppBrush);
13168
13169 if (lpp1 > 0) {
13170 dc.SetPen(ppPeng);
13171 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13172 }
13173
13174 if (lpp2 > 0) {
13175 dc.SetPen(ppPeng);
13176 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13177 }
13178
13179 if (lpp1 < 0) {
13180 dc.SetPen(ppPenr);
13181 dc.StrokeCircle(lAnchorPoint1.x, lAnchorPoint1.y, fabs(lpp1));
13182 }
13183
13184 if (lpp2 < 0) {
13185 dc.SetPen(ppPenr);
13186 dc.StrokeCircle(lAnchorPoint2.x, lAnchorPoint2.y, fabs(lpp2));
13187 }
13188 }
13189}
13190
13191double ChartCanvas::GetAnchorWatchRadiusPixels(RoutePoint *pAnchorWatchPoint) {
13192 double lpp = 0.;
13193 wxPoint r1;
13194 wxPoint lAnchorPoint;
13195 double d1 = 0.0;
13196 double dabs;
13197 double tlat1, tlon1;
13198
13199 if (pAnchorWatchPoint) {
13200 (pAnchorWatchPoint->GetName()).ToDouble(&d1);
13201 d1 = AnchorDistFix(d1, AnchorPointMinDist, g_nAWMax);
13202 dabs = fabs(d1 / 1852.);
13203 ll_gc_ll(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon, 0, dabs,
13204 &tlat1, &tlon1);
13205 GetCanvasPointPix(tlat1, tlon1, &r1);
13206 GetCanvasPointPix(pAnchorWatchPoint->m_lat, pAnchorWatchPoint->m_lon,
13207 &lAnchorPoint);
13208 lpp = sqrt(pow((double)(lAnchorPoint.x - r1.x), 2) +
13209 pow((double)(lAnchorPoint.y - r1.y), 2));
13210
13211 // This is an entry watch
13212 if (d1 < 0) lpp = -lpp;
13213 }
13214 return lpp;
13215}
13216
13217//------------------------------------------------------------------------------------------
13218// Tides Support
13219//------------------------------------------------------------------------------------------
13220void ChartCanvas::RebuildTideSelectList(LLBBox &BBox) {
13221 if (!ptcmgr) return;
13222
13223 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_TIDEPOINT);
13224
13225 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13226 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13227 double lon = pIDX->IDX_lon;
13228 double lat = pIDX->IDX_lat;
13229
13230 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13231 if ((type == 't') || (type == 'T')) {
13232 if (BBox.Contains(lat, lon)) {
13233 // Manage the point selection list
13234 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_TIDEPOINT);
13235 }
13236 }
13237 }
13238}
13239
13240void ChartCanvas::DrawAllTidesInBBox(ocpnDC &dc, LLBBox &BBox) {
13241 if (!ptcmgr) return;
13242
13243 wxDateTime this_now = gTimeSource;
13244 bool cur_time = !gTimeSource.IsValid();
13245 if (cur_time) this_now = wxDateTime::Now();
13246 time_t t_this_now = this_now.GetTicks();
13247
13248 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13249 wxPENSTYLE_SOLID);
13250 wxPen *pyelo_pen = wxThePenList->FindOrCreatePen(
13251 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), 1, wxPENSTYLE_SOLID);
13252 wxPen *pblue_pen = wxThePenList->FindOrCreatePen(
13253 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), 1, wxPENSTYLE_SOLID);
13254
13255 wxBrush *pgreen_brush = wxTheBrushList->FindOrCreateBrush(
13256 GetGlobalColor("GREEN1"), wxBRUSHSTYLE_SOLID);
13257 wxBrush *pblue_brush = wxTheBrushList->FindOrCreateBrush(
13258 GetGlobalColor(cur_time ? "BLUE2" : "BLUE3"), wxBRUSHSTYLE_SOLID);
13259 wxBrush *pyelo_brush = wxTheBrushList->FindOrCreateBrush(
13260 GetGlobalColor(cur_time ? "YELO1" : "YELO2"), wxBRUSHSTYLE_SOLID);
13261
13262 wxFont *dFont = FontMgr::Get().GetFont(_("ExtendedTideIcon"));
13263 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("ExtendedTideIcon")));
13264 int font_size = wxMax(10, dFont->GetPointSize());
13265 font_size /= g_Platform->GetDisplayDIPMult(this);
13266 wxFont *plabelFont = FontMgr::Get().FindOrCreateFont(
13267 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13268 false, dFont->GetFaceName());
13269
13270 dc.SetPen(*pblack_pen);
13271 dc.SetBrush(*pgreen_brush);
13272
13273 wxBitmap bm;
13274 switch (m_cs) {
13275 case GLOBAL_COLOR_SCHEME_DAY:
13276 bm = m_bmTideDay;
13277 break;
13278 case GLOBAL_COLOR_SCHEME_DUSK:
13279 bm = m_bmTideDusk;
13280 break;
13281 case GLOBAL_COLOR_SCHEME_NIGHT:
13282 bm = m_bmTideNight;
13283 break;
13284 default:
13285 bm = m_bmTideDay;
13286 break;
13287 }
13288
13289 int bmw = bm.GetWidth();
13290 int bmh = bm.GetHeight();
13291
13292 float scale_factor = 1.0;
13293
13294 // Set the onscreen size of the symbol
13295 // Compensate for various display resolutions
13296 float icon_pixelRefDim = 45;
13297
13298 // Tidal report graphic is scaled by the text size of the label in use
13299 wxScreenDC sdc;
13300 int height;
13301 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, plabelFont);
13302 height *= g_Platform->GetDisplayDIPMult(this);
13303 float pix_factor = (1.5 * height) / icon_pixelRefDim;
13304
13305 scale_factor *= pix_factor;
13306
13307 float user_scale_factor = g_ChartScaleFactorExp;
13308 if (g_ChartScaleFactorExp > 1.0)
13309 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13310 1.2; // soften the scale factor a bit
13311
13312 scale_factor *= user_scale_factor;
13313 scale_factor *= GetContentScaleFactor();
13314
13315 {
13316 double marge = 0.05;
13317 std::vector<LLBBox> drawn_boxes;
13318 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13319 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13320
13321 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13322 if ((type == 't') || (type == 'T')) // only Tides
13323 {
13324 double lon = pIDX->IDX_lon;
13325 double lat = pIDX->IDX_lat;
13326
13327 if (BBox.ContainsMarge(lat, lon, marge)) {
13328 // Avoid drawing detailed graphic for duplicate tide stations
13329 if (GetVP().chart_scale < 500000) {
13330 bool bdrawn = false;
13331 for (size_t i = 0; i < drawn_boxes.size(); i++) {
13332 if (drawn_boxes[i].Contains(lat, lon)) {
13333 bdrawn = true;
13334 break;
13335 }
13336 }
13337 if (bdrawn) continue; // the station loop
13338
13339 LLBBox this_box;
13340 this_box.Set(lat, lon, lat, lon);
13341 this_box.EnLarge(.005);
13342 drawn_boxes.push_back(this_box);
13343 }
13344
13345 wxPoint r;
13346 GetCanvasPointPix(lat, lon, &r);
13347 // draw standard icons
13348 if (GetVP().chart_scale > 500000) {
13349 dc.DrawBitmap(bm, r.x - bmw / 2, r.y - bmh / 2, true);
13350 }
13351 // draw "extended" icons
13352 else {
13353 dc.SetFont(*plabelFont);
13354 {
13355 {
13356 float val, nowlev;
13357 float ltleve = 0.;
13358 float htleve = 0.;
13359 time_t tctime;
13360 time_t lttime = 0;
13361 time_t httime = 0;
13362 bool wt;
13363 // define if flood or ebb in the last ten minutes and verify if
13364 // data are useable
13365 if (ptcmgr->GetTideFlowSens(
13366 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13367 pIDX->IDX_rec_num, nowlev, val, wt)) {
13368 // search forward the first HW or LW near "now" ( starting at
13369 // "now" - ten minutes )
13370 ptcmgr->GetHightOrLowTide(
13371 t_this_now + BACKWARD_TEN_MINUTES_STEP,
13372 FORWARD_TEN_MINUTES_STEP, FORWARD_ONE_MINUTES_STEP, val,
13373 wt, pIDX->IDX_rec_num, val, tctime);
13374 if (wt) {
13375 httime = tctime;
13376 htleve = val;
13377 } else {
13378 lttime = tctime;
13379 ltleve = val;
13380 }
13381 wt = !wt;
13382
13383 // then search opposite tide near "now"
13384 if (tctime > t_this_now) // search backward
13385 ptcmgr->GetHightOrLowTide(
13386 t_this_now, BACKWARD_TEN_MINUTES_STEP,
13387 BACKWARD_ONE_MINUTES_STEP, nowlev, wt,
13388 pIDX->IDX_rec_num, val, tctime);
13389 else
13390 // or search forward
13391 ptcmgr->GetHightOrLowTide(
13392 t_this_now, FORWARD_TEN_MINUTES_STEP,
13393 FORWARD_ONE_MINUTES_STEP, nowlev, wt, pIDX->IDX_rec_num,
13394 val, tctime);
13395 if (wt) {
13396 httime = tctime;
13397 htleve = val;
13398 } else {
13399 lttime = tctime;
13400 ltleve = val;
13401 }
13402
13403 // draw the tide rectangle:
13404
13405 // tide icon rectangle has default pre-scaled width = 12 ,
13406 // height = 45
13407 int width = (int)(12 * scale_factor + 0.5);
13408 int height = (int)(45 * scale_factor + 0.5);
13409 int linew = wxMax(1, (int)(scale_factor));
13410 int xDraw = r.x - (width / 2);
13411 int yDraw = r.y - (height / 2);
13412
13413 // process tide state ( %height and flow sens )
13414 float ts = 1 - ((nowlev - ltleve) / (htleve - ltleve));
13415 int hs = (httime > lttime) ? -4 : 4;
13416 hs *= (int)(scale_factor + 0.5);
13417 if (ts > 0.995 || ts < 0.005) hs = 0;
13418 int ht_y = (int)(height * ts);
13419
13420 // draw yellow tide rectangle outlined in black
13421 pblack_pen->SetWidth(linew);
13422 dc.SetPen(*pblack_pen);
13423 dc.SetBrush(*pyelo_brush);
13424 dc.DrawRectangle(xDraw, yDraw, width, height);
13425
13426 // draw blue rectangle as water height, smaller in width than
13427 // yellow rectangle
13428 dc.SetPen(*pblue_pen);
13429 dc.SetBrush(*pblue_brush);
13430 dc.DrawRectangle((xDraw + 2 * linew), yDraw + ht_y,
13431 (width - (4 * linew)), height - ht_y);
13432
13433 // draw sens arrows (ensure they are not "under-drawn" by top
13434 // line of blue rectangle )
13435 int hl;
13436 wxPoint arrow[3];
13437 arrow[0].x = xDraw + 2 * linew;
13438 arrow[1].x = xDraw + width / 2;
13439 arrow[2].x = xDraw + width - 2 * linew;
13440 pyelo_pen->SetWidth(linew);
13441 pblue_pen->SetWidth(linew);
13442 if (ts > 0.35 || ts < 0.15) // one arrow at 3/4 hight tide
13443 {
13444 hl = (int)(height * 0.25) + yDraw;
13445 arrow[0].y = hl;
13446 arrow[1].y = hl + hs;
13447 arrow[2].y = hl;
13448 if (ts < 0.15)
13449 dc.SetPen(*pyelo_pen);
13450 else
13451 dc.SetPen(*pblue_pen);
13452 dc.DrawLines(3, arrow);
13453 }
13454 if (ts > 0.60 || ts < 0.40) // one arrow at 1/2 hight tide
13455 {
13456 hl = (int)(height * 0.5) + yDraw;
13457 arrow[0].y = hl;
13458 arrow[1].y = hl + hs;
13459 arrow[2].y = hl;
13460 if (ts < 0.40)
13461 dc.SetPen(*pyelo_pen);
13462 else
13463 dc.SetPen(*pblue_pen);
13464 dc.DrawLines(3, arrow);
13465 }
13466 if (ts < 0.65 || ts > 0.85) // one arrow at 1/4 Hight tide
13467 {
13468 hl = (int)(height * 0.75) + yDraw;
13469 arrow[0].y = hl;
13470 arrow[1].y = hl + hs;
13471 arrow[2].y = hl;
13472 if (ts < 0.65)
13473 dc.SetPen(*pyelo_pen);
13474 else
13475 dc.SetPen(*pblue_pen);
13476 dc.DrawLines(3, arrow);
13477 }
13478 // draw tide level text
13479 wxString s;
13480 s.Printf("%3.1f", nowlev);
13481 Station_Data *pmsd = pIDX->pref_sta_data; // write unit
13482 if (pmsd) s.Append(wxString(pmsd->units_abbrv, wxConvUTF8));
13483 int wx1;
13484 dc.GetTextExtent(s, &wx1, NULL);
13485 wx1 *= g_Platform->GetDisplayDIPMult(this);
13486 dc.DrawText(s, r.x - (wx1 / 2), yDraw + height);
13487 }
13488 }
13489 }
13490 }
13491 }
13492 }
13493 }
13494 }
13495}
13496
13497//------------------------------------------------------------------------------------------
13498// Currents Support
13499//------------------------------------------------------------------------------------------
13500
13501void ChartCanvas::RebuildCurrentSelectList(LLBBox &BBox) {
13502 if (!ptcmgr) return;
13503
13504 pSelectTC->DeleteAllSelectableTypePoints(SELTYPE_CURRENTPOINT);
13505
13506 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13507 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13508 double lon = pIDX->IDX_lon;
13509 double lat = pIDX->IDX_lat;
13510
13511 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13512 if (((type == 'c') || (type == 'C')) && (!pIDX->b_skipTooDeep)) {
13513 if ((BBox.Contains(lat, lon))) {
13514 // Manage the point selection list
13515 pSelectTC->AddSelectablePoint(lat, lon, pIDX, SELTYPE_CURRENTPOINT);
13516 }
13517 }
13518 }
13519}
13520
13521void ChartCanvas::DrawAllCurrentsInBBox(ocpnDC &dc, LLBBox &BBox) {
13522 if (!ptcmgr) return;
13523
13524 float tcvalue, dir;
13525 bool bnew_val;
13526 char sbuf[20];
13527 wxFont *pTCFont;
13528 double lon_last = 0.;
13529 double lat_last = 0.;
13530 // arrow size for Raz Blanchard : 12 knots north
13531 double marge = 0.2;
13532 bool cur_time = !gTimeSource.IsValid();
13533
13534 double true_scale_display = floor(VPoint.chart_scale / 100.) * 100.;
13535 bDrawCurrentValues = true_scale_display < g_Show_Target_Name_Scale;
13536
13537 wxPen *pblack_pen = wxThePenList->FindOrCreatePen(GetGlobalColor("UINFD"), 1,
13538 wxPENSTYLE_SOLID);
13539 wxPen *porange_pen = wxThePenList->FindOrCreatePen(
13540 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), 1, wxPENSTYLE_SOLID);
13541 wxBrush *porange_brush = wxTheBrushList->FindOrCreateBrush(
13542 GetGlobalColor(cur_time ? "UINFO" : "UINFB"), wxBRUSHSTYLE_SOLID);
13543 wxBrush *pgray_brush = wxTheBrushList->FindOrCreateBrush(
13544 GetGlobalColor("UIBDR"), wxBRUSHSTYLE_SOLID);
13545 wxBrush *pblack_brush = wxTheBrushList->FindOrCreateBrush(
13546 GetGlobalColor("UINFD"), wxBRUSHSTYLE_SOLID);
13547
13548 double skew_angle = GetVPRotation();
13549
13550 wxFont *dFont = FontMgr::Get().GetFont(_("CurrentValue"));
13551 dc.SetTextForeground(FontMgr::Get().GetFontColor(_("CurrentValue")));
13552 int font_size = wxMax(10, dFont->GetPointSize());
13553 font_size /= g_Platform->GetDisplayDIPMult(this);
13554 pTCFont = FontMgr::Get().FindOrCreateFont(
13555 font_size, dFont->GetFamily(), dFont->GetStyle(), dFont->GetWeight(),
13556 false, dFont->GetFaceName());
13557
13558 float scale_factor = 1.0;
13559
13560 // Set the onscreen size of the symbol
13561 // Current report graphic is scaled by the text size of the label in use
13562 wxScreenDC sdc;
13563 int height;
13564 sdc.GetTextExtent("M", NULL, &height, NULL, NULL, pTCFont);
13565 height *= g_Platform->GetDisplayDIPMult(this);
13566 float nominal_icon_size_pixels = 15;
13567 float pix_factor = (1 * height) / nominal_icon_size_pixels;
13568
13569 scale_factor *= pix_factor;
13570
13571 float user_scale_factor = g_ChartScaleFactorExp;
13572 if (g_ChartScaleFactorExp > 1.0)
13573 user_scale_factor = (log(g_ChartScaleFactorExp) + 1.0) *
13574 1.2; // soften the scale factor a bit
13575
13576 scale_factor *= user_scale_factor;
13577
13578 scale_factor *= GetContentScaleFactor();
13579
13580 {
13581 for (int i = 1; i < ptcmgr->Get_max_IDX() + 1; i++) {
13582 const IDX_entry *pIDX = ptcmgr->GetIDX_entry(i);
13583 double lon = pIDX->IDX_lon;
13584 double lat = pIDX->IDX_lat;
13585
13586 char type = pIDX->IDX_type; // Entry "TCtcIUu" identifier
13587 if (((type == 'c') || (type == 'C')) && (1 /*pIDX->IDX_Useable*/)) {
13588 if (!pIDX->b_skipTooDeep && (BBox.ContainsMarge(lat, lon, marge))) {
13589 wxPoint r;
13590 GetCanvasPointPix(lat, lon, &r);
13591
13592 wxPoint d[4]; // points of a diamond at the current station location
13593 int dd = (int)(5.0 * scale_factor + 0.5);
13594 d[0].x = r.x;
13595 d[0].y = r.y + dd;
13596 d[1].x = r.x + dd;
13597 d[1].y = r.y;
13598 d[2].x = r.x;
13599 d[2].y = r.y - dd;
13600 d[3].x = r.x - dd;
13601 d[3].y = r.y;
13602
13603 if (1) {
13604 pblack_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13605 dc.SetPen(*pblack_pen);
13606 dc.SetBrush(*porange_brush);
13607 dc.DrawPolygon(4, d);
13608
13609 if (type == 'C') {
13610 dc.SetBrush(*pblack_brush);
13611 dc.DrawCircle(r.x, r.y, (int)(2 * scale_factor));
13612 }
13613
13614 if (GetVP().chart_scale < 1000000) {
13615 if (!ptcmgr->GetTideOrCurrent15(0, i, tcvalue, dir, bnew_val))
13616 continue;
13617 } else
13618 continue;
13619
13620 if (1 /*type == 'c'*/) {
13621 {
13622 // Get the display pixel location of the current station
13623 int pixxc, pixyc;
13624 pixxc = r.x;
13625 pixyc = r.y;
13626
13627 // Adjust drawing size using logarithmic scale. tcvalue is
13628 // current in knots
13629 double a1 = fabs(tcvalue) * 10.;
13630 // Current values <= 0.1 knot will have no arrow
13631 a1 = wxMax(1.0, a1);
13632 double a2 = log10(a1);
13633
13634 float cscale = scale_factor * a2 * 0.3;
13635
13636 porange_pen->SetWidth(wxMax(2, (int)(scale_factor + 0.5)));
13637 dc.SetPen(*porange_pen);
13638 DrawArrow(dc, pixxc, pixyc, dir - 90 + (skew_angle * 180. / PI),
13639 cscale);
13640 // Draw text, if enabled
13641
13642 if (bDrawCurrentValues) {
13643 dc.SetFont(*pTCFont);
13644 snprintf(sbuf, 19, "%3.1f", fabs(tcvalue));
13645 dc.DrawText(wxString(sbuf, wxConvUTF8), pixxc, pixyc);
13646 }
13647 }
13648 } // scale
13649 }
13650 /* This is useful for debugging the TC database
13651 else
13652 {
13653 dc.SetPen ( *porange_pen );
13654 dc.SetBrush ( *pgray_brush );
13655 dc.DrawPolygon ( 4, d );
13656 }
13657 */
13658 }
13659 lon_last = lon;
13660 lat_last = lat;
13661 }
13662 }
13663 }
13664}
13665
13666void ChartCanvas::DrawTCWindow(int x, int y, void *pvIDX) {
13667 ShowSingleTideDialog(x, y, pvIDX);
13668}
13669
13670void ChartCanvas::ShowSingleTideDialog(int x, int y, void *pvIDX) {
13671 if (!pvIDX) return; // Validate input
13672
13673 IDX_entry *pNewIDX = (IDX_entry *)pvIDX;
13674
13675 // Check if a tide dialog is already open and visible
13676 if (pCwin && pCwin->IsShown()) {
13677 // Same tide station: bring existing dialog to front (preserves user
13678 // context)
13679 if (pCwin->GetCurrentIDX() == pNewIDX) {
13680 pCwin->Raise();
13681 pCwin->SetFocus();
13682
13683 // Provide subtle visual feedback that dialog is already open
13684 pCwin->RequestUserAttention(wxUSER_ATTENTION_INFO);
13685 return;
13686 }
13687
13688 // Different tide station: close current dialog before opening new one
13689 pCwin->Close(); // This sets pCwin = NULL in OnCloseWindow
13690 }
13691
13692 if (pCwin) {
13693 // This shouldn't happen but ensures clean state
13694 pCwin->Destroy();
13695 pCwin = NULL;
13696 }
13697
13698 // Create and display new tide dialog
13699 pCwin = new TCWin(this, x, y, pvIDX);
13700
13701 // Ensure the dialog is properly shown and focused
13702 if (pCwin) {
13703 pCwin->Show();
13704 pCwin->Raise();
13705 pCwin->SetFocus();
13706 }
13707}
13708
13709bool ChartCanvas::IsTideDialogOpen() const { return pCwin && pCwin->IsShown(); }
13710
13712 if (pCwin) {
13713 pCwin->Close(); // This will set pCwin = NULL via OnCloseWindow
13714 }
13715}
13716
13717#define NUM_CURRENT_ARROW_POINTS 9
13718static wxPoint CurrentArrowArray[NUM_CURRENT_ARROW_POINTS] = {
13719 wxPoint(0, 0), wxPoint(0, -10), wxPoint(55, -10),
13720 wxPoint(55, -25), wxPoint(100, 0), wxPoint(55, 25),
13721 wxPoint(55, 10), wxPoint(0, 10), wxPoint(0, 0)};
13722
13723void ChartCanvas::DrawArrow(ocpnDC &dc, int x, int y, double rot_angle,
13724 double scale) {
13725 if (scale > 1e-2) {
13726 float sin_rot = sin(rot_angle * PI / 180.);
13727 float cos_rot = cos(rot_angle * PI / 180.);
13728
13729 // Move to the first point
13730
13731 float xt = CurrentArrowArray[0].x;
13732 float yt = CurrentArrowArray[0].y;
13733
13734 float xp = (xt * cos_rot) - (yt * sin_rot);
13735 float yp = (xt * sin_rot) + (yt * cos_rot);
13736 int x1 = (int)(xp * scale);
13737 int y1 = (int)(yp * scale);
13738
13739 // Walk thru the point list
13740 for (int ip = 1; ip < NUM_CURRENT_ARROW_POINTS; ip++) {
13741 xt = CurrentArrowArray[ip].x;
13742 yt = CurrentArrowArray[ip].y;
13743
13744 float xp = (xt * cos_rot) - (yt * sin_rot);
13745 float yp = (xt * sin_rot) + (yt * cos_rot);
13746 int x2 = (int)(xp * scale);
13747 int y2 = (int)(yp * scale);
13748
13749 dc.DrawLine(x1 + x, y1 + y, x2 + x, y2 + y);
13750
13751 x1 = x2;
13752 y1 = y2;
13753 }
13754 }
13755}
13756
13757wxString ChartCanvas::FindValidUploadPort() {
13758 wxString port;
13759 // Try to use the saved persistent upload port first
13760 if (!g_uploadConnection.IsEmpty() &&
13761 g_uploadConnection.StartsWith("Serial")) {
13762 port = g_uploadConnection;
13763 }
13764
13765 else {
13766 // If there is no persistent upload port recorded (yet)
13767 // then use the first available serial connection which has output defined.
13768 for (auto *cp : TheConnectionParams()) {
13769 if ((cp->IOSelect != DS_TYPE_INPUT) && cp->Type == SERIAL)
13770 port << "Serial:" << cp->Port;
13771 }
13772 }
13773 return port;
13774}
13775
13776void ShowAISTargetQueryDialog(wxWindow *win, int mmsi) {
13777 if (!win) return;
13778
13779 if (NULL == g_pais_query_dialog_active) {
13780 int pos_x = g_ais_query_dialog_x;
13781 int pos_y = g_ais_query_dialog_y;
13782
13783 if (g_pais_query_dialog_active) {
13784 g_pais_query_dialog_active->Destroy();
13785 g_pais_query_dialog_active = new AISTargetQueryDialog();
13786 } else {
13787 g_pais_query_dialog_active = new AISTargetQueryDialog();
13788 }
13789
13790 g_pais_query_dialog_active->Create(win, -1, _("AIS Target Query"),
13791 wxPoint(pos_x, pos_y));
13792
13793 g_pais_query_dialog_active->SetAutoCentre(g_btouch);
13794 g_pais_query_dialog_active->SetAutoSize(g_bresponsive);
13795 g_pais_query_dialog_active->SetMMSI(mmsi);
13796 g_pais_query_dialog_active->UpdateText();
13797 wxSize sz = g_pais_query_dialog_active->GetSize();
13798
13799 bool b_reset_pos = false;
13800#ifdef __WXMSW__
13801 // Support MultiMonitor setups which an allow negative window positions.
13802 // If the requested window title bar does not intersect any installed
13803 // monitor, then default to simple primary monitor positioning.
13804 RECT frame_title_rect;
13805 frame_title_rect.left = pos_x;
13806 frame_title_rect.top = pos_y;
13807 frame_title_rect.right = pos_x + sz.x;
13808 frame_title_rect.bottom = pos_y + 30;
13809
13810 if (NULL == MonitorFromRect(&frame_title_rect, MONITOR_DEFAULTTONULL))
13811 b_reset_pos = true;
13812#else
13813
13814 // Make sure drag bar (title bar) of window intersects wxClient Area of
13815 // screen, with a little slop...
13816 wxRect window_title_rect; // conservative estimate
13817 window_title_rect.x = pos_x;
13818 window_title_rect.y = pos_y;
13819 window_title_rect.width = sz.x;
13820 window_title_rect.height = 30;
13821
13822 wxRect ClientRect = wxGetClientDisplayRect();
13823 ClientRect.Deflate(
13824 60, 60); // Prevent the new window from being too close to the edge
13825 if (!ClientRect.Intersects(window_title_rect)) b_reset_pos = true;
13826
13827#endif
13828
13829 if (b_reset_pos) g_pais_query_dialog_active->Move(50, 200);
13830
13831 } else {
13832 g_pais_query_dialog_active->SetMMSI(mmsi);
13833 g_pais_query_dialog_active->UpdateText();
13834 }
13835
13836 g_pais_query_dialog_active->Show();
13837}
13838
13839void ChartCanvas::ToggleCanvasQuiltMode() {
13840 bool cur_mode = GetQuiltMode();
13841
13842 if (!GetQuiltMode())
13843 SetQuiltMode(true);
13844 else if (GetQuiltMode()) {
13845 SetQuiltMode(false);
13846 g_sticky_chart = GetQuiltReferenceChartIndex();
13847 }
13848
13849 if (cur_mode != GetQuiltMode()) {
13850 SetupCanvasQuiltMode();
13851 DoCanvasUpdate();
13852 InvalidateGL();
13853 Refresh();
13854 }
13855 // TODO What to do about this?
13856 // g_bQuiltEnable = GetQuiltMode();
13857
13858 // Recycle the S52 PLIB so that vector charts will flush caches and re-render
13859 if (ps52plib) ps52plib->GenerateStateHash();
13860
13861 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
13862 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
13863}
13864
13865void ChartCanvas::DoCanvasStackDelta(int direction) {
13866 if (!GetQuiltMode()) {
13867 int current_stack_index = GetpCurrentStack()->CurrentStackEntry;
13868 if ((current_stack_index + direction) >= GetpCurrentStack()->nEntry) return;
13869 if ((current_stack_index + direction) < 0) return;
13870
13871 if (m_bpersistent_quilt /*&& g_bQuiltEnable*/) {
13872 int new_dbIndex =
13873 GetpCurrentStack()->GetDBIndex(current_stack_index + direction);
13874
13875 if (IsChartQuiltableRef(new_dbIndex)) {
13876 ToggleCanvasQuiltMode();
13877 SelectQuiltRefdbChart(new_dbIndex);
13878 m_bpersistent_quilt = false;
13879 }
13880 } else {
13881 SelectChartFromStack(current_stack_index + direction);
13882 }
13883 } else {
13884 std::vector<int> piano_chart_index_array =
13885 GetQuiltExtendedStackdbIndexArray();
13886 int refdb = GetQuiltRefChartdbIndex();
13887
13888 // Find the ref chart in the stack
13889 int current_index = -1;
13890 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
13891 if (refdb == piano_chart_index_array[i]) {
13892 current_index = i;
13893 break;
13894 }
13895 }
13896 if (current_index == -1) return;
13897
13898 const ChartTableEntry &ctet = ChartData->GetChartTableEntry(refdb);
13899 int target_family = ctet.GetChartFamily();
13900
13901 int new_index = -1;
13902 int check_index = current_index + direction;
13903 bool found = false;
13904 int check_dbIndex = -1;
13905 int new_dbIndex = -1;
13906
13907 // When quilted. switch within the same chart family
13908 while (!found &&
13909 (unsigned int)check_index < piano_chart_index_array.size() &&
13910 (check_index >= 0)) {
13911 check_dbIndex = piano_chart_index_array[check_index];
13912 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
13913 if (target_family == cte.GetChartFamily()) {
13914 found = true;
13915 new_index = check_index;
13916 new_dbIndex = check_dbIndex;
13917 break;
13918 }
13919
13920 check_index += direction;
13921 }
13922
13923 if (!found) return;
13924
13925 if (!IsChartQuiltableRef(new_dbIndex)) {
13926 ToggleCanvasQuiltMode();
13927 SelectdbChart(new_dbIndex);
13928 m_bpersistent_quilt = true;
13929 } else {
13930 SelectQuiltRefChart(new_index);
13931 }
13932 }
13933
13934 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
13935 // (checkmarks etc)
13936 SetQuiltChartHiLiteIndex(-1);
13937
13938 ReloadVP();
13939}
13940
13941//--------------------------------------------------------------------------------------------------------
13942//
13943// Toolbar support
13944//
13945//--------------------------------------------------------------------------------------------------------
13946
13947void ChartCanvas::OnToolLeftClick(wxCommandEvent &event) {
13948 // Handle the per-canvas toolbar clicks here
13949
13950 switch (event.GetId()) {
13951 case ID_ZOOMIN: {
13952 ZoomCanvasSimple(g_plus_minus_zoom_factor);
13953 break;
13954 }
13955
13956 case ID_ZOOMOUT: {
13957 ZoomCanvasSimple(1.0 / g_plus_minus_zoom_factor);
13958 break;
13959 }
13960
13961 case ID_STKUP:
13962 DoCanvasStackDelta(1);
13963 DoCanvasUpdate();
13964 break;
13965
13966 case ID_STKDN:
13967 DoCanvasStackDelta(-1);
13968 DoCanvasUpdate();
13969 break;
13970
13971 case ID_FOLLOW: {
13972 TogglebFollow();
13973 break;
13974 }
13975
13976 case ID_CURRENT: {
13977 ShowCurrents(!GetbShowCurrent());
13978 ReloadVP();
13979 Refresh(false);
13980 break;
13981 }
13982
13983 case ID_TIDE: {
13984 ShowTides(!GetbShowTide());
13985 ReloadVP();
13986 Refresh(false);
13987 break;
13988 }
13989
13990 case ID_ROUTE: {
13991 if (0 == m_routeState) {
13992 StartRoute();
13993 } else {
13994 FinishRoute();
13995 }
13996
13997#ifdef __ANDROID__
13998 androidSetRouteAnnunciator(m_routeState == 1);
13999#endif
14000 break;
14001 }
14002
14003 case ID_AIS: {
14004 SetAISCanvasDisplayStyle(-1);
14005 break;
14006 }
14007
14008 default:
14009 break;
14010 }
14011
14012 // And then let gFrame handle the rest....
14013 event.Skip();
14014}
14015
14016void ChartCanvas::SetShowAIS(bool show) {
14017 m_bShowAIS = show;
14018 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14019 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14020}
14021
14022void ChartCanvas::SetAttenAIS(bool show) {
14023 m_bShowAISScaled = show;
14024 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14025 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14026}
14027
14028void ChartCanvas::SetAISCanvasDisplayStyle(int StyleIndx) {
14029 // make some arrays to hold the dfferences between cycle steps
14030 // show all, scaled, hide all
14031 bool bShowAIS_Array[3] = {true, true, false};
14032 bool bShowScaled_Array[3] = {false, true, true};
14033 wxString ToolShortHelp_Array[3] = {_("Show all AIS Targets"),
14034 _("Attenuate less critical AIS targets"),
14035 _("Hide AIS Targets")};
14036 wxString iconName_Array[3] = {"AIS", "AIS_Suppressed", "AIS_Disabled"};
14037 int ArraySize = 3;
14038 int AIS_Toolbar_Switch = 0;
14039 if (StyleIndx == -1) { // -1 means coming from toolbar button
14040 // find current state of switch
14041 for (int i = 1; i < ArraySize; i++) {
14042 if ((bShowAIS_Array[i] == m_bShowAIS) &&
14043 (bShowScaled_Array[i] == m_bShowAISScaled))
14044 AIS_Toolbar_Switch = i;
14045 }
14046 AIS_Toolbar_Switch++; // we did click so continu with next item
14047 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch == 1))
14048 AIS_Toolbar_Switch++;
14049
14050 } else { // coming from menu bar.
14051 AIS_Toolbar_Switch = StyleIndx;
14052 }
14053 // make sure we are not above array
14054 if (AIS_Toolbar_Switch >= ArraySize) AIS_Toolbar_Switch = 0;
14055
14056 int AIS_Toolbar_Switch_Next =
14057 AIS_Toolbar_Switch + 1; // Find out what will happen at next click
14058 if ((!g_bAllowShowScaled) && (AIS_Toolbar_Switch_Next == 1))
14059 AIS_Toolbar_Switch_Next++;
14060 if (AIS_Toolbar_Switch_Next >= ArraySize)
14061 AIS_Toolbar_Switch_Next = 0; // If at end of cycle start at 0
14062
14063 // Set found values to global and member variables
14064 m_bShowAIS = bShowAIS_Array[AIS_Toolbar_Switch];
14065 m_bShowAISScaled = bShowScaled_Array[AIS_Toolbar_Switch];
14066}
14067
14068void ChartCanvas::TouchAISToolActive() {}
14069
14070void ChartCanvas::UpdateAISTBTool() {}
14071
14072//---------------------------------------------------------------------------------
14073//
14074// Compass/GPS status icon support
14075//
14076//---------------------------------------------------------------------------------
14077
14078void ChartCanvas::UpdateGPSCompassStatusBox(bool b_force_new) {
14079 // Look for change in overlap or positions
14080 bool b_update = false;
14081 int cc1_edge_comp = 2;
14082 wxRect rect = m_Compass->GetRect();
14083 wxSize parent_size = GetSize();
14084
14085 parent_size *= m_displayScale;
14086
14087 // check to see if it would overlap if it was in its home position (upper
14088 // right)
14089 wxPoint compass_pt(parent_size.x - rect.width - cc1_edge_comp,
14090 g_StyleManager->GetCurrentStyle()->GetCompassYOffset());
14091 wxRect compass_rect(compass_pt, rect.GetSize());
14092
14093 m_Compass->Move(compass_pt);
14094
14095 if (m_Compass && m_Compass->IsShown())
14096 m_Compass->UpdateStatus(b_force_new | b_update);
14097
14098 double scaler = g_Platform->GetCompassScaleFactor(g_GUIScaleFactor);
14099 scaler = wxMax(scaler, 1.0);
14100 wxPoint note_point = wxPoint(
14101 parent_size.x - (scaler * 20 * wxWindow::GetCharWidth()), compass_rect.y);
14102 if (m_notification_button) {
14103 m_notification_button->Move(note_point);
14104 m_notification_button->UpdateStatus();
14105 }
14106
14107 if (b_force_new | b_update) Refresh();
14108}
14109
14110void ChartCanvas::SelectChartFromStack(int index, bool bDir,
14111 ChartTypeEnum New_Type,
14112 ChartFamilyEnum New_Family) {
14113 if (!GetpCurrentStack()) return;
14114 if (!ChartData) return;
14115
14116 if (index < GetpCurrentStack()->nEntry) {
14117 // Open the new chart
14118 ChartBase *pTentative_Chart;
14119 pTentative_Chart = ChartData->OpenStackChartConditional(
14120 GetpCurrentStack(), index, bDir, New_Type, New_Family);
14121
14122 if (pTentative_Chart) {
14123 if (m_singleChart) m_singleChart->Deactivate();
14124
14125 m_singleChart = pTentative_Chart;
14126 m_singleChart->Activate();
14127
14128 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14129 GetpCurrentStack(), m_singleChart->GetFullPath());
14130 }
14131
14132 // Setup the view
14133 double zLat, zLon;
14134 if (m_bFollow) {
14135 zLat = gLat;
14136 zLon = gLon;
14137 } else {
14138 zLat = m_vLat;
14139 zLon = m_vLon;
14140 }
14141
14142 double best_scale_ppm = GetBestVPScale(m_singleChart);
14143 double rotation = GetVPRotation();
14144 double oldskew = GetVPSkew();
14145 double newskew = m_singleChart->GetChartSkew() * PI / 180.0;
14146
14147 if (!g_bskew_comp && (GetUpMode() == NORTH_UP_MODE)) {
14148 if (fabs(oldskew) > 0.0001) rotation = 0.0;
14149 if (fabs(newskew) > 0.0001) rotation = newskew;
14150 }
14151
14152 SetViewPoint(zLat, zLon, best_scale_ppm, newskew, rotation);
14153
14154 UpdateGPSCompassStatusBox(true); // Pick up the rotation
14155 }
14156
14157 // refresh Piano
14158 int idx = GetpCurrentStack()->GetCurrentEntrydbIndex();
14159 if (idx < 0) return;
14160
14161 std::vector<int> piano_active_chart_index_array;
14162 piano_active_chart_index_array.push_back(
14163 GetpCurrentStack()->GetCurrentEntrydbIndex());
14164 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14165}
14166
14167void ChartCanvas::SelectdbChart(int dbindex) {
14168 if (!GetpCurrentStack()) return;
14169 if (!ChartData) return;
14170
14171 if (dbindex >= 0) {
14172 // Open the new chart
14173 ChartBase *pTentative_Chart;
14174 pTentative_Chart = ChartData->OpenChartFromDB(dbindex, FULL_INIT);
14175
14176 if (pTentative_Chart) {
14177 if (m_singleChart) m_singleChart->Deactivate();
14178
14179 m_singleChart = pTentative_Chart;
14180 m_singleChart->Activate();
14181
14182 GetpCurrentStack()->CurrentStackEntry = ChartData->GetStackEntry(
14183 GetpCurrentStack(), m_singleChart->GetFullPath());
14184 }
14185
14186 // Setup the view
14187 double zLat, zLon;
14188 if (m_bFollow) {
14189 zLat = gLat;
14190 zLon = gLon;
14191 } else {
14192 zLat = m_vLat;
14193 zLon = m_vLon;
14194 }
14195
14196 double best_scale_ppm = GetBestVPScale(m_singleChart);
14197
14198 if (m_singleChart)
14199 SetViewPoint(zLat, zLon, best_scale_ppm,
14200 m_singleChart->GetChartSkew() * PI / 180., GetVPRotation());
14201
14202 // SetChartUpdatePeriod( );
14203
14204 // UpdateGPSCompassStatusBox(); // Pick up the rotation
14205 }
14206
14207 // TODO refresh_Piano();
14208}
14209
14210void ChartCanvas::selectCanvasChartDisplay(int type, int family) {
14211 double target_scale = GetVP().view_scale_ppm;
14212
14213 if (!GetQuiltMode()) {
14214 if (GetpCurrentStack()) {
14215 int stack_index = -1;
14216 for (int i = 0; i < GetpCurrentStack()->nEntry; i++) {
14217 int check_dbIndex = GetpCurrentStack()->GetDBIndex(i);
14218 if (check_dbIndex < 0) continue;
14219 const ChartTableEntry &cte =
14220 ChartData->GetChartTableEntry(check_dbIndex);
14221 if (type == cte.GetChartType()) {
14222 stack_index = i;
14223 break;
14224 } else if (family == cte.GetChartFamily()) {
14225 stack_index = i;
14226 break;
14227 }
14228 }
14229
14230 if (stack_index >= 0) {
14231 SelectChartFromStack(stack_index);
14232 }
14233 }
14234 } else {
14235 int sel_dbIndex = -1;
14236 std::vector<int> piano_chart_index_array =
14237 GetQuiltExtendedStackdbIndexArray();
14238 for (unsigned int i = 0; i < piano_chart_index_array.size(); i++) {
14239 int check_dbIndex = piano_chart_index_array[i];
14240 const ChartTableEntry &cte = ChartData->GetChartTableEntry(check_dbIndex);
14241 if (type == cte.GetChartType()) {
14242 if (IsChartQuiltableRef(check_dbIndex)) {
14243 sel_dbIndex = check_dbIndex;
14244 break;
14245 }
14246 } else if (family == cte.GetChartFamily()) {
14247 if (IsChartQuiltableRef(check_dbIndex)) {
14248 sel_dbIndex = check_dbIndex;
14249 break;
14250 }
14251 }
14252 }
14253
14254 if (sel_dbIndex >= 0) {
14255 SelectQuiltRefdbChart(sel_dbIndex, false); // no autoscale
14256 // Re-qualify the quilt reference chart selection
14257 AdjustQuiltRefChart();
14258 }
14259
14260 // Now reset the scale to the target...
14261 SetVPScale(target_scale);
14262 }
14263
14264 SetQuiltChartHiLiteIndex(-1);
14265
14266 ReloadVP();
14267}
14268
14269bool ChartCanvas::IsTileOverlayIndexInYesShow(int index) {
14270 return std::find(m_tile_yesshow_index_array.begin(),
14271 m_tile_yesshow_index_array.end(),
14272 index) != m_tile_yesshow_index_array.end();
14273}
14274
14275bool ChartCanvas::IsTileOverlayIndexInNoShow(int index) {
14276 return std::find(m_tile_noshow_index_array.begin(),
14277 m_tile_noshow_index_array.end(),
14278 index) != m_tile_noshow_index_array.end();
14279}
14280
14281void ChartCanvas::AddTileOverlayIndexToNoShow(int index) {
14282 if (std::find(m_tile_noshow_index_array.begin(),
14283 m_tile_noshow_index_array.end(),
14284 index) == m_tile_noshow_index_array.end()) {
14285 m_tile_noshow_index_array.push_back(index);
14286 }
14287}
14288
14289//-------------------------------------------------------------------------------------------------------
14290//
14291// Piano support
14292//
14293//-------------------------------------------------------------------------------------------------------
14294
14295void ChartCanvas::HandlePianoClick(
14296 int selected_index, const std::vector<int> &selected_dbIndex_array) {
14297 if (g_options && g_options->IsShown())
14298 return; // Piano might be invalid due to chartset updates.
14299 if (!m_pCurrentStack) return;
14300 if (!ChartData) return;
14301
14302 // stop movement or on slow computer we may get something like :
14303 // zoom out with the wheel (timer is set)
14304 // quickly click and display a chart, which may zoom in
14305 // but the delayed timer fires first and it zooms out again!
14306 StopMovement();
14307
14308 // When switching by piano key click, we may appoint the new target chart to
14309 // be any chart in the composite array.
14310 // As an improvement to UX, find the chart that is "closest" to the current
14311 // vp,
14312 // and select that chart. This will cause a jump to the centroid of that
14313 // chart
14314
14315 double distance = 25000; // RTW
14316 int closest_index = -1;
14317 for (int chart_index : selected_dbIndex_array) {
14318 const ChartTableEntry &cte = ChartData->GetChartTableEntry(chart_index);
14319 double chart_lat = (cte.GetLatMax() + cte.GetLatMin()) / 2;
14320 double chart_lon = (cte.GetLonMax() + cte.GetLonMin()) / 2;
14321
14322 // measure distance as Manhattan style
14323 double test_distance = abs(m_vLat - chart_lat) + abs(m_vLon - chart_lon);
14324 if (test_distance < distance) {
14325 distance = test_distance;
14326 closest_index = chart_index;
14327 }
14328 }
14329
14330 int selected_dbIndex = selected_dbIndex_array[0];
14331 if (closest_index >= 0) selected_dbIndex = closest_index;
14332
14333 if (!GetQuiltMode()) {
14334 if (m_bpersistent_quilt /* && g_bQuiltEnable*/) {
14335 if (IsChartQuiltableRef(selected_dbIndex)) {
14336 ToggleCanvasQuiltMode();
14337 SelectQuiltRefdbChart(selected_dbIndex);
14338 m_bpersistent_quilt = false;
14339 } else {
14340 SelectChartFromStack(selected_index);
14341 }
14342 } else {
14343 SelectChartFromStack(selected_index);
14344 g_sticky_chart = selected_dbIndex;
14345 }
14346
14347 if (m_singleChart)
14348 GetVP().SetProjectionType(m_singleChart->GetChartProjectionType());
14349 } else {
14350 // Handle MBTiles overlays first
14351 // Left click simply toggles the noshow array index entry
14352 if (CHART_TYPE_MBTILES == ChartData->GetDBChartType(selected_dbIndex)) {
14353 bool bfound = false;
14354 for (unsigned int i = 0; i < m_tile_noshow_index_array.size(); i++) {
14355 if (m_tile_noshow_index_array[i] ==
14356 selected_dbIndex) { // chart is in the noshow list
14357 m_tile_noshow_index_array.erase(m_tile_noshow_index_array.begin() +
14358 i); // erase it
14359 bfound = true;
14360 break;
14361 }
14362 }
14363 if (!bfound) {
14364 m_tile_noshow_index_array.push_back(selected_dbIndex);
14365 }
14366
14367 // If not already present, add this tileset to the "yes_show" array.
14368 if (!IsTileOverlayIndexInYesShow(selected_dbIndex))
14369 m_tile_yesshow_index_array.push_back(selected_dbIndex);
14370 }
14371
14372 else {
14373 if (IsChartQuiltableRef(selected_dbIndex)) {
14374 // if( ChartData ) ChartData->PurgeCache();
14375
14376 // If the chart is a vector chart, and of very large scale,
14377 // then we had better set the new scale directly to avoid excessive
14378 // underzoom on, eg, Inland ENCs
14379 bool set_scale = false;
14380 if (CHART_TYPE_S57 == ChartData->GetDBChartType(selected_dbIndex)) {
14381 if (ChartData->GetDBChartScale(selected_dbIndex) < 5000) {
14382 set_scale = true;
14383 }
14384 }
14385
14386 if (!set_scale) {
14387 SelectQuiltRefdbChart(selected_dbIndex, true); // autoscale
14388 } else {
14389 SelectQuiltRefdbChart(selected_dbIndex, false); // no autoscale
14390
14391 // Adjust scale so that the selected chart is underzoomed/overzoomed
14392 // by a controlled amount
14393 ChartBase *pc =
14394 ChartData->OpenChartFromDB(selected_dbIndex, FULL_INIT);
14395 if (pc) {
14396 double proposed_scale_onscreen =
14398
14399 if (g_bPreserveScaleOnX) {
14400 proposed_scale_onscreen =
14401 wxMin(proposed_scale_onscreen,
14402 100 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14403 GetCanvasWidth()));
14404 } else {
14405 proposed_scale_onscreen =
14406 wxMin(proposed_scale_onscreen,
14407 20 * pc->GetNormalScaleMax(GetCanvasScaleFactor(),
14408 GetCanvasWidth()));
14409
14410 proposed_scale_onscreen =
14411 wxMax(proposed_scale_onscreen,
14412 pc->GetNormalScaleMin(GetCanvasScaleFactor(),
14414 }
14415
14416 SetVPScale(GetCanvasScaleFactor() / proposed_scale_onscreen);
14417 }
14418 }
14419 } else {
14420 ToggleCanvasQuiltMode();
14421 SelectdbChart(selected_dbIndex);
14422 m_bpersistent_quilt = true;
14423 }
14424 }
14425 }
14426
14427 SetQuiltChartHiLiteIndex(-1);
14428 gFrame->UpdateGlobalMenuItems(); // update the state of the menu items
14429 // (checkmarks etc)
14430 HideChartInfoWindow();
14431 DoCanvasUpdate();
14432 ReloadVP(); // Pick up the new selections
14433}
14434
14435void ChartCanvas::HandlePianoRClick(
14436 int x, int y, int selected_index,
14437 const std::vector<int> &selected_dbIndex_array) {
14438 if (g_options && g_options->IsShown())
14439 return; // Piano might be invalid due to chartset updates.
14440 if (!GetpCurrentStack()) return;
14441
14442 PianoPopupMenu(x, y, selected_index, selected_dbIndex_array);
14443 UpdateCanvasControlBar();
14444
14445 SetQuiltChartHiLiteIndex(-1);
14446}
14447
14448void ChartCanvas::HandlePianoRollover(
14449 int selected_index, const std::vector<int> &selected_dbIndex_array,
14450 int n_charts, int scale) {
14451 if (g_options && g_options->IsShown())
14452 return; // Piano might be invalid due to chartset updates.
14453 if (!GetpCurrentStack()) return;
14454 if (!ChartData) return;
14455
14456 if (ChartData->IsBusy()) return;
14457
14458 wxPoint key_location = m_Piano->GetKeyOrigin(selected_index);
14459
14460 if (!GetQuiltMode()) {
14461 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14462 } else {
14463 // Select the correct vector
14464 std::vector<int> piano_chart_index_array;
14465 if (m_Piano->GetPianoMode() == PIANO_MODE_LEGACY) {
14466 piano_chart_index_array = GetQuiltExtendedStackdbIndexArray();
14467 if ((GetpCurrentStack()->nEntry > 1) ||
14468 (piano_chart_index_array.size() >= 1)) {
14469 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14470
14471 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14472 ReloadVP(false); // no VP adjustment allowed
14473 } else if (GetpCurrentStack()->nEntry == 1) {
14474 const ChartTableEntry &cte =
14475 ChartData->GetChartTableEntry(GetpCurrentStack()->GetDBIndex(0));
14476 if (CHART_TYPE_CM93COMP != cte.GetChartType()) {
14477 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14478 ReloadVP(false);
14479 } else if ((-1 == selected_index) &&
14480 (0 == selected_dbIndex_array.size())) {
14481 ShowChartInfoWindow(key_location.x, -1);
14482 }
14483 }
14484 } else {
14485 piano_chart_index_array = GetQuiltFullScreendbIndexArray();
14486
14487 if ((GetpCurrentStack()->nEntry > 1) ||
14488 (piano_chart_index_array.size() >= 1)) {
14489 if (n_charts > 1)
14490 ShowCompositeInfoWindow(key_location.x, n_charts, scale,
14491 selected_dbIndex_array);
14492 else if (n_charts == 1)
14493 ShowChartInfoWindow(key_location.x, selected_dbIndex_array[0]);
14494
14495 SetQuiltChartHiLiteIndexArray(selected_dbIndex_array);
14496 ReloadVP(false); // no VP adjustment allowed
14497 }
14498 }
14499 }
14500}
14501
14502void ChartCanvas::ClearPianoRollover() {
14503 ClearQuiltChartHiLiteIndexArray();
14504 ShowChartInfoWindow(0, -1);
14505 std::vector<int> vec;
14506 ShowCompositeInfoWindow(0, 0, 0, vec);
14507 ReloadVP(false);
14508}
14509
14510void ChartCanvas::UpdateCanvasControlBar() {
14511 if (m_pianoFrozen) return;
14512
14513 if (!GetpCurrentStack()) return;
14514 if (!ChartData) return;
14515 if (!g_bShowChartBar) return;
14516
14517 int sel_type = -1;
14518 int sel_family = -1;
14519
14520 std::vector<int> piano_chart_index_array;
14521 std::vector<int> empty_piano_chart_index_array;
14522
14523 wxString old_hash = m_Piano->GetStoredHash();
14524
14525 if (GetQuiltMode()) {
14526 m_Piano->SetKeyArray(GetQuiltExtendedStackdbIndexArray(),
14527 GetQuiltFullScreendbIndexArray());
14528
14529 std::vector<int> piano_active_chart_index_array =
14530 GetQuiltCandidatedbIndexArray();
14531 m_Piano->SetActiveKeyArray(piano_active_chart_index_array);
14532
14533 std::vector<int> piano_eclipsed_chart_index_array =
14534 GetQuiltEclipsedStackdbIndexArray();
14535 m_Piano->SetEclipsedIndexArray(piano_eclipsed_chart_index_array);
14536
14537 m_Piano->SetNoshowIndexArray(m_quilt_noshow_index_array);
14538 m_Piano->AddNoshowIndexArray(m_tile_noshow_index_array);
14539
14540 sel_type = ChartData->GetDBChartType(GetQuiltReferenceChartIndex());
14541 sel_family = ChartData->GetDBChartFamily(GetQuiltReferenceChartIndex());
14542 } else {
14543 piano_chart_index_array = ChartData->GetCSArray(GetpCurrentStack());
14544 m_Piano->SetKeyArray(piano_chart_index_array, piano_chart_index_array);
14545 // TODO refresh_Piano();
14546
14547 if (m_singleChart) {
14548 sel_type = m_singleChart->GetChartType();
14549 sel_family = m_singleChart->GetChartFamily();
14550 }
14551 }
14552
14553 // Set up the TMerc and Skew arrays
14554 std::vector<int> piano_skew_chart_index_array;
14555 std::vector<int> piano_tmerc_chart_index_array;
14556 std::vector<int> piano_poly_chart_index_array;
14557
14558 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14559 const ChartTableEntry &ctei =
14560 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14561 double skew_norm = ctei.GetChartSkew();
14562 if (skew_norm > 180.) skew_norm -= 360.;
14563
14564 if (ctei.GetChartProjectionType() == PROJECTION_TRANSVERSE_MERCATOR)
14565 piano_tmerc_chart_index_array.push_back(piano_chart_index_array[ino]);
14566
14567 // Polyconic skewed charts should show as skewed
14568 else if (ctei.GetChartProjectionType() == PROJECTION_POLYCONIC) {
14569 if (fabs(skew_norm) > 1.)
14570 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14571 else
14572 piano_poly_chart_index_array.push_back(piano_chart_index_array[ino]);
14573 } else if (fabs(skew_norm) > 1.)
14574 piano_skew_chart_index_array.push_back(piano_chart_index_array[ino]);
14575 }
14576 m_Piano->SetSkewIndexArray(piano_skew_chart_index_array);
14577 m_Piano->SetTmercIndexArray(piano_tmerc_chart_index_array);
14578 m_Piano->SetPolyIndexArray(piano_poly_chart_index_array);
14579
14580 wxString new_hash = m_Piano->GenerateAndStoreNewHash();
14581 if (new_hash != old_hash) {
14582 m_Piano->FormatKeys();
14583 HideChartInfoWindow();
14584 m_Piano->ResetRollover();
14585 SetQuiltChartHiLiteIndex(-1);
14586 m_brepaint_piano = true;
14587 }
14588
14589 // Create a bitmask int that describes what Family/Type of charts are shown in
14590 // the bar, and notify the platform.
14591 int mask = 0;
14592 for (unsigned int ino = 0; ino < piano_chart_index_array.size(); ino++) {
14593 const ChartTableEntry &ctei =
14594 ChartData->GetChartTableEntry(piano_chart_index_array[ino]);
14595 ChartFamilyEnum e = (ChartFamilyEnum)ctei.GetChartFamily();
14596 ChartTypeEnum t = (ChartTypeEnum)ctei.GetChartType();
14597 if (e == CHART_FAMILY_RASTER) mask |= 1;
14598 if (e == CHART_FAMILY_VECTOR) {
14599 if (t == CHART_TYPE_CM93COMP)
14600 mask |= 4;
14601 else
14602 mask |= 2;
14603 }
14604 }
14605
14606 wxString s_indicated;
14607 if (sel_type == CHART_TYPE_CM93COMP)
14608 s_indicated = "cm93";
14609 else {
14610 if (sel_family == CHART_FAMILY_RASTER)
14611 s_indicated = "raster";
14612 else if (sel_family == CHART_FAMILY_VECTOR)
14613 s_indicated = "vector";
14614 }
14615
14616 g_Platform->setChartTypeMaskSel(mask, s_indicated);
14617}
14618
14619void ChartCanvas::FormatPianoKeys() { m_Piano->FormatKeys(); }
14620
14621void ChartCanvas::PianoPopupMenu(
14622 int x, int y, int selected_index,
14623 const std::vector<int> &selected_dbIndex_array) {
14624 if (!GetpCurrentStack()) return;
14625
14626 // No context menu if quilting is disabled
14627 if (!GetQuiltMode()) return;
14628
14629 m_piano_ctx_menu = new wxMenu();
14630
14631 if (m_Piano->GetPianoMode() == PIANO_MODE_COMPOSITE) {
14632 // m_piano_ctx_menu->Append(ID_PIANO_EXPAND_PIANO, _("Legacy chartbar"));
14633 // Connect(ID_PIANO_EXPAND_PIANO, wxEVT_COMMAND_MENU_SELECTED,
14634 // wxCommandEventHandler(ChartCanvas::OnPianoMenuExpandChartbar));
14635 } else {
14636 // m_piano_ctx_menu->Append(ID_PIANO_CONTRACT_PIANO, _("Fullscreen
14637 // chartbar")); Connect(ID_PIANO_CONTRACT_PIANO,
14638 // wxEVT_COMMAND_MENU_SELECTED,
14639 // wxCommandEventHandler(ChartCanvas::OnPianoMenuContractChartbar));
14640
14641 menu_selected_dbIndex = selected_dbIndex_array[0];
14642 menu_selected_index = selected_index;
14643
14644 // Search the no-show array
14645 bool b_is_in_noshow = false;
14646 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14647 if (m_quilt_noshow_index_array[i] ==
14648 menu_selected_dbIndex) // chart is in the noshow list
14649 {
14650 b_is_in_noshow = true;
14651 break;
14652 }
14653 }
14654
14655 if (b_is_in_noshow) {
14656 m_piano_ctx_menu->Append(ID_PIANO_ENABLE_QUILT_CHART,
14657 _("Show This Chart"));
14658 Connect(ID_PIANO_ENABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14659 wxCommandEventHandler(ChartCanvas::OnPianoMenuEnableChart));
14660 } else if (GetpCurrentStack()->nEntry > 1) {
14661 m_piano_ctx_menu->Append(ID_PIANO_DISABLE_QUILT_CHART,
14662 _("Hide This Chart"));
14663 Connect(ID_PIANO_DISABLE_QUILT_CHART, wxEVT_COMMAND_MENU_SELECTED,
14664 wxCommandEventHandler(ChartCanvas::OnPianoMenuDisableChart));
14665 }
14666 }
14667
14668 wxPoint pos = wxPoint(x, y - 30);
14669
14670 // Invoke the drop-down menu
14671 if (m_piano_ctx_menu->GetMenuItems().GetCount())
14672 PopupMenu(m_piano_ctx_menu, pos);
14673
14674 delete m_piano_ctx_menu;
14675 m_piano_ctx_menu = NULL;
14676
14677 HideChartInfoWindow();
14678 m_Piano->ResetRollover();
14679
14680 SetQuiltChartHiLiteIndex(-1);
14681 ClearQuiltChartHiLiteIndexArray();
14682
14683 ReloadVP();
14684}
14685
14686void ChartCanvas::OnPianoMenuEnableChart(wxCommandEvent &event) {
14687 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14688 if (m_quilt_noshow_index_array[i] ==
14689 menu_selected_dbIndex) // chart is in the noshow list
14690 {
14691 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14692 break;
14693 }
14694 }
14695}
14696
14697void ChartCanvas::OnPianoMenuDisableChart(wxCommandEvent &event) {
14698 if (!GetpCurrentStack()) return;
14699 if (!ChartData) return;
14700
14701 RemoveChartFromQuilt(menu_selected_dbIndex);
14702
14703 // It could happen that the chart being disabled is the reference
14704 // chart....
14705 if (menu_selected_dbIndex == GetQuiltRefChartdbIndex()) {
14706 int type = ChartData->GetDBChartType(menu_selected_dbIndex);
14707
14708 int i = menu_selected_index + 1; // select next smaller scale chart
14709 bool b_success = false;
14710 while (i < GetpCurrentStack()->nEntry - 1) {
14711 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14712 if (type == ChartData->GetDBChartType(dbIndex)) {
14713 SelectQuiltRefChart(i);
14714 b_success = true;
14715 break;
14716 }
14717 i++;
14718 }
14719
14720 // If that did not work, try to select the next larger scale compatible
14721 // chart
14722 if (!b_success) {
14723 i = menu_selected_index - 1;
14724 while (i > 0) {
14725 int dbIndex = GetpCurrentStack()->GetDBIndex(i);
14726 if (type == ChartData->GetDBChartType(dbIndex)) {
14727 SelectQuiltRefChart(i);
14728 b_success = true;
14729 break;
14730 }
14731 i--;
14732 }
14733 }
14734 }
14735}
14736
14737void ChartCanvas::RemoveChartFromQuilt(int dbIndex) {
14738 // Remove the item from the list (if it appears) to avoid multiple addition
14739 for (unsigned int i = 0; i < m_quilt_noshow_index_array.size(); i++) {
14740 if (m_quilt_noshow_index_array[i] ==
14741 dbIndex) // chart is already in the noshow list
14742 {
14743 m_quilt_noshow_index_array.erase(m_quilt_noshow_index_array.begin() + i);
14744 break;
14745 }
14746 }
14747
14748 m_quilt_noshow_index_array.push_back(dbIndex);
14749}
14750
14751bool ChartCanvas::UpdateS52State() {
14752 bool retval = false;
14753
14754 if (ps52plib) {
14755 ps52plib->SetShowS57Text(m_encShowText);
14756 ps52plib->SetDisplayCategory((DisCat)m_encDisplayCategory);
14757 ps52plib->m_bShowSoundg = m_encShowDepth;
14758 ps52plib->m_bShowAtonText = m_encShowBuoyLabels;
14759 ps52plib->m_bShowLdisText = m_encShowLightDesc;
14760
14761 // Lights
14762 if (!m_encShowLights) // On, going off
14763 ps52plib->AddObjNoshow("LIGHTS");
14764 else // Off, going on
14765 ps52plib->RemoveObjNoshow("LIGHTS");
14766 ps52plib->SetLightsOff(!m_encShowLights);
14767 ps52plib->m_bExtendLightSectors = true;
14768
14769 // TODO ps52plib->m_bShowAtons = m_encShowBuoys;
14770 ps52plib->SetAnchorOn(m_encShowAnchor);
14771 ps52plib->SetQualityOfData(m_encShowDataQual);
14772 }
14773
14774 return retval;
14775}
14776
14777void ChartCanvas::SetShowENCDataQual(bool show) {
14778 m_encShowDataQual = show;
14779 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14780 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14781
14782 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14783}
14784
14785void ChartCanvas::SetShowENCText(bool show) {
14786 m_encShowText = show;
14787 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14788 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14789
14790 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14791}
14792
14793void ChartCanvas::SetENCDisplayCategory(int category) {
14794 m_encDisplayCategory = category;
14795 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14796}
14797
14798void ChartCanvas::SetShowENCDepth(bool show) {
14799 m_encShowDepth = show;
14800 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14801 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14802
14803 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14804}
14805
14806void ChartCanvas::SetShowENCLightDesc(bool show) {
14807 m_encShowLightDesc = show;
14808 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14809 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14810
14811 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14812}
14813
14814void ChartCanvas::SetShowENCBuoyLabels(bool show) {
14815 m_encShowBuoyLabels = show;
14816 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14817}
14818
14819void ChartCanvas::SetShowENCLights(bool show) {
14820 m_encShowLights = show;
14821 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14822 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14823
14824 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14825}
14826
14827void ChartCanvas::SetShowENCAnchor(bool show) {
14828 m_encShowAnchor = show;
14829 if (GetMUIBar() && GetMUIBar()->GetCanvasOptions())
14830 GetMUIBar()->GetCanvasOptions()->RefreshControlValues();
14831
14832 m_s52StateHash = 0; // Force a S52 PLIB re-configure
14833}
14834
14835wxRect ChartCanvas::GetMUIBarRect() {
14836 wxRect rv;
14837 if (m_muiBar) {
14838 rv = m_muiBar->GetRect();
14839 }
14840
14841 return rv;
14842}
14843
14844void ChartCanvas::RenderAlertMessage(wxDC &dc, const ViewPort &vp) {
14845 if (!GetAlertString().IsEmpty()) {
14846 wxFont *pfont = wxTheFontList->FindOrCreateFont(
14847 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
14848
14849 dc.SetFont(*pfont);
14850 dc.SetPen(*wxTRANSPARENT_PEN);
14851
14852 dc.SetBrush(wxColour(243, 229, 47));
14853 int w, h;
14854 dc.GetMultiLineTextExtent(GetAlertString(), &w, &h);
14855 h += 2;
14856 // int yp = vp.pix_height - 20 - h;
14857
14858 wxRect sbr = GetScaleBarRect();
14859 int xp = sbr.x + sbr.width + 10;
14860 int yp = (sbr.y + sbr.height) - h;
14861
14862 int wdraw = w + 10;
14863 dc.DrawRectangle(xp, yp, wdraw, h);
14864 dc.DrawLabel(GetAlertString(), wxRect(xp, yp, wdraw, h),
14865 wxALIGN_CENTRE_HORIZONTAL | wxALIGN_CENTRE_VERTICAL);
14866 }
14867}
14868
14869//--------------------------------------------------------------------------------------------------------
14870// Screen Brightness Control Support Routines
14871//
14872//--------------------------------------------------------------------------------------------------------
14873
14874#ifdef __UNIX__
14875#define BRIGHT_XCALIB
14876#define __OPCPN_USEICC__
14877#endif
14878
14879#ifdef __OPCPN_USEICC__
14880int CreateSimpleICCProfileFile(const char *file_name, double co_red,
14881 double co_green, double co_blue);
14882
14883wxString temp_file_name;
14884#endif
14885
14886#if 0
14887class ocpnCurtain: public wxDialog
14888{
14889 DECLARE_CLASS( ocpnCurtain )
14890 DECLARE_EVENT_TABLE()
14891
14892public:
14893 ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle );
14894 ~ocpnCurtain( );
14895 bool ProcessEvent(wxEvent& event);
14896
14897};
14898
14899IMPLEMENT_CLASS ( ocpnCurtain, wxDialog )
14900
14901BEGIN_EVENT_TABLE(ocpnCurtain, wxDialog)
14902END_EVENT_TABLE()
14903
14904ocpnCurtain::ocpnCurtain( wxWindow *parent, wxPoint position, wxSize size, long wstyle )
14905{
14906 wxDialog::Create( parent, -1, "ocpnCurtain", position, size, wxNO_BORDER | wxSTAY_ON_TOP );
14907}
14908
14909ocpnCurtain::~ocpnCurtain()
14910{
14911}
14912
14913bool ocpnCurtain::ProcessEvent(wxEvent& event)
14914{
14915 GetParent()->GetEventHandler()->SetEvtHandlerEnabled(true);
14916 return GetParent()->GetEventHandler()->ProcessEvent(event);
14917}
14918#endif
14919
14920#ifdef _WIN32
14921#include <windows.h>
14922
14923HMODULE hGDI32DLL;
14924typedef BOOL(WINAPI *SetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14925typedef BOOL(WINAPI *GetDeviceGammaRamp_ptr_type)(HDC hDC, LPVOID lpRampTable);
14926SetDeviceGammaRamp_ptr_type
14927 g_pSetDeviceGammaRamp; // the API entry points in the dll
14928GetDeviceGammaRamp_ptr_type g_pGetDeviceGammaRamp;
14929
14930WORD *g_pSavedGammaMap;
14931
14932#endif
14933
14934int InitScreenBrightness() {
14935#ifdef _WIN32
14936#ifdef ocpnUSE_GL
14937 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
14938 HDC hDC;
14939 BOOL bbr;
14940
14941 if (NULL == hGDI32DLL) {
14942 hGDI32DLL = LoadLibrary(TEXT("gdi32.dll"));
14943
14944 if (NULL != hGDI32DLL) {
14945 // Get the entry points of the required functions
14946 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
14947 hGDI32DLL, "SetDeviceGammaRamp");
14948 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
14949 hGDI32DLL, "GetDeviceGammaRamp");
14950
14951 // If the functions are not found, unload the DLL and return false
14952 if ((NULL == g_pSetDeviceGammaRamp) ||
14953 (NULL == g_pGetDeviceGammaRamp)) {
14954 FreeLibrary(hGDI32DLL);
14955 hGDI32DLL = NULL;
14956 return 0;
14957 }
14958 }
14959 }
14960
14961 // Interface is ready, so....
14962 // Get some storage
14963 if (!g_pSavedGammaMap) {
14964 g_pSavedGammaMap = (WORD *)malloc(3 * 256 * sizeof(WORD));
14965
14966 hDC = GetDC(NULL); // Get the full screen DC
14967 bbr = g_pGetDeviceGammaRamp(
14968 hDC, g_pSavedGammaMap); // Get the existing ramp table
14969 ReleaseDC(NULL, hDC); // Release the DC
14970 }
14971
14972 // On Windows hosts, try to adjust the registry to allow full range
14973 // setting of Gamma table This is an undocumented Windows hack.....
14974 wxRegKey *pRegKey = new wxRegKey(
14975 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows "
14976 "NT\\CurrentVersion\\ICM");
14977 if (!pRegKey->Exists()) pRegKey->Create();
14978 pRegKey->SetValue("GdiIcmGammaRange", 256);
14979
14980 g_brightness_init = true;
14981 return 1;
14982 }
14983#endif
14984
14985 {
14986 if (NULL == g_pcurtain) {
14987 if (gFrame->CanSetTransparent()) {
14988 // Build the curtain window
14989 g_pcurtain = new wxDialog(gFrame->GetPrimaryCanvas(), -1, "",
14990 wxPoint(0, 0), ::wxGetDisplaySize(),
14991 wxNO_BORDER | wxTRANSPARENT_WINDOW |
14992 wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14993
14994 // g_pcurtain = new ocpnCurtain(gFrame,
14995 // wxPoint(0,0),::wxGetDisplaySize(),
14996 // wxNO_BORDER | wxTRANSPARENT_WINDOW
14997 // |wxSTAY_ON_TOP | wxDIALOG_NO_PARENT);
14998
14999 g_pcurtain->Hide();
15000
15001 HWND hWnd = GetHwndOf(g_pcurtain);
15002 SetWindowLong(hWnd, GWL_EXSTYLE,
15003 GetWindowLong(hWnd, GWL_EXSTYLE) | ~WS_EX_APPWINDOW);
15004 g_pcurtain->SetBackgroundColour(wxColour(0, 0, 0));
15005 g_pcurtain->SetTransparent(0);
15006
15007 g_pcurtain->Maximize();
15008 g_pcurtain->Show();
15009
15010 // All of this is obtuse, but necessary for Windows...
15011 g_pcurtain->Enable();
15012 g_pcurtain->Disable();
15013
15014 gFrame->Disable();
15015 gFrame->Enable();
15016 // SetFocus();
15017 }
15018 }
15019 g_brightness_init = true;
15020
15021 return 1;
15022 }
15023#else
15024 // Look for "xcalib" application
15025 wxString cmd("xcalib -version");
15026
15027 wxArrayString output;
15028 long r = wxExecute(cmd, output);
15029 if (0 != r)
15030 wxLogMessage(
15031 " External application \"xcalib\" not found. Screen brightness "
15032 "not changed.");
15033
15034 g_brightness_init = true;
15035 return 0;
15036#endif
15037}
15038
15039int RestoreScreenBrightness() {
15040#ifdef _WIN32
15041
15042 if (g_pSavedGammaMap) {
15043 HDC hDC = GetDC(NULL); // Get the full screen DC
15044 g_pSetDeviceGammaRamp(hDC,
15045 g_pSavedGammaMap); // Restore the saved ramp table
15046 ReleaseDC(NULL, hDC); // Release the DC
15047
15048 free(g_pSavedGammaMap);
15049 g_pSavedGammaMap = NULL;
15050 }
15051
15052 if (g_pcurtain) {
15053 g_pcurtain->Close();
15054 g_pcurtain->Destroy();
15055 g_pcurtain = NULL;
15056 }
15057
15058 g_brightness_init = false;
15059 return 1;
15060
15061#endif
15062
15063#ifdef BRIGHT_XCALIB
15064 if (g_brightness_init) {
15065 wxString cmd;
15066 cmd = "xcalib -clear";
15067 wxExecute(cmd, wxEXEC_ASYNC);
15068 g_brightness_init = false;
15069 }
15070
15071 return 1;
15072#endif
15073
15074 return 0;
15075}
15076
15077// Set brightness. [0..100]
15078int SetScreenBrightness(int brightness) {
15079#ifdef _WIN32
15080
15081 // Under Windows, we use the SetDeviceGammaRamp function which exists in
15082 // some (most modern?) versions of gdi32.dll Load the required library dll,
15083 // if not already in place
15084#ifdef ocpnUSE_GL
15085 if (gFrame->GetPrimaryCanvas()->GetglCanvas() && g_bopengl) {
15086 if (g_pcurtain) {
15087 g_pcurtain->Close();
15088 g_pcurtain->Destroy();
15089 g_pcurtain = NULL;
15090 }
15091
15092 InitScreenBrightness();
15093
15094 if (NULL == hGDI32DLL) {
15095 // Unicode stuff.....
15096 wchar_t wdll_name[80];
15097 MultiByteToWideChar(0, 0, "gdi32.dll", -1, wdll_name, 80);
15098 LPCWSTR cstr = wdll_name;
15099
15100 hGDI32DLL = LoadLibrary(cstr);
15101
15102 if (NULL != hGDI32DLL) {
15103 // Get the entry points of the required functions
15104 g_pSetDeviceGammaRamp = (SetDeviceGammaRamp_ptr_type)GetProcAddress(
15105 hGDI32DLL, "SetDeviceGammaRamp");
15106 g_pGetDeviceGammaRamp = (GetDeviceGammaRamp_ptr_type)GetProcAddress(
15107 hGDI32DLL, "GetDeviceGammaRamp");
15108
15109 // If the functions are not found, unload the DLL and return false
15110 if ((NULL == g_pSetDeviceGammaRamp) ||
15111 (NULL == g_pGetDeviceGammaRamp)) {
15112 FreeLibrary(hGDI32DLL);
15113 hGDI32DLL = NULL;
15114 return 0;
15115 }
15116 }
15117 }
15118
15119 HDC hDC = GetDC(NULL); // Get the full screen DC
15120
15121 /*
15122 int cmcap = GetDeviceCaps(hDC, COLORMGMTCAPS);
15123 if (cmcap != CM_GAMMA_RAMP)
15124 {
15125 wxLogMessage(" Video hardware does not support brightness control by
15126 gamma ramp adjustment."); return false;
15127 }
15128 */
15129
15130 int increment = brightness * 256 / 100;
15131
15132 // Build the Gamma Ramp table
15133 WORD GammaTable[3][256];
15134
15135 int table_val = 0;
15136 for (int i = 0; i < 256; i++) {
15137 GammaTable[0][i] = r_gamma_mult * (WORD)table_val;
15138 GammaTable[1][i] = g_gamma_mult * (WORD)table_val;
15139 GammaTable[2][i] = b_gamma_mult * (WORD)table_val;
15140
15141 table_val += increment;
15142
15143 if (table_val > 65535) table_val = 65535;
15144 }
15145
15146 g_pSetDeviceGammaRamp(hDC, GammaTable); // Set the ramp table
15147 ReleaseDC(NULL, hDC); // Release the DC
15148
15149 return 1;
15150 }
15151#endif
15152
15153 {
15154 if (g_pSavedGammaMap) {
15155 HDC hDC = GetDC(NULL); // Get the full screen DC
15156 g_pSetDeviceGammaRamp(hDC,
15157 g_pSavedGammaMap); // Restore the saved ramp table
15158 ReleaseDC(NULL, hDC); // Release the DC
15159 }
15160
15161 if (brightness < 100) {
15162 if (NULL == g_pcurtain) InitScreenBrightness();
15163
15164 if (g_pcurtain) {
15165 int sbrite = wxMax(1, brightness);
15166 sbrite = wxMin(100, sbrite);
15167
15168 g_pcurtain->SetTransparent((100 - sbrite) * 256 / 100);
15169 }
15170 } else {
15171 if (g_pcurtain) {
15172 g_pcurtain->Close();
15173 g_pcurtain->Destroy();
15174 g_pcurtain = NULL;
15175 }
15176 }
15177
15178 return 1;
15179 }
15180
15181#endif
15182
15183#ifdef BRIGHT_XCALIB
15184
15185 if (!g_brightness_init) {
15186 last_brightness = 100;
15187 g_brightness_init = true;
15188 temp_file_name = wxFileName::CreateTempFileName("");
15189 InitScreenBrightness();
15190 }
15191
15192#ifdef __OPCPN_USEICC__
15193 // Create a dead simple temporary ICC profile file, with gamma ramps set as
15194 // desired, and then activate this temporary profile using xcalib <filename>
15195 if (!CreateSimpleICCProfileFile(
15196 (const char *)temp_file_name.fn_str(), brightness * r_gamma_mult,
15197 brightness * g_gamma_mult, brightness * b_gamma_mult)) {
15198 wxString cmd("xcalib ");
15199 cmd += temp_file_name;
15200
15201 wxExecute(cmd, wxEXEC_ASYNC);
15202 }
15203
15204#else
15205 // Or, use "xcalib -co" to set overall contrast value
15206 // This is not as nice, since the -co parameter wants to be a fraction of
15207 // the current contrast, and values greater than 100 are not allowed. As a
15208 // result, increases of contrast must do a "-clear" step first, which
15209 // produces objectionable flashing.
15210 if (brightness > last_brightness) {
15211 wxString cmd;
15212 cmd = "xcalib -clear";
15213 wxExecute(cmd, wxEXEC_ASYNC);
15214
15215 ::wxMilliSleep(10);
15216
15217 int brite_adj = wxMax(1, brightness);
15218 cmd.Printf("xcalib -co %2d -a", brite_adj);
15219 wxExecute(cmd, wxEXEC_ASYNC);
15220 } else {
15221 int brite_adj = wxMax(1, brightness);
15222 int factor = (brite_adj * 100) / last_brightness;
15223 factor = wxMax(1, factor);
15224 wxString cmd;
15225 cmd.Printf("xcalib -co %2d -a", factor);
15226 wxExecute(cmd, wxEXEC_ASYNC);
15227 }
15228
15229#endif
15230
15231 last_brightness = brightness;
15232
15233#endif
15234
15235 return 0;
15236}
15237
15238#ifdef __OPCPN_USEICC__
15239
15240#define MLUT_TAG 0x6d4c5554L
15241#define VCGT_TAG 0x76636774L
15242
15243int GetIntEndian(unsigned char *s) {
15244 int ret;
15245 unsigned char *p;
15246 int i;
15247
15248 p = (unsigned char *)&ret;
15249
15250 if (1)
15251 for (i = sizeof(int) - 1; i > -1; --i) *p++ = s[i];
15252 else
15253 for (i = 0; i < (int)sizeof(int); ++i) *p++ = s[i];
15254
15255 return ret;
15256}
15257
15258unsigned short GetShortEndian(unsigned char *s) {
15259 unsigned short ret;
15260 unsigned char *p;
15261 int i;
15262
15263 p = (unsigned char *)&ret;
15264
15265 if (1)
15266 for (i = sizeof(unsigned short) - 1; i > -1; --i) *p++ = s[i];
15267 else
15268 for (i = 0; i < (int)sizeof(unsigned short); ++i) *p++ = s[i];
15269
15270 return ret;
15271}
15272
15273// Create a very simple Gamma correction file readable by xcalib
15274int CreateSimpleICCProfileFile(const char *file_name, double co_red,
15275 double co_green, double co_blue) {
15276 FILE *fp;
15277
15278 if (file_name) {
15279 fp = fopen(file_name, "wb");
15280 if (!fp) return -1; /* file can not be created */
15281 } else
15282 return -1; /* filename char pointer not valid */
15283
15284 // Write header
15285 char header[128];
15286 for (int i = 0; i < 128; i++) header[i] = 0;
15287
15288 fwrite(header, 128, 1, fp);
15289
15290 // Num tags
15291 int numTags0 = 1;
15292 int numTags = GetIntEndian((unsigned char *)&numTags0);
15293 fwrite(&numTags, 1, 4, fp);
15294
15295 int tagName0 = VCGT_TAG;
15296 int tagName = GetIntEndian((unsigned char *)&tagName0);
15297 fwrite(&tagName, 1, 4, fp);
15298
15299 int tagOffset0 = 128 + 4 * sizeof(int);
15300 int tagOffset = GetIntEndian((unsigned char *)&tagOffset0);
15301 fwrite(&tagOffset, 1, 4, fp);
15302
15303 int tagSize0 = 1;
15304 int tagSize = GetIntEndian((unsigned char *)&tagSize0);
15305 fwrite(&tagSize, 1, 4, fp);
15306
15307 fwrite(&tagName, 1, 4, fp); // another copy of tag
15308
15309 fwrite(&tagName, 1, 4, fp); // dummy
15310
15311 // Table type
15312
15313 /* VideoCardGammaTable (The simplest type) */
15314 int gammatype0 = 0;
15315 int gammatype = GetIntEndian((unsigned char *)&gammatype0);
15316 fwrite(&gammatype, 1, 4, fp);
15317
15318 int numChannels0 = 3;
15319 unsigned short numChannels = GetShortEndian((unsigned char *)&numChannels0);
15320 fwrite(&numChannels, 1, 2, fp);
15321
15322 int numEntries0 = 256;
15323 unsigned short numEntries = GetShortEndian((unsigned char *)&numEntries0);
15324 fwrite(&numEntries, 1, 2, fp);
15325
15326 int entrySize0 = 1;
15327 unsigned short entrySize = GetShortEndian((unsigned char *)&entrySize0);
15328 fwrite(&entrySize, 1, 2, fp);
15329
15330 unsigned char ramp[256];
15331
15332 // Red ramp
15333 for (int i = 0; i < 256; i++) ramp[i] = i * co_red / 100.;
15334 fwrite(ramp, 256, 1, fp);
15335
15336 // Green ramp
15337 for (int i = 0; i < 256; i++) ramp[i] = i * co_green / 100.;
15338 fwrite(ramp, 256, 1, fp);
15339
15340 // Blue ramp
15341 for (int i = 0; i < 256; i++) ramp[i] = i * co_blue / 100.;
15342 fwrite(ramp, 256, 1, fp);
15343
15344 fclose(fp);
15345
15346 return 0;
15347}
15348#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:1311
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1310
Generic Chart canvas base.
ChartCanvas * g_focusCanvas
Global instance.
Definition chcanv.cpp:1311
ChartCanvas * g_overlayCanvas
Global instance.
Definition chcanv.cpp:1310
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:13711
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:4538
void OnPaint(wxPaintEvent &event)
Definition chcanv.cpp:11755
bool GetCanvasPointPix(double rlat, double rlon, wxPoint *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) rounded to nearest integer.
Definition chcanv.cpp:4534
void DoMovement(long dt)
Performs a step of smooth movement animation on the chart canvas.
Definition chcanv.cpp:3632
void ShowSingleTideDialog(int x, int y, void *pvIDX)
Display tide/current dialog with single-instance management.
Definition chcanv.cpp:13670
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:4484
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:2339
int PrepareContextSelections(double lat, double lon)
Definition chcanv.cpp:7969
bool MouseEventSetup(wxMouseEvent &event, bool b_handle_dclick=true)
Definition chcanv.cpp:7778
bool PanCanvas(double dx, double dy)
Pans (moves) the canvas by the specified physical pixels in x and y directions.
Definition chcanv.cpp:5066
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:4615
bool SetVPScale(double sc, bool b_refresh=true)
Sets the viewport scale while maintaining the center point.
Definition chcanv.cpp:5346
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:4559
bool IsTideDialogOpen() const
Definition chcanv.cpp:13709
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:4621
void DrawTCWindow(int x, int y, void *pIDX)
Legacy tide dialog creation method.
Definition chcanv.cpp:13666
void GetDoubleCanvasPointPix(double rlat, double rlon, wxPoint2DDouble *r)
Convert latitude/longitude to canvas pixel coordinates (physical pixels) with double precision.
Definition chcanv.cpp:4479
bool SetViewPoint(double lat, double lon)
Centers the view on a specific lat/lon position.
Definition chcanv.cpp:5365
bool MouseEventProcessCanvas(wxMouseEvent &event)
Processes mouse events for core chart panning and zooming operations.
Definition chcanv.cpp:10162
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:1763
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.